callman-core 1.25.1 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/host/db/mysqlAdapter.d.ts.map +1 -1
  2. package/dist/host/db/mysqlAdapter.js +39 -9
  3. package/dist/host/db/mysqlAdapter.js.map +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/request/executeRequest.d.ts +1 -1
  9. package/dist/request/executeRequest.d.ts.map +1 -1
  10. package/dist/request/executeRequest.js +8 -1
  11. package/dist/request/executeRequest.js.map +1 -1
  12. package/dist/scenario-runner/adapters/request-executor.d.ts.map +1 -1
  13. package/dist/scenario-runner/adapters/request-executor.js +53 -3
  14. package/dist/scenario-runner/adapters/request-executor.js.map +1 -1
  15. package/dist/scenario-runner/adapters/script-executor.js +1 -1
  16. package/dist/scenario-runner/adapters/script-executor.js.map +1 -1
  17. package/dist/scenario-runner/adapters/types.d.ts +1 -0
  18. package/dist/scenario-runner/adapters/types.d.ts.map +1 -1
  19. package/dist/scenario-runner/boundSnapshot.d.ts +15 -0
  20. package/dist/scenario-runner/boundSnapshot.d.ts.map +1 -0
  21. package/dist/scenario-runner/boundSnapshot.js +67 -0
  22. package/dist/scenario-runner/boundSnapshot.js.map +1 -0
  23. package/dist/scenario-runner/runScenario.d.ts +1 -1
  24. package/dist/scenario-runner/runScenario.d.ts.map +1 -1
  25. package/dist/scenario-runner/runScenario.js +146 -25
  26. package/dist/scenario-runner/runScenario.js.map +1 -1
  27. package/dist/scenario-runner/runScenarioWithDataset.d.ts.map +1 -1
  28. package/dist/scenario-runner/runScenarioWithDataset.js +21 -6
  29. package/dist/scenario-runner/runScenarioWithDataset.js.map +1 -1
  30. package/dist/scenario-runner/types.d.ts +19 -0
  31. package/dist/scenario-runner/types.d.ts.map +1 -1
  32. package/dist/script/isolated-runner.d.ts +7 -1
  33. package/dist/script/isolated-runner.d.ts.map +1 -1
  34. package/dist/script/isolated-runner.js +11 -5
  35. package/dist/script/isolated-runner.js.map +1 -1
  36. package/dist/script/worker-entry.js +62 -28
  37. package/dist/script/worker-entry.js.map +1 -1
  38. package/dist/script/worker-pool.d.ts +60 -0
  39. package/dist/script/worker-pool.d.ts.map +1 -0
  40. package/dist/script/worker-pool.js +274 -0
  41. package/dist/script/worker-pool.js.map +1 -0
  42. package/dist/types/index.d.ts +1 -0
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/dist-cjs/host/db/mysqlAdapter.js +39 -9
  45. package/dist-cjs/host/db/mysqlAdapter.js.map +1 -1
  46. package/dist-cjs/index.js +4 -1
  47. package/dist-cjs/index.js.map +1 -1
  48. package/dist-cjs/request/executeRequest.js +8 -1
  49. package/dist-cjs/request/executeRequest.js.map +1 -1
  50. package/dist-cjs/scenario-runner/adapters/request-executor.js +53 -3
  51. package/dist-cjs/scenario-runner/adapters/request-executor.js.map +1 -1
  52. package/dist-cjs/scenario-runner/adapters/script-executor.js +1 -1
  53. package/dist-cjs/scenario-runner/adapters/script-executor.js.map +1 -1
  54. package/dist-cjs/scenario-runner/boundSnapshot.js +71 -0
  55. package/dist-cjs/scenario-runner/boundSnapshot.js.map +1 -0
  56. package/dist-cjs/scenario-runner/runScenario.js +146 -25
  57. package/dist-cjs/scenario-runner/runScenario.js.map +1 -1
  58. package/dist-cjs/scenario-runner/runScenarioWithDataset.js +21 -6
  59. package/dist-cjs/scenario-runner/runScenarioWithDataset.js.map +1 -1
  60. package/dist-cjs/script/isolated-runner.js +12 -5
  61. package/dist-cjs/script/isolated-runner.js.map +1 -1
  62. package/dist-cjs/script/worker-entry.js +62 -28
  63. package/dist-cjs/script/worker-entry.js.map +1 -1
  64. package/dist-cjs/script/worker-pool.js +312 -0
  65. package/dist-cjs/script/worker-pool.js.map +1 -0
  66. package/package.json +1 -1
@@ -4,6 +4,7 @@ import { evaluateAssertionConfig, validateAssertionConfig, } from "./assertion.j
4
4
  import { createSubScenarioStepId, buildRuntimeStepDescriptors, DEFAULT_END_LOOP_DELAY_MS, DEFAULT_END_LOOP_ITERATIONS, flattenScenarioNodesForContext, MAX_END_LOOP_ITERATIONS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_POLL_MAX_ATTEMPTS, MAX_POLL_ATTEMPTS, DEFAULT_FOREACH_MAX_ITERATIONS, MAX_FOREACH_ITERATIONS, DEFAULT_WS_CONNECT_TIMEOUT_MS, MAX_WS_CONNECT_TIMEOUT_MS, DEFAULT_WS_EXPECT_TIMEOUT_MS, MAX_WS_EXPECT_TIMEOUT_MS, MAX_WS_WAIT_DURATION_MS, MAX_WS_STEPS, DEFAULT_WS_CLOSE_CODE, } from "./graphTraversal.js";
5
5
  import { evaluateWebsocketExpect } from "./websocket.js";
6
6
  import { executeWithScenarioNodePolicy } from "./executionPolicyRunner.js";
7
+ import { boundSnapshot } from "./boundSnapshot.js";
7
8
  import { getLegacyScenarioNodeExecutionPolicy, getScenarioNodeExecutionPolicy, supportsScenarioNodeExecutionPolicy, } from "./nodePolicy.js";
8
9
  import { buildScenarioExecutionGraph, buildScenarioTemplateValues, collectExclusiveBranchNodeIds, evaluateScenarioExpression, resolveScenarioJsonTemplateString, resolveScenarioTemplateString, } from "./templateResolver.js";
9
10
  import { executeWaitNode, ScenarioWaitNodeCancelledError, ScenarioWaitNodeValidationError, } from "./waitNodeExecutor.js";
@@ -110,19 +111,24 @@ const createInputSnapshot = (context) => ({
110
111
  result: context.redisResult ?? null,
111
112
  },
112
113
  });
113
- const createContextSnapshot = (context) => ({
114
- environment: { ...context.environment },
115
- globals: { ...context.globals },
116
- workflowContext: { ...context.workflowContext },
117
- nodeMetaByLabel: cloneNodeMetaByLabel(context.nodeMetaByLabel),
118
- workflowMeta: cloneWorkflowExecutionMeta(context.workflowMeta),
119
- responseRoot: context.responseRoot ? { ...context.responseRoot } : null,
120
- lastResponse: context.lastResponse,
121
- dbResult: context.dbResult,
122
- kafkaEvent: context.kafkaEvent,
123
- kafkaCapture: context.kafkaCapture,
124
- redisResult: context.redisResult,
125
- });
114
+ const createContextSnapshot = (context, mode = "full") => {
115
+ // "bounded": the payload-bearing slots are size-capped copies instead of
116
+ // references, so the event log never pins a node's full response body.
117
+ const bound = mode === "bounded" ? boundSnapshot : (value) => value;
118
+ return {
119
+ environment: { ...context.environment },
120
+ globals: { ...context.globals },
121
+ workflowContext: bound({ ...context.workflowContext }),
122
+ nodeMetaByLabel: cloneNodeMetaByLabel(context.nodeMetaByLabel),
123
+ workflowMeta: cloneWorkflowExecutionMeta(context.workflowMeta),
124
+ responseRoot: context.responseRoot ? bound({ ...context.responseRoot }) : null,
125
+ lastResponse: bound(context.lastResponse),
126
+ dbResult: bound(context.dbResult),
127
+ kafkaEvent: bound(context.kafkaEvent),
128
+ kafkaCapture: bound(context.kafkaCapture),
129
+ redisResult: bound(context.redisResult),
130
+ };
131
+ };
126
132
  const buildKafkaEventValue = (message) => {
127
133
  const fallbackValue = {
128
134
  value: message.valueJson ?? message.valueText,
@@ -333,6 +339,7 @@ const createStepExecutionRecord = ({ stepId, stepType, title, }) => ({
333
339
  activeBranch: null,
334
340
  ai: null,
335
341
  inputData: null,
342
+ resolvedInput: null,
336
343
  outputData: null,
337
344
  incomingEdgeId: null,
338
345
  startedAt: null,
@@ -512,6 +519,7 @@ const updateRecordFromEvent = (records, event) => {
512
519
  activeBranch: event.activeBranch ?? current.activeBranch,
513
520
  ai: event.ai ?? current.ai,
514
521
  inputData: event.inputData ?? current.inputData,
522
+ resolvedInput: event.resolvedInput ?? current.resolvedInput ?? null,
515
523
  outputData: event.outputData ?? current.outputData,
516
524
  incomingEdgeId: event.incomingEdgeId ?? current.incomingEdgeId,
517
525
  startedAt: current.startedAt ?? event.at,
@@ -557,7 +565,7 @@ const callHandler = async (handlers, event) => {
557
565
  return;
558
566
  }
559
567
  };
560
- export const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAdapters, eventHandlers, signal, minNodeVisualStateMs = DEFAULT_MIN_NODE_VISUAL_STATE_MS, }) => {
568
+ export const runScenario = async ({ scenario, environment = {}, globals = {}, runtimeAdapters, eventHandlers, signal, minNodeVisualStateMs = DEFAULT_MIN_NODE_VISUAL_STATE_MS, eventContextMode = "full", }) => {
561
569
  const runId = createRunId();
562
570
  const startedAtMs = Date.now();
563
571
  const startedAt = new Date(startedAtMs).toISOString();
@@ -568,6 +576,9 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
568
576
  const localStatuses = new Map(runtimeStepIds.map((stepId) => [stepId, "idle"]));
569
577
  const records = new Map();
570
578
  const events = [];
579
+ // Every emitted event carries a context snapshot; `runtimeContext` is
580
+ // assigned just below and is always initialised before the first emit.
581
+ const snapshotContext = () => createContextSnapshot(runtimeContext, eventContextMode);
571
582
  const initialWorkflowMeta = createInitialWorkflowMeta({
572
583
  startedAt,
573
584
  totalNodes: runtimeStepIds.length,
@@ -862,6 +873,11 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
862
873
  }
863
874
  const executionStartedAtMs = Date.now();
864
875
  let inputData = createInputSnapshot(runtimeContext);
876
+ // The node's OWN resolved input (query + params, topic + payload, the
877
+ // request as sent…) — set by each node branch below once the values it
878
+ // will act on are known, so a failing node still reports what it tried.
879
+ let resolvedInput = null;
880
+ const resolvedInputForEvent = () => eventContextMode === "bounded" ? boundSnapshot(resolvedInput) : resolvedInput;
865
881
  let statusCode = null;
866
882
  let statusExpectation = null;
867
883
  let response = null;
@@ -892,6 +908,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
892
908
  statusCode = null;
893
909
  response = null;
894
910
  outputData = null;
911
+ resolvedInput = null;
895
912
  testResults = [];
896
913
  contractResults = [];
897
914
  contractSummary = null;
@@ -977,7 +994,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
977
994
  runId,
978
995
  stepId,
979
996
  node,
980
- context: createContextSnapshot(runtimeContext),
997
+ context: snapshotContext(),
981
998
  kafkaCapturePlans,
982
999
  captureFromTimestampMs: startedAtMs,
983
1000
  signal,
@@ -993,6 +1010,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
993
1010
  statusCode = requestResult.response.status;
994
1011
  statusExpectation = requestResult.statusExpectation ?? null;
995
1012
  outputData = requestResult.outputData;
1013
+ resolvedInput = requestResult.resolvedInput ?? null;
996
1014
  testResults = requestResult.testResults;
997
1015
  contractResults = requestResult.contractResults;
998
1016
  contractSummary = requestResult.contractSummary;
@@ -1024,6 +1042,12 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1024
1042
  return resolved;
1025
1043
  })
1026
1044
  : (node.data.config.params ?? []).map((param) => resolveScenarioTemplateString(param, templateValues));
1045
+ resolvedInput = {
1046
+ connectionId: node.data.config.connectionId,
1047
+ query: resolvedQuery,
1048
+ params: resolvedParams,
1049
+ useSession: Boolean(node.data.config.useSession),
1050
+ };
1027
1051
  const dbResult = await runtimeAdapters.dbExecutor({
1028
1052
  runId,
1029
1053
  stepId,
@@ -1038,7 +1062,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1038
1062
  },
1039
1063
  }
1040
1064
  : {}),
1041
- context: createContextSnapshot(runtimeContext),
1065
+ context: snapshotContext(),
1042
1066
  signal,
1043
1067
  });
1044
1068
  runtimeContext.dbResult = dbResult.dbResult;
@@ -1048,6 +1072,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1048
1072
  }
1049
1073
  }
1050
1074
  if (node.type === "wait") {
1075
+ resolvedInput = { durationMs: node.data.config.durationMs };
1051
1076
  try {
1052
1077
  outputData = await executeWaitNode({
1053
1078
  durationMs: node.data.config.durationMs,
@@ -1071,7 +1096,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1071
1096
  runId,
1072
1097
  stepId,
1073
1098
  node,
1074
- context: createContextSnapshot(runtimeContext),
1099
+ context: snapshotContext(),
1075
1100
  signal,
1076
1101
  });
1077
1102
  scriptSource = scriptResult.scriptSource;
@@ -1090,12 +1115,16 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1090
1115
  const resolvedCommands = node.data.config.commands
1091
1116
  .map((command) => resolveScenarioTemplateString(command, templateValues))
1092
1117
  .filter((command) => command.trim().length > 0);
1118
+ resolvedInput = {
1119
+ connectionId: node.data.config.connectionId,
1120
+ commands: resolvedCommands,
1121
+ };
1093
1122
  const redisResult = await runtimeAdapters.redisExecutor({
1094
1123
  runId,
1095
1124
  stepId,
1096
1125
  node,
1097
1126
  resolvedCommands,
1098
- context: createContextSnapshot(runtimeContext),
1127
+ context: snapshotContext(),
1099
1128
  signal,
1100
1129
  });
1101
1130
  runtimeContext.redisResult = redisResult.redisResult;
@@ -1118,6 +1147,13 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1118
1147
  : null;
1119
1148
  // Thinned emission keeps stored events bounded (~100 tick events).
1120
1149
  const pollEmitStride = Math.max(1, Math.ceil(pollMaxAttempts / 100));
1150
+ resolvedInput = {
1151
+ actionType: pollConfig.actionType,
1152
+ intervalMs: pollBaseIntervalMs,
1153
+ timeoutMs: pollTimeoutMs,
1154
+ maxAttempts: pollMaxAttempts,
1155
+ onTimeout: pollConfig.onTimeout ?? "fail",
1156
+ };
1121
1157
  // One tick = the embedded action through the SAME host adapters real
1122
1158
  // nodes use (desktop IPC / backend drivers both covered), publishing
1123
1159
  // results into the runtime context exactly like the real node would,
@@ -1147,7 +1183,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1147
1183
  position: node.position,
1148
1184
  data: { label: node.data.label, config: pollConfig.request },
1149
1185
  },
1150
- context: createContextSnapshot(runtimeContext),
1186
+ context: snapshotContext(),
1151
1187
  kafkaCapturePlans: [],
1152
1188
  captureFromTimestampMs: startedAtMs,
1153
1189
  signal,
@@ -1187,7 +1223,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1187
1223
  },
1188
1224
  resolvedQuery,
1189
1225
  resolvedParams,
1190
- context: createContextSnapshot(runtimeContext),
1226
+ context: snapshotContext(),
1191
1227
  signal,
1192
1228
  });
1193
1229
  runtimeContext.dbResult = tickResult.dbResult;
@@ -1210,7 +1246,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1210
1246
  data: { label: node.data.label, config: pollConfig.redis },
1211
1247
  },
1212
1248
  resolvedCommands,
1213
- context: createContextSnapshot(runtimeContext),
1249
+ context: snapshotContext(),
1214
1250
  signal,
1215
1251
  });
1216
1252
  runtimeContext.redisResult = tickResult.redisResult;
@@ -1228,7 +1264,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1228
1264
  position: node.position,
1229
1265
  data: { label: node.data.label, config: pollConfig.script },
1230
1266
  },
1231
- context: createContextSnapshot(runtimeContext),
1267
+ context: snapshotContext(),
1232
1268
  signal,
1233
1269
  });
1234
1270
  scriptSource = tickResult.scriptSource;
@@ -1347,11 +1383,17 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1347
1383
  workflowMeta: runtimeContext.workflowMeta,
1348
1384
  useRichBlocks: node.data.config.advanced?.useRichBlocks !== false,
1349
1385
  });
1386
+ resolvedInput = {
1387
+ provider: node.data.config.provider,
1388
+ severity: normalizeNotificationSeverity(node.data.config.severity),
1389
+ channel: resolvedChannelOverride,
1390
+ message: resolvedMessage || "N/A",
1391
+ };
1350
1392
  const notificationResult = await runtimeAdapters.notificationExecutor({
1351
1393
  runId,
1352
1394
  stepId,
1353
1395
  node,
1354
- context: createContextSnapshot(runtimeContext),
1396
+ context: snapshotContext(),
1355
1397
  deliveryRequest,
1356
1398
  signal,
1357
1399
  });
@@ -1374,6 +1416,13 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1374
1416
  : "";
1375
1417
  const resolvedKey = resolvedKeyRaw.length > 0 ? resolvedKeyRaw : null;
1376
1418
  const resolvedValue = resolveScenarioTemplateString(node.data.config.messageValue ?? "", templateValues);
1419
+ resolvedInput = {
1420
+ mode: "publish",
1421
+ topic: resolvedTopic,
1422
+ key: resolvedKey,
1423
+ valueFormat,
1424
+ value: resolvedValue,
1425
+ };
1377
1426
  if (valueFormat === "json" && resolvedValue.trim().length > 0) {
1378
1427
  try {
1379
1428
  JSON.parse(resolvedValue);
@@ -1395,6 +1444,14 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1395
1444
  continue;
1396
1445
  resolvedHeaders[headerKey] = resolveScenarioTemplateString(header.value, templateValues);
1397
1446
  }
1447
+ resolvedInput = {
1448
+ mode: "publish",
1449
+ topic: resolvedTopic,
1450
+ key: resolvedKey,
1451
+ valueFormat,
1452
+ value: resolvedValue,
1453
+ headers: resolvedHeaders,
1454
+ };
1398
1455
  const { publishResult } = await runtimeAdapters.kafkaExecutor({
1399
1456
  runId,
1400
1457
  stepId,
@@ -1420,6 +1477,12 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1420
1477
  else {
1421
1478
  const rules = node.data.config.rules?.filter((rule) => rule.trim()) ?? [];
1422
1479
  const resolvedMatchMode = node.data.config.matchMode ?? "regex";
1480
+ resolvedInput = {
1481
+ mode: "consume",
1482
+ topic: resolvedTopic,
1483
+ matchMode: resolvedMatchMode,
1484
+ rules,
1485
+ };
1423
1486
  const prefetchedCaptureResult = prefetchedKafkaResults.get(stepId);
1424
1487
  const captureResult = prefetchedCaptureResult ??
1425
1488
  (await runtimeAdapters.kafkaExecutor({
@@ -1527,6 +1590,13 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1527
1590
  rules: step.rules,
1528
1591
  };
1529
1592
  });
1593
+ resolvedInput = {
1594
+ url: resolvedUrl,
1595
+ headers: resolvedHeaders,
1596
+ subprotocols: resolvedSubprotocols,
1597
+ connectTimeoutMs,
1598
+ steps: resolvedSteps,
1599
+ };
1530
1600
  const wsExecution = await runtimeAdapters.websocketExecutor({
1531
1601
  runId,
1532
1602
  stepId,
@@ -1647,12 +1717,19 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1647
1717
  runId,
1648
1718
  stepId,
1649
1719
  node,
1650
- context: createContextSnapshot(runtimeContext),
1720
+ context: snapshotContext(),
1651
1721
  signal,
1652
1722
  });
1653
1723
  throwIfStopped(signal);
1654
1724
  aiDetails = aiResult.details;
1655
1725
  outputData = aiResult.outputData;
1726
+ resolvedInput = aiDetails
1727
+ ? {
1728
+ provider: aiDetails.provider,
1729
+ model: aiDetails.model,
1730
+ prompt: aiDetails.resolvedPrompt,
1731
+ }
1732
+ : null;
1656
1733
  if (aiResult.failureMessage) {
1657
1734
  throw new ScenarioNodeFailureError(aiResult.failureMessage);
1658
1735
  }
@@ -1681,6 +1758,11 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1681
1758
  conditionMode = conditionPayload.mode;
1682
1759
  conditionDebug = conditionPayload.debug;
1683
1760
  outputData = conditionPayload;
1761
+ resolvedInput = {
1762
+ mode: conditionPayload.mode,
1763
+ expression: conditionPayload.expression,
1764
+ preview: conditionPayload.preview,
1765
+ };
1684
1766
  await emit({
1685
1767
  type: "step:condition",
1686
1768
  runId,
@@ -1721,6 +1803,13 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1721
1803
  });
1722
1804
  assertionResults = assertionEvaluation.checks;
1723
1805
  assertionSummary = assertionEvaluation;
1806
+ resolvedInput = {
1807
+ matchMode: assertionEvaluation.matchMode,
1808
+ checks: assertionEvaluation.checks.map((check) => ({
1809
+ name: check.name,
1810
+ enabled: check.enabled,
1811
+ })),
1812
+ };
1724
1813
  // Downstream nodes can reference e.g. {{AssertNode1.passed}}.
1725
1814
  outputData = {
1726
1815
  passed: assertionEvaluation.passed,
@@ -1825,6 +1914,19 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
1825
1914
  const collectResults = foreachConfig.collectResults !== false;
1826
1915
  // Thinned emission keeps stored events bounded (~100 iteration markers).
1827
1916
  const iterationEmitStride = Math.max(1, Math.ceil(processLimit / 100));
1917
+ resolvedInput = {
1918
+ sourceMode: foreachConfig.sourceMode,
1919
+ itemsPath: foreachConfig.sourceMode === "inline"
1920
+ ? null
1921
+ : (foreachConfig.itemsPath ?? "").trim(),
1922
+ itemAlias: itemAlias || null,
1923
+ itemCount: items.length,
1924
+ maxIterations,
1925
+ delayMs: iterationDelayMs,
1926
+ onItemFailure,
1927
+ collectResults,
1928
+ items,
1929
+ };
1828
1930
  const internalGraph = buildScenarioExecutionGraph(foreachConfig.nodes, foreachConfig.edges);
1829
1931
  const internalScope = {
1830
1932
  graph: internalGraph,
@@ -2008,6 +2110,21 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
2008
2110
  const writtenEnvironment = {};
2009
2111
  const writtenGlobals = {};
2010
2112
  const extractionOutcomes = [];
2113
+ resolvedInput = {
2114
+ assignments: (node.data.config.assignments ?? [])
2115
+ .filter((assignment) => assignment && assignment.enabled !== false)
2116
+ .map((assignment) => ({
2117
+ scope: assignment.scope === "global" ? "global" : "environment",
2118
+ key: assignment.key ?? "",
2119
+ ...(assignment.extractorType && assignment.extractorType !== "template"
2120
+ ? {
2121
+ extractorType: assignment.extractorType,
2122
+ source: assignment.source ?? "",
2123
+ expression: assignment.expression ?? "",
2124
+ }
2125
+ : { value: assignment.value ?? "" }),
2126
+ })),
2127
+ };
2011
2128
  for (const assignment of node.data.config.assignments ?? []) {
2012
2129
  if (!assignment || assignment.enabled === false) {
2013
2130
  continue;
@@ -2093,6 +2210,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
2093
2210
  }
2094
2211
  if (node.type === "aggregator") {
2095
2212
  const resolvedTemplate = resolveScenarioJsonTemplateString(node.data.config.template, templateValues);
2213
+ resolvedInput = { template: resolvedTemplate };
2096
2214
  try {
2097
2215
  outputData = JSON.parse(resolvedTemplate);
2098
2216
  }
@@ -2238,6 +2356,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
2238
2356
  activeBranch,
2239
2357
  ai: aiDetails,
2240
2358
  inputData,
2359
+ resolvedInput: resolvedInputForEvent(),
2241
2360
  outputData,
2242
2361
  incomingEdgeId: incomingEdgeId ?? null,
2243
2362
  });
@@ -2321,6 +2440,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
2321
2440
  activeBranch,
2322
2441
  ai: aiDetails,
2323
2442
  inputData,
2443
+ resolvedInput: resolvedInputForEvent(),
2324
2444
  outputData,
2325
2445
  incomingEdgeId: incomingEdgeId ?? null,
2326
2446
  });
@@ -2399,6 +2519,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
2399
2519
  activeBranch,
2400
2520
  ai: aiDetails,
2401
2521
  inputData,
2522
+ resolvedInput: resolvedInputForEvent(),
2402
2523
  outputData,
2403
2524
  incomingEdgeId: incomingEdgeId ?? null,
2404
2525
  });
@@ -2639,7 +2760,7 @@ export const runScenario = async ({ scenario, environment = {}, globals = {}, ru
2639
2760
  durationMs: Date.now() - startedAtMs,
2640
2761
  totalSteps: runtimeStepIds.length,
2641
2762
  completedSteps: Array.from(localStatuses.values()).filter(isTerminalStatus).length,
2642
- context: createContextSnapshot(runtimeContext),
2763
+ context: snapshotContext(),
2643
2764
  workflowMeta: cloneWorkflowExecutionMeta(finalWorkflowMeta),
2644
2765
  nodeExecutions: runtimeStepIds.map((stepId) => {
2645
2766
  const descriptor = runtimeStepDescriptors.find((entry) => entry.stepId === stepId);