@marktoflow/core 2.0.0-alpha.12

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 (142) hide show
  1. package/README.md +307 -0
  2. package/dist/bundle.d.ts +43 -0
  3. package/dist/bundle.d.ts.map +1 -0
  4. package/dist/bundle.js +202 -0
  5. package/dist/bundle.js.map +1 -0
  6. package/dist/config.d.ts +33 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +27 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/core-tools.d.ts +39 -0
  11. package/dist/core-tools.d.ts.map +1 -0
  12. package/dist/core-tools.js +58 -0
  13. package/dist/core-tools.js.map +1 -0
  14. package/dist/costs.d.ts +182 -0
  15. package/dist/costs.d.ts.map +1 -0
  16. package/dist/costs.js +464 -0
  17. package/dist/costs.js.map +1 -0
  18. package/dist/credentials.d.ts +162 -0
  19. package/dist/credentials.d.ts.map +1 -0
  20. package/dist/credentials.js +646 -0
  21. package/dist/credentials.js.map +1 -0
  22. package/dist/engine.d.ts +243 -0
  23. package/dist/engine.d.ts.map +1 -0
  24. package/dist/engine.js +1453 -0
  25. package/dist/engine.js.map +1 -0
  26. package/dist/env.d.ts +59 -0
  27. package/dist/env.d.ts.map +1 -0
  28. package/dist/env.js +256 -0
  29. package/dist/env.js.map +1 -0
  30. package/dist/failover.d.ts +43 -0
  31. package/dist/failover.d.ts.map +1 -0
  32. package/dist/failover.js +53 -0
  33. package/dist/failover.js.map +1 -0
  34. package/dist/filewatcher.d.ts +32 -0
  35. package/dist/filewatcher.d.ts.map +1 -0
  36. package/dist/filewatcher.js +92 -0
  37. package/dist/filewatcher.js.map +1 -0
  38. package/dist/index.d.ts +40 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +77 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/logging.d.ts +62 -0
  43. package/dist/logging.d.ts.map +1 -0
  44. package/dist/logging.js +211 -0
  45. package/dist/logging.js.map +1 -0
  46. package/dist/mcp-loader.d.ts +29 -0
  47. package/dist/mcp-loader.d.ts.map +1 -0
  48. package/dist/mcp-loader.js +60 -0
  49. package/dist/mcp-loader.js.map +1 -0
  50. package/dist/metrics.d.ts +19 -0
  51. package/dist/metrics.d.ts.map +1 -0
  52. package/dist/metrics.js +65 -0
  53. package/dist/metrics.js.map +1 -0
  54. package/dist/models.d.ts +1686 -0
  55. package/dist/models.d.ts.map +1 -0
  56. package/dist/models.js +333 -0
  57. package/dist/models.js.map +1 -0
  58. package/dist/parser.d.ts +40 -0
  59. package/dist/parser.d.ts.map +1 -0
  60. package/dist/parser.js +532 -0
  61. package/dist/parser.js.map +1 -0
  62. package/dist/permissions.d.ts +49 -0
  63. package/dist/permissions.d.ts.map +1 -0
  64. package/dist/permissions.js +286 -0
  65. package/dist/permissions.js.map +1 -0
  66. package/dist/plugins.d.ts +105 -0
  67. package/dist/plugins.d.ts.map +1 -0
  68. package/dist/plugins.js +182 -0
  69. package/dist/plugins.js.map +1 -0
  70. package/dist/prompt-loader.d.ts +47 -0
  71. package/dist/prompt-loader.d.ts.map +1 -0
  72. package/dist/prompt-loader.js +268 -0
  73. package/dist/prompt-loader.js.map +1 -0
  74. package/dist/queue.d.ts +114 -0
  75. package/dist/queue.d.ts.map +1 -0
  76. package/dist/queue.js +385 -0
  77. package/dist/queue.js.map +1 -0
  78. package/dist/rollback.d.ts +117 -0
  79. package/dist/rollback.d.ts.map +1 -0
  80. package/dist/rollback.js +374 -0
  81. package/dist/rollback.js.map +1 -0
  82. package/dist/routing.d.ts +144 -0
  83. package/dist/routing.d.ts.map +1 -0
  84. package/dist/routing.js +457 -0
  85. package/dist/routing.js.map +1 -0
  86. package/dist/scheduler.d.ts +91 -0
  87. package/dist/scheduler.d.ts.map +1 -0
  88. package/dist/scheduler.js +259 -0
  89. package/dist/scheduler.js.map +1 -0
  90. package/dist/script-tool.d.ts +22 -0
  91. package/dist/script-tool.d.ts.map +1 -0
  92. package/dist/script-tool.js +90 -0
  93. package/dist/script-tool.js.map +1 -0
  94. package/dist/sdk-registry.d.ts +94 -0
  95. package/dist/sdk-registry.d.ts.map +1 -0
  96. package/dist/sdk-registry.js +328 -0
  97. package/dist/sdk-registry.js.map +1 -0
  98. package/dist/security.d.ts +155 -0
  99. package/dist/security.d.ts.map +1 -0
  100. package/dist/security.js +362 -0
  101. package/dist/security.js.map +1 -0
  102. package/dist/state.d.ts +67 -0
  103. package/dist/state.d.ts.map +1 -0
  104. package/dist/state.js +276 -0
  105. package/dist/state.js.map +1 -0
  106. package/dist/templates.d.ts +70 -0
  107. package/dist/templates.d.ts.map +1 -0
  108. package/dist/templates.js +244 -0
  109. package/dist/templates.js.map +1 -0
  110. package/dist/tool-base.d.ts +54 -0
  111. package/dist/tool-base.d.ts.map +1 -0
  112. package/dist/tool-base.js +43 -0
  113. package/dist/tool-base.js.map +1 -0
  114. package/dist/tool-registry.d.ts +24 -0
  115. package/dist/tool-registry.d.ts.map +1 -0
  116. package/dist/tool-registry.js +164 -0
  117. package/dist/tool-registry.js.map +1 -0
  118. package/dist/tools/custom-tool.d.ts +16 -0
  119. package/dist/tools/custom-tool.d.ts.map +1 -0
  120. package/dist/tools/custom-tool.js +85 -0
  121. package/dist/tools/custom-tool.js.map +1 -0
  122. package/dist/tools/mcp-tool.d.ts +16 -0
  123. package/dist/tools/mcp-tool.d.ts.map +1 -0
  124. package/dist/tools/mcp-tool.js +98 -0
  125. package/dist/tools/mcp-tool.js.map +1 -0
  126. package/dist/tools/openapi-tool.d.ts +17 -0
  127. package/dist/tools/openapi-tool.d.ts.map +1 -0
  128. package/dist/tools/openapi-tool.js +165 -0
  129. package/dist/tools/openapi-tool.js.map +1 -0
  130. package/dist/trigger-manager.d.ts +26 -0
  131. package/dist/trigger-manager.d.ts.map +1 -0
  132. package/dist/trigger-manager.js +107 -0
  133. package/dist/trigger-manager.js.map +1 -0
  134. package/dist/webhook.d.ts +95 -0
  135. package/dist/webhook.d.ts.map +1 -0
  136. package/dist/webhook.js +261 -0
  137. package/dist/webhook.js.map +1 -0
  138. package/dist/workflow-tools.d.ts +102 -0
  139. package/dist/workflow-tools.d.ts.map +1 -0
  140. package/dist/workflow-tools.js +130 -0
  141. package/dist/workflow-tools.js.map +1 -0
  142. package/package.json +62 -0
package/dist/engine.js ADDED
@@ -0,0 +1,1453 @@
1
+ /**
2
+ * Workflow Execution Engine for marktoflow v2.0
3
+ *
4
+ * Executes workflow steps with retry logic, variable resolution,
5
+ * and SDK invocation.
6
+ */
7
+ import { StepStatus, WorkflowStatus, createExecutionContext, createStepResult, isActionStep, isSubWorkflowStep, isIfStep, isSwitchStep, isForEachStep, isWhileStep, isMapStep, isFilterStep, isReduceStep, isParallelStep, isTryStep, } from './models.js';
8
+ import { mergePermissions, toSecurityPolicy, } from './permissions.js';
9
+ import { loadPromptFile, resolvePromptTemplate, validatePromptInputs, } from './prompt-loader.js';
10
+ import { DEFAULT_FAILOVER_CONFIG, AgentHealthTracker, FailoverReason, } from './failover.js';
11
+ import { parseFile } from './parser.js';
12
+ import { resolve, dirname } from 'node:path';
13
+ // ============================================================================
14
+ // Helper Functions
15
+ // ============================================================================
16
+ /**
17
+ * Convert error to string for display/logging
18
+ */
19
+ function errorToString(error) {
20
+ if (!error)
21
+ return 'Unknown error';
22
+ if (typeof error === 'string')
23
+ return error;
24
+ if (error instanceof Error)
25
+ return error.message;
26
+ try {
27
+ return JSON.stringify(error);
28
+ }
29
+ catch {
30
+ return String(error);
31
+ }
32
+ }
33
+ // ============================================================================
34
+ // Retry Policy
35
+ // ============================================================================
36
+ export class RetryPolicy {
37
+ maxRetries;
38
+ baseDelay;
39
+ maxDelay;
40
+ exponentialBase;
41
+ jitter;
42
+ constructor(maxRetries = 3, baseDelay = 1000, maxDelay = 30000, exponentialBase = 2, jitter = 0.1) {
43
+ this.maxRetries = maxRetries;
44
+ this.baseDelay = baseDelay;
45
+ this.maxDelay = maxDelay;
46
+ this.exponentialBase = exponentialBase;
47
+ this.jitter = jitter;
48
+ }
49
+ /**
50
+ * Calculate delay for a given retry attempt.
51
+ */
52
+ getDelay(attempt) {
53
+ const exponentialDelay = this.baseDelay * Math.pow(this.exponentialBase, attempt);
54
+ const clampedDelay = Math.min(exponentialDelay, this.maxDelay);
55
+ // Add jitter
56
+ const jitterAmount = clampedDelay * this.jitter * (Math.random() * 2 - 1);
57
+ return Math.max(0, clampedDelay + jitterAmount);
58
+ }
59
+ }
60
+ export class CircuitBreaker {
61
+ failureThreshold;
62
+ recoveryTimeout;
63
+ halfOpenMaxCalls;
64
+ state = 'CLOSED';
65
+ failures = 0;
66
+ lastFailureTime = 0;
67
+ halfOpenCalls = 0;
68
+ constructor(failureThreshold = 5, recoveryTimeout = 30000, halfOpenMaxCalls = 3) {
69
+ this.failureThreshold = failureThreshold;
70
+ this.recoveryTimeout = recoveryTimeout;
71
+ this.halfOpenMaxCalls = halfOpenMaxCalls;
72
+ }
73
+ canExecute() {
74
+ if (this.state === 'CLOSED') {
75
+ return true;
76
+ }
77
+ if (this.state === 'OPEN') {
78
+ const timeSinceFailure = Date.now() - this.lastFailureTime;
79
+ if (timeSinceFailure >= this.recoveryTimeout) {
80
+ this.state = 'HALF_OPEN';
81
+ this.halfOpenCalls = 0;
82
+ return true;
83
+ }
84
+ return false;
85
+ }
86
+ // HALF_OPEN
87
+ return this.halfOpenCalls < this.halfOpenMaxCalls;
88
+ }
89
+ recordSuccess() {
90
+ this.failures = 0;
91
+ this.state = 'CLOSED';
92
+ }
93
+ recordFailure() {
94
+ this.failures++;
95
+ this.lastFailureTime = Date.now();
96
+ if (this.state === 'HALF_OPEN') {
97
+ this.state = 'OPEN';
98
+ }
99
+ else if (this.failures >= this.failureThreshold) {
100
+ this.state = 'OPEN';
101
+ }
102
+ }
103
+ getState() {
104
+ return this.state;
105
+ }
106
+ reset() {
107
+ this.state = 'CLOSED';
108
+ this.failures = 0;
109
+ this.halfOpenCalls = 0;
110
+ }
111
+ }
112
+ // ============================================================================
113
+ // Variable Resolution
114
+ // ============================================================================
115
+ /**
116
+ * Smart serialization for template interpolation.
117
+ * Converts values to strings in a meaningful way for different types.
118
+ */
119
+ function serializeValue(value) {
120
+ if (value === undefined || value === null) {
121
+ return '';
122
+ }
123
+ // For primitives, use standard string conversion
124
+ if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
125
+ return String(value);
126
+ }
127
+ // For objects and arrays, use JSON serialization for readability
128
+ // This makes them useful in prompts and messages instead of "[object Object]"
129
+ try {
130
+ return JSON.stringify(value, null, 2);
131
+ }
132
+ catch (error) {
133
+ // Fallback to string conversion if JSON serialization fails
134
+ return String(value);
135
+ }
136
+ }
137
+ /**
138
+ * Resolve template variables in a value.
139
+ * Supports {{variable}} and {{inputs.name}} syntax.
140
+ */
141
+ export function resolveTemplates(value, context) {
142
+ if (typeof value === 'string') {
143
+ // Check if the entire string is a single template expression
144
+ const singleTemplateMatch = value.match(/^\{\{([^}]+)\}\}$/);
145
+ if (singleTemplateMatch) {
146
+ // Return the actual value without converting to string
147
+ const path = singleTemplateMatch[1].trim();
148
+ const resolved = resolveVariablePath(path, context);
149
+ // For single template expressions, return the actual value (could be object, array, etc.)
150
+ // If undefined, return empty string for backward compatibility
151
+ return resolved !== undefined ? resolved : '';
152
+ }
153
+ // Otherwise, do string interpolation with smart serialization
154
+ return value.replace(/\{\{([^}]+)\}\}/g, (_, varPath) => {
155
+ const path = varPath.trim();
156
+ const resolved = resolveVariablePath(path, context);
157
+ return serializeValue(resolved);
158
+ });
159
+ }
160
+ if (Array.isArray(value)) {
161
+ return value.map((v) => resolveTemplates(v, context));
162
+ }
163
+ if (value && typeof value === 'object') {
164
+ const result = {};
165
+ for (const [k, v] of Object.entries(value)) {
166
+ result[k] = resolveTemplates(v, context);
167
+ }
168
+ return result;
169
+ }
170
+ return value;
171
+ }
172
+ /**
173
+ * Resolve a variable path from context.
174
+ * First checks inputs.*, then variables, then stepMetadata, then direct context properties.
175
+ * Exported to allow access from condition evaluation.
176
+ */
177
+ export function resolveVariablePath(path, context) {
178
+ // Handle inputs.* prefix
179
+ if (path.startsWith('inputs.')) {
180
+ const inputPath = path.slice(7); // Remove 'inputs.'
181
+ return getNestedValue(context.inputs, inputPath);
182
+ }
183
+ // Check variables first (most common case)
184
+ const fromVars = getNestedValue(context.variables, path);
185
+ if (fromVars !== undefined) {
186
+ return fromVars;
187
+ }
188
+ // Check step metadata (for status checks like: step_id.status)
189
+ const fromStepMeta = getNestedValue(context.stepMetadata, path);
190
+ if (fromStepMeta !== undefined) {
191
+ return fromStepMeta;
192
+ }
193
+ // Fall back to direct context access
194
+ return getNestedValue(context, path);
195
+ }
196
+ /**
197
+ * Get a nested value from an object using dot notation and array indexing.
198
+ * Supports paths like: "user.name", "items[0].name", "data.users[1].email"
199
+ */
200
+ function getNestedValue(obj, path) {
201
+ if (obj === null || obj === undefined) {
202
+ return undefined;
203
+ }
204
+ // Parse path into parts, handling both dot notation and array indexing
205
+ // Convert "a.b[0].c[1]" into ["a", "b", "0", "c", "1"]
206
+ const parts = [];
207
+ let current = '';
208
+ for (let i = 0; i < path.length; i++) {
209
+ const char = path[i];
210
+ if (char === '.') {
211
+ if (current) {
212
+ parts.push(current);
213
+ current = '';
214
+ }
215
+ }
216
+ else if (char === '[') {
217
+ if (current) {
218
+ parts.push(current);
219
+ current = '';
220
+ }
221
+ }
222
+ else if (char === ']') {
223
+ if (current) {
224
+ parts.push(current);
225
+ current = '';
226
+ }
227
+ }
228
+ else {
229
+ current += char;
230
+ }
231
+ }
232
+ if (current) {
233
+ parts.push(current);
234
+ }
235
+ // Traverse the object using the parsed parts
236
+ let result = obj;
237
+ for (const part of parts) {
238
+ if (result === null || result === undefined) {
239
+ return undefined;
240
+ }
241
+ // Check if part is a number (array index)
242
+ const index = Number(part);
243
+ if (!isNaN(index) && Array.isArray(result)) {
244
+ result = result[index];
245
+ }
246
+ else if (typeof result === 'object') {
247
+ result = result[part];
248
+ }
249
+ else {
250
+ return undefined;
251
+ }
252
+ }
253
+ return result;
254
+ }
255
+ export class WorkflowEngine {
256
+ config;
257
+ retryPolicy;
258
+ circuitBreakers = new Map();
259
+ events;
260
+ stateStore;
261
+ rollbackRegistry;
262
+ failoverConfig;
263
+ healthTracker;
264
+ failoverEvents = [];
265
+ workflowPath; // Base path for resolving sub-workflows
266
+ workflowPermissions; // Workflow-level permissions
267
+ promptCache = new Map(); // Cache for loaded prompts
268
+ constructor(config = {}, events = {}, stateStore) {
269
+ this.config = {
270
+ defaultTimeout: config.defaultTimeout ?? 60000,
271
+ maxRetries: config.maxRetries ?? 3,
272
+ retryBaseDelay: config.retryBaseDelay ?? 1000,
273
+ retryMaxDelay: config.retryMaxDelay ?? 30000,
274
+ defaultAgent: config.defaultAgent,
275
+ defaultModel: config.defaultModel,
276
+ };
277
+ this.retryPolicy = new RetryPolicy(this.config.maxRetries, this.config.retryBaseDelay, this.config.retryMaxDelay);
278
+ this.events = events;
279
+ this.stateStore = stateStore;
280
+ this.rollbackRegistry = config.rollbackRegistry;
281
+ this.failoverConfig = { ...DEFAULT_FAILOVER_CONFIG, ...(config.failoverConfig ?? {}) };
282
+ this.healthTracker = config.healthTracker ?? new AgentHealthTracker();
283
+ }
284
+ /**
285
+ * Execute a single step - dispatcher to specialized execution methods.
286
+ */
287
+ async executeStep(step, context, sdkRegistry, stepExecutor) {
288
+ // Check conditions first (applies to all step types)
289
+ if (step.conditions && !this.evaluateConditions(step.conditions, context)) {
290
+ return createStepResult(step.id, StepStatus.SKIPPED, null, new Date());
291
+ }
292
+ // Dispatch to specialized execution method based on step type
293
+ if (isIfStep(step)) {
294
+ return this.executeIfStep(step, context, sdkRegistry, stepExecutor);
295
+ }
296
+ if (isSwitchStep(step)) {
297
+ return this.executeSwitchStep(step, context, sdkRegistry, stepExecutor);
298
+ }
299
+ if (isForEachStep(step)) {
300
+ return this.executeForEachStep(step, context, sdkRegistry, stepExecutor);
301
+ }
302
+ if (isWhileStep(step)) {
303
+ return this.executeWhileStep(step, context, sdkRegistry, stepExecutor);
304
+ }
305
+ if (isMapStep(step)) {
306
+ return this.executeMapStep(step, context, sdkRegistry, stepExecutor);
307
+ }
308
+ if (isFilterStep(step)) {
309
+ return this.executeFilterStep(step, context, sdkRegistry, stepExecutor);
310
+ }
311
+ if (isReduceStep(step)) {
312
+ return this.executeReduceStep(step, context, sdkRegistry, stepExecutor);
313
+ }
314
+ if (isParallelStep(step)) {
315
+ return this.executeParallelStep(step, context, sdkRegistry, stepExecutor);
316
+ }
317
+ if (isTryStep(step)) {
318
+ return this.executeTryStep(step, context, sdkRegistry, stepExecutor);
319
+ }
320
+ // Default: action or workflow step
321
+ return this.executeStepWithFailover(step, context, sdkRegistry, stepExecutor);
322
+ }
323
+ /**
324
+ * Execute a workflow.
325
+ */
326
+ async execute(workflow, inputs = {}, sdkRegistry, stepExecutor) {
327
+ const context = createExecutionContext(workflow, inputs);
328
+ const stepResults = [];
329
+ const startedAt = new Date();
330
+ // Store workflow-level permissions and defaults
331
+ this.workflowPermissions = workflow.permissions;
332
+ // Use workflow defaults if not set in engine config
333
+ if (!this.config.defaultAgent && workflow.defaultAgent) {
334
+ this.config.defaultAgent = workflow.defaultAgent;
335
+ }
336
+ if (!this.config.defaultModel && workflow.defaultModel) {
337
+ this.config.defaultModel = workflow.defaultModel;
338
+ }
339
+ context.status = WorkflowStatus.RUNNING;
340
+ this.events.onWorkflowStart?.(workflow, context);
341
+ if (this.stateStore) {
342
+ this.stateStore.createExecution({
343
+ runId: context.runId,
344
+ workflowId: workflow.metadata.id,
345
+ workflowPath: 'unknown',
346
+ status: WorkflowStatus.RUNNING,
347
+ startedAt: startedAt,
348
+ completedAt: null,
349
+ currentStep: 0,
350
+ totalSteps: workflow.steps.length,
351
+ inputs: inputs,
352
+ outputs: null,
353
+ error: null,
354
+ metadata: null,
355
+ });
356
+ }
357
+ try {
358
+ for (let i = 0; i < workflow.steps.length; i++) {
359
+ const step = workflow.steps[i];
360
+ context.currentStepIndex = i;
361
+ // Execute step using dispatcher
362
+ const result = await this.executeStep(step, context, sdkRegistry, stepExecutor);
363
+ stepResults.push(result);
364
+ // Store step metadata (status, error, etc.) in separate field for condition evaluation
365
+ // This allows conditions like: step_id.status == 'failed'
366
+ context.stepMetadata[step.id] = {
367
+ status: result.status.toLowerCase(),
368
+ retryCount: result.retryCount,
369
+ ...(result.error ? { error: errorToString(result.error) } : {}),
370
+ };
371
+ // Store output variable
372
+ if (step.outputVariable && result.status === StepStatus.COMPLETED) {
373
+ context.variables[step.outputVariable] = result.output;
374
+ }
375
+ // Check if this step set workflow outputs (from workflow.set_outputs action)
376
+ if (result.status === StepStatus.COMPLETED &&
377
+ result.output &&
378
+ typeof result.output === 'object' &&
379
+ '__workflow_outputs__' in result.output) {
380
+ const outputObj = result.output;
381
+ const outputs = outputObj['__workflow_outputs__'];
382
+ context.workflowOutputs = outputs;
383
+ }
384
+ // Handle failure
385
+ if (result.status === StepStatus.FAILED) {
386
+ // Get error action from step if it has error handling
387
+ let errorAction = 'stop';
388
+ if ('errorHandling' in step && step.errorHandling?.action) {
389
+ errorAction = step.errorHandling.action;
390
+ }
391
+ if (errorAction === 'stop') {
392
+ context.status = WorkflowStatus.FAILED;
393
+ const workflowError = result.error ? errorToString(result.error) : `Step ${step.id} failed`;
394
+ const workflowResult = this.buildWorkflowResult(workflow, context, stepResults, startedAt, workflowError);
395
+ this.events.onWorkflowComplete?.(workflow, workflowResult);
396
+ return workflowResult;
397
+ }
398
+ // 'continue' - keep going
399
+ if (errorAction === 'rollback') {
400
+ if (this.rollbackRegistry) {
401
+ await this.rollbackRegistry.rollbackAllAsync({
402
+ context,
403
+ inputs: context.inputs,
404
+ variables: context.variables,
405
+ });
406
+ }
407
+ context.status = WorkflowStatus.FAILED;
408
+ const workflowError = result.error ? errorToString(result.error) : `Step ${step.id} failed`;
409
+ const workflowResult = this.buildWorkflowResult(workflow, context, stepResults, startedAt, workflowError);
410
+ this.events.onWorkflowComplete?.(workflow, workflowResult);
411
+ return workflowResult;
412
+ }
413
+ }
414
+ }
415
+ // Determine final status
416
+ context.status = WorkflowStatus.COMPLETED;
417
+ }
418
+ catch (error) {
419
+ context.status = WorkflowStatus.FAILED;
420
+ if (this.stateStore) {
421
+ this.stateStore.updateExecution(context.runId, {
422
+ status: WorkflowStatus.FAILED,
423
+ completedAt: new Date(),
424
+ error: error instanceof Error ? error.message : String(error),
425
+ });
426
+ }
427
+ const workflowResult = this.buildWorkflowResult(workflow, context, stepResults, startedAt, error instanceof Error ? error.message : String(error));
428
+ this.events.onWorkflowComplete?.(workflow, workflowResult);
429
+ return workflowResult;
430
+ }
431
+ const workflowResult = this.buildWorkflowResult(workflow, context, stepResults, startedAt);
432
+ if (this.stateStore) {
433
+ this.stateStore.updateExecution(context.runId, {
434
+ status: context.status,
435
+ completedAt: new Date(),
436
+ outputs: context.variables,
437
+ });
438
+ }
439
+ this.events.onWorkflowComplete?.(workflow, workflowResult);
440
+ return workflowResult;
441
+ }
442
+ /**
443
+ * Execute a workflow from a file.
444
+ * This method automatically sets the workflow path for resolving sub-workflows.
445
+ */
446
+ async executeFile(workflowPath, inputs = {}, sdkRegistry, stepExecutor) {
447
+ // Parse the workflow file
448
+ const { workflow } = await parseFile(workflowPath);
449
+ // Set the workflow path for sub-workflow resolution
450
+ this.workflowPath = resolve(workflowPath);
451
+ // Execute the workflow
452
+ return this.execute(workflow, inputs, sdkRegistry, stepExecutor);
453
+ }
454
+ getFailoverHistory() {
455
+ return [...this.failoverEvents];
456
+ }
457
+ /**
458
+ * Execute a sub-workflow.
459
+ */
460
+ async executeSubWorkflow(step, context, sdkRegistry, stepExecutor) {
461
+ if (!isSubWorkflowStep(step)) {
462
+ throw new Error(`Step ${step.id} is not a workflow step`);
463
+ }
464
+ // Resolve the sub-workflow path relative to the parent workflow
465
+ const subWorkflowPath = this.workflowPath
466
+ ? resolve(dirname(this.workflowPath), step.workflow)
467
+ : resolve(step.workflow);
468
+ // Parse the sub-workflow
469
+ const { workflow: subWorkflow } = await parseFile(subWorkflowPath);
470
+ // Resolve inputs for the sub-workflow
471
+ const resolvedInputs = resolveTemplates(step.inputs, context);
472
+ // Create a new engine instance for the sub-workflow with the same configuration
473
+ const subEngineConfig = {
474
+ defaultTimeout: this.config.defaultTimeout,
475
+ maxRetries: this.config.maxRetries,
476
+ retryBaseDelay: this.config.retryBaseDelay,
477
+ retryMaxDelay: this.config.retryMaxDelay,
478
+ failoverConfig: this.failoverConfig,
479
+ healthTracker: this.healthTracker,
480
+ };
481
+ if (this.rollbackRegistry) {
482
+ subEngineConfig.rollbackRegistry = this.rollbackRegistry;
483
+ }
484
+ const subEngine = new WorkflowEngine(subEngineConfig, this.events, this.stateStore);
485
+ // Set the base path for the sub-workflow
486
+ subEngine.workflowPath = subWorkflowPath;
487
+ // Execute the sub-workflow
488
+ const result = await subEngine.execute(subWorkflow, resolvedInputs, sdkRegistry, stepExecutor);
489
+ // Check if sub-workflow failed
490
+ if (result.status === WorkflowStatus.FAILED) {
491
+ throw new Error(result.error || 'Sub-workflow execution failed');
492
+ }
493
+ // Return the sub-workflow output
494
+ return result.output;
495
+ }
496
+ /**
497
+ * Execute a sub-workflow using an AI sub-agent.
498
+ * The agent interprets the workflow and executes it autonomously.
499
+ */
500
+ async executeSubWorkflowWithAgent(step, context, sdkRegistry, stepExecutor) {
501
+ // Resolve the sub-workflow path
502
+ const subWorkflowPath = this.workflowPath
503
+ ? resolve(dirname(this.workflowPath), step.workflow)
504
+ : resolve(step.workflow);
505
+ // Read the workflow file content
506
+ const { readFile } = await import('node:fs/promises');
507
+ const workflowContent = await readFile(subWorkflowPath, 'utf-8');
508
+ // Resolve inputs for the sub-workflow
509
+ const resolvedInputs = resolveTemplates(step.inputs, context);
510
+ // Get subagent configuration
511
+ const subagentConfig = step.subagentConfig || {};
512
+ const model = subagentConfig.model || step.model || this.config.defaultModel;
513
+ const maxTurns = subagentConfig.maxTurns || 10;
514
+ const systemPrompt = subagentConfig.systemPrompt || this.buildDefaultSubagentSystemPrompt();
515
+ const tools = subagentConfig.tools || ['Read', 'Write', 'Bash', 'Glob', 'Grep'];
516
+ // Build the prompt for the agent
517
+ const agentPrompt = this.buildSubagentPrompt(workflowContent, resolvedInputs, tools);
518
+ // Determine the agent action to use
519
+ const agentName = step.agent || this.config.defaultAgent || 'agent';
520
+ const agentAction = `${agentName}.chat.completions`;
521
+ // Build the messages array
522
+ const messages = [
523
+ { role: 'system', content: systemPrompt },
524
+ { role: 'user', content: agentPrompt },
525
+ ];
526
+ // Create a virtual action step to execute via the agent
527
+ const agentStep = {
528
+ id: `${step.id}-subagent`,
529
+ type: 'action',
530
+ action: agentAction,
531
+ inputs: {
532
+ model,
533
+ messages,
534
+ max_tokens: 8192,
535
+ },
536
+ model,
537
+ agent: agentName,
538
+ };
539
+ // Build executor context
540
+ const executorContext = this.buildStepExecutorContext(agentStep);
541
+ // Execute the agent call
542
+ let response;
543
+ let turns = 0;
544
+ let conversationMessages = [...messages];
545
+ let finalOutput = {};
546
+ while (turns < maxTurns) {
547
+ turns++;
548
+ try {
549
+ response = await stepExecutor({ ...agentStep, inputs: { ...agentStep.inputs, messages: conversationMessages } }, context, sdkRegistry, executorContext);
550
+ // Parse the response
551
+ const parsedResponse = this.parseSubagentResponse(response);
552
+ if (parsedResponse.completed) {
553
+ finalOutput = parsedResponse.output || {};
554
+ break;
555
+ }
556
+ // If agent needs to continue, add its response and continue
557
+ if (parsedResponse.message) {
558
+ conversationMessages.push({ role: 'assistant', content: parsedResponse.message });
559
+ // Agent might request a tool call - for now, we'll prompt it to continue
560
+ conversationMessages.push({ role: 'user', content: 'Continue with the workflow execution.' });
561
+ }
562
+ else {
563
+ // No clear continuation, assume completed
564
+ finalOutput = parsedResponse.output || {};
565
+ break;
566
+ }
567
+ }
568
+ catch (error) {
569
+ throw new Error(`Sub-agent execution failed at turn ${turns}: ${error instanceof Error ? error.message : String(error)}`);
570
+ }
571
+ }
572
+ if (turns >= maxTurns) {
573
+ throw new Error(`Sub-agent exceeded maximum turns (${maxTurns})`);
574
+ }
575
+ return finalOutput;
576
+ }
577
+ /**
578
+ * Build the default system prompt for sub-agent execution.
579
+ */
580
+ buildDefaultSubagentSystemPrompt() {
581
+ return `You are an AI agent executing a workflow. Your task is to interpret the workflow definition and execute each step in order.
582
+
583
+ For each step:
584
+ 1. Understand what the step requires
585
+ 2. Execute the action described
586
+ 3. Store any outputs as specified
587
+
588
+ When you complete all steps, respond with a JSON object containing the workflow outputs.
589
+
590
+ Format your final response as:
591
+ \`\`\`json
592
+ {
593
+ "completed": true,
594
+ "output": { /* workflow outputs here */ }
595
+ }
596
+ \`\`\`
597
+
598
+ If you encounter an error, respond with:
599
+ \`\`\`json
600
+ {
601
+ "completed": false,
602
+ "error": "description of the error"
603
+ }
604
+ \`\`\``;
605
+ }
606
+ /**
607
+ * Build the prompt for sub-agent workflow execution.
608
+ */
609
+ buildSubagentPrompt(workflowContent, inputs, tools) {
610
+ return `Execute the following workflow:
611
+
612
+ ## Workflow Definition
613
+ \`\`\`markdown
614
+ ${workflowContent}
615
+ \`\`\`
616
+
617
+ ## Inputs
618
+ \`\`\`json
619
+ ${JSON.stringify(inputs, null, 2)}
620
+ \`\`\`
621
+
622
+ ## Available Tools
623
+ ${tools.join(', ')}
624
+
625
+ Execute the workflow steps in order and return the final outputs as JSON.`;
626
+ }
627
+ /**
628
+ * Parse the sub-agent's response to extract completion status and output.
629
+ */
630
+ parseSubagentResponse(response) {
631
+ // Try to extract content from various response formats
632
+ let content;
633
+ if (typeof response === 'string') {
634
+ content = response;
635
+ }
636
+ else if (response && typeof response === 'object') {
637
+ const resp = response;
638
+ // OpenAI-style response
639
+ if (resp.choices && Array.isArray(resp.choices)) {
640
+ const choice = resp.choices[0];
641
+ if (choice.message && typeof choice.message === 'object') {
642
+ const message = choice.message;
643
+ content = message.content;
644
+ }
645
+ }
646
+ // Anthropic-style response
647
+ else if (resp.content && Array.isArray(resp.content)) {
648
+ const textBlock = resp.content.find((c) => typeof c === 'object' && c !== null && c.type === 'text');
649
+ content = textBlock?.text;
650
+ }
651
+ // Direct content field
652
+ else if (typeof resp.content === 'string') {
653
+ content = resp.content;
654
+ }
655
+ // Direct message field
656
+ else if (typeof resp.message === 'string') {
657
+ content = resp.message;
658
+ }
659
+ }
660
+ if (!content) {
661
+ return { completed: false, message: 'No content in response' };
662
+ }
663
+ // Try to parse JSON from the response
664
+ const jsonMatch = content.match(/```json\n?([\s\S]*?)```/);
665
+ if (jsonMatch) {
666
+ try {
667
+ const parsed = JSON.parse(jsonMatch[1]);
668
+ const output = parsed.output;
669
+ const error = parsed.error;
670
+ return {
671
+ completed: parsed.completed === true,
672
+ ...(output !== undefined ? { output } : {}),
673
+ ...(error !== undefined ? { error } : {}),
674
+ };
675
+ }
676
+ catch {
677
+ // JSON parse failed, treat as message
678
+ }
679
+ }
680
+ // Try to parse raw JSON
681
+ try {
682
+ const parsed = JSON.parse(content);
683
+ if (typeof parsed.completed === 'boolean') {
684
+ const output = parsed.output;
685
+ const error = parsed.error;
686
+ return {
687
+ completed: parsed.completed,
688
+ ...(output !== undefined ? { output } : {}),
689
+ ...(error !== undefined ? { error } : {}),
690
+ };
691
+ }
692
+ }
693
+ catch {
694
+ // Not JSON
695
+ }
696
+ // Return the content as a message
697
+ return { completed: false, message: content };
698
+ }
699
+ /**
700
+ * Build the step executor context with effective model/agent/permissions.
701
+ */
702
+ buildStepExecutorContext(step) {
703
+ // Merge workflow and step permissions
704
+ const effectivePermissions = mergePermissions(this.workflowPermissions, step.permissions);
705
+ // Resolve effective model/agent (step overrides workflow defaults)
706
+ const effectiveModel = step.model || this.config.defaultModel;
707
+ const effectiveAgent = step.agent || this.config.defaultAgent;
708
+ return {
709
+ model: effectiveModel,
710
+ agent: effectiveAgent,
711
+ permissions: effectivePermissions,
712
+ securityPolicy: toSecurityPolicy(effectivePermissions),
713
+ basePath: this.workflowPath,
714
+ };
715
+ }
716
+ /**
717
+ * Load and resolve an external prompt file for a step.
718
+ */
719
+ async loadAndResolvePrompt(step, context) {
720
+ if (!step.prompt) {
721
+ return step.inputs;
722
+ }
723
+ // Check cache
724
+ let loadedPrompt = this.promptCache.get(step.prompt);
725
+ if (!loadedPrompt) {
726
+ loadedPrompt = await loadPromptFile(step.prompt, this.workflowPath);
727
+ this.promptCache.set(step.prompt, loadedPrompt);
728
+ }
729
+ // Resolve prompt inputs (from step.promptInputs, with template resolution)
730
+ const promptInputs = step.promptInputs
731
+ ? resolveTemplates(step.promptInputs, context)
732
+ : {};
733
+ // Validate prompt inputs
734
+ const validation = validatePromptInputs(loadedPrompt, promptInputs);
735
+ if (!validation.valid) {
736
+ throw new Error(`Invalid prompt inputs: ${validation.errors.join(', ')}`);
737
+ }
738
+ // Resolve the prompt template
739
+ const resolved = resolvePromptTemplate(loadedPrompt, promptInputs, context);
740
+ // Merge resolved prompt content into inputs
741
+ // The resolved content typically goes into a 'messages' or 'prompt' field
742
+ const resolvedInputs = { ...step.inputs };
743
+ // If inputs has a 'messages' array with a user message, inject prompt content
744
+ if (Array.isArray(resolvedInputs.messages)) {
745
+ resolvedInputs.messages = resolvedInputs.messages.map((msg) => {
746
+ if (typeof msg === 'object' && msg !== null) {
747
+ const message = msg;
748
+ if (message.role === 'user' && typeof message.content === 'string') {
749
+ // Replace {{ prompt }} placeholder with resolved content
750
+ return {
751
+ ...message,
752
+ content: message.content.replace(/\{\{\s*prompt\s*\}\}/g, resolved.content),
753
+ };
754
+ }
755
+ }
756
+ return msg;
757
+ });
758
+ }
759
+ else {
760
+ // Add resolved prompt as 'promptContent' for the executor to use
761
+ resolvedInputs.promptContent = resolved.content;
762
+ }
763
+ return resolvedInputs;
764
+ }
765
+ /**
766
+ * Execute a step with retry logic.
767
+ */
768
+ async executeStepWithRetry(step, context, sdkRegistry, stepExecutor) {
769
+ const startedAt = new Date();
770
+ let lastError;
771
+ // Build executor context with model/agent/permissions
772
+ const executorContext = this.buildStepExecutorContext(step);
773
+ // Handle sub-workflow execution
774
+ if (isSubWorkflowStep(step)) {
775
+ // Check if we should use subagent execution
776
+ if (step.useSubagent) {
777
+ try {
778
+ this.events.onStepStart?.(step, context);
779
+ const output = await this.executeWithTimeout(() => this.executeSubWorkflowWithAgent(step, context, sdkRegistry, stepExecutor), step.timeout ?? this.config.defaultTimeout);
780
+ const result = createStepResult(step.id, StepStatus.COMPLETED, output, startedAt, 0);
781
+ this.events.onStepComplete?.(step, result);
782
+ return result;
783
+ }
784
+ catch (error) {
785
+ lastError = error instanceof Error ? error : new Error(String(error));
786
+ const result = createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, lastError);
787
+ this.events.onStepComplete?.(step, result);
788
+ return result;
789
+ }
790
+ }
791
+ // Standard sub-workflow execution
792
+ try {
793
+ this.events.onStepStart?.(step, context);
794
+ const output = await this.executeWithTimeout(() => this.executeSubWorkflow(step, context, sdkRegistry, stepExecutor), step.timeout ?? this.config.defaultTimeout);
795
+ const result = createStepResult(step.id, StepStatus.COMPLETED, output, startedAt, 0);
796
+ this.events.onStepComplete?.(step, result);
797
+ return result;
798
+ }
799
+ catch (error) {
800
+ lastError = error instanceof Error ? error : new Error(String(error));
801
+ const result = createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, lastError // Pass full error object
802
+ );
803
+ this.events.onStepComplete?.(step, result);
804
+ return result;
805
+ }
806
+ }
807
+ // Regular action step - ensure action is defined
808
+ if (!isActionStep(step)) {
809
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Step is neither an action nor a workflow');
810
+ }
811
+ const maxRetries = step.errorHandling?.maxRetries ?? this.config.maxRetries;
812
+ // Get or create circuit breaker for this step's action
813
+ const [serviceName] = step.action.split('.');
814
+ let circuitBreaker = this.circuitBreakers.get(serviceName);
815
+ if (!circuitBreaker) {
816
+ circuitBreaker = new CircuitBreaker();
817
+ this.circuitBreakers.set(serviceName, circuitBreaker);
818
+ }
819
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
820
+ // Check circuit breaker
821
+ if (!circuitBreaker.canExecute()) {
822
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, attempt, `Circuit breaker open for service: ${serviceName}`);
823
+ }
824
+ this.events.onStepStart?.(step, context);
825
+ try {
826
+ // Load and resolve external prompt if specified
827
+ let resolvedInputs;
828
+ if (step.prompt) {
829
+ resolvedInputs = await this.loadAndResolvePrompt(step, context);
830
+ resolvedInputs = resolveTemplates(resolvedInputs, context);
831
+ }
832
+ else {
833
+ // Resolve templates in inputs
834
+ resolvedInputs = resolveTemplates(step.inputs, context);
835
+ }
836
+ const stepWithResolvedInputs = { ...step, inputs: resolvedInputs };
837
+ // Execute step with executor context
838
+ const output = await this.executeWithTimeout(() => stepExecutor(stepWithResolvedInputs, context, sdkRegistry, executorContext), step.timeout ?? this.config.defaultTimeout);
839
+ circuitBreaker.recordSuccess();
840
+ const result = createStepResult(step.id, StepStatus.COMPLETED, output, startedAt, attempt);
841
+ this.events.onStepComplete?.(step, result);
842
+ return result;
843
+ }
844
+ catch (error) {
845
+ lastError = error instanceof Error ? error : new Error(String(error));
846
+ circuitBreaker.recordFailure();
847
+ this.events.onStepError?.(step, lastError, attempt);
848
+ // Wait before retry (unless last attempt)
849
+ if (attempt < maxRetries) {
850
+ const delay = this.retryPolicy.getDelay(attempt);
851
+ await sleep(delay);
852
+ }
853
+ }
854
+ }
855
+ // All retries exhausted
856
+ const result = createStepResult(step.id, StepStatus.FAILED, null, startedAt, maxRetries, lastError // Pass full error object to preserve HTTP details, stack traces, etc.
857
+ );
858
+ this.events.onStepComplete?.(step, result);
859
+ return result;
860
+ }
861
+ /**
862
+ * Execute a step with retry + failover support.
863
+ */
864
+ async executeStepWithFailover(step, context, sdkRegistry, stepExecutor) {
865
+ const primaryResult = await this.executeStepWithRetry(step, context, sdkRegistry, stepExecutor);
866
+ // Sub-workflows and non-action steps don't support failover
867
+ if (!isActionStep(step)) {
868
+ return primaryResult;
869
+ }
870
+ const [primaryTool, ...methodParts] = step.action.split('.');
871
+ const method = methodParts.join('.');
872
+ if (primaryResult.status === StepStatus.COMPLETED) {
873
+ this.healthTracker.markHealthy(primaryTool);
874
+ return primaryResult;
875
+ }
876
+ const errorMessage = primaryResult.error ? errorToString(primaryResult.error) : '';
877
+ const isTimeout = errorMessage.includes('timed out');
878
+ if (isTimeout && !this.failoverConfig.failoverOnTimeout) {
879
+ this.healthTracker.markUnhealthy(primaryTool, errorMessage);
880
+ return primaryResult;
881
+ }
882
+ if (!isTimeout && !this.failoverConfig.failoverOnStepFailure) {
883
+ this.healthTracker.markUnhealthy(primaryTool, errorMessage);
884
+ return primaryResult;
885
+ }
886
+ if (!method || this.failoverConfig.fallbackAgents.length === 0) {
887
+ this.healthTracker.markUnhealthy(primaryTool, errorMessage);
888
+ return primaryResult;
889
+ }
890
+ let attempts = 0;
891
+ for (const fallbackTool of this.failoverConfig.fallbackAgents) {
892
+ if (fallbackTool === primaryTool)
893
+ continue;
894
+ if (attempts >= this.failoverConfig.maxFailoverAttempts)
895
+ break;
896
+ const fallbackStep = { ...step, action: `${fallbackTool}.${method}`, type: 'action' };
897
+ const result = await this.executeStepWithRetry(fallbackStep, context, sdkRegistry, stepExecutor);
898
+ this.failoverEvents.push({
899
+ timestamp: new Date(),
900
+ fromAgent: primaryTool,
901
+ toAgent: fallbackTool,
902
+ reason: isTimeout ? FailoverReason.TIMEOUT : FailoverReason.STEP_EXECUTION_FAILED,
903
+ stepIndex: context.currentStepIndex,
904
+ error: errorMessage || undefined,
905
+ });
906
+ attempts += 1;
907
+ if (result.status === StepStatus.COMPLETED) {
908
+ this.healthTracker.markHealthy(fallbackTool);
909
+ return result;
910
+ }
911
+ }
912
+ this.healthTracker.markUnhealthy(primaryTool, errorMessage);
913
+ return primaryResult;
914
+ }
915
+ /**
916
+ * Execute a function with a timeout.
917
+ */
918
+ async executeWithTimeout(fn, timeoutMs) {
919
+ return Promise.race([
920
+ fn(),
921
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Step timed out after ${timeoutMs}ms`)), timeoutMs)),
922
+ ]);
923
+ }
924
+ /**
925
+ * Evaluate step conditions.
926
+ */
927
+ evaluateConditions(conditions, context) {
928
+ for (const condition of conditions) {
929
+ if (!this.evaluateCondition(condition, context)) {
930
+ return false;
931
+ }
932
+ }
933
+ return true;
934
+ }
935
+ /**
936
+ * Evaluate a single condition.
937
+ * Supports: ==, !=, >, <, >=, <=
938
+ * Also supports nested property access (e.g., check_result.success)
939
+ * and step status checks (e.g., step_id.status == 'failed')
940
+ */
941
+ evaluateCondition(condition, context) {
942
+ // Simple expression parsing
943
+ const operators = ['==', '!=', '>=', '<=', '>', '<'];
944
+ let operator;
945
+ let parts = [];
946
+ for (const op of operators) {
947
+ if (condition.includes(op)) {
948
+ operator = op;
949
+ parts = condition.split(op).map((s) => s.trim());
950
+ break;
951
+ }
952
+ }
953
+ if (!operator || parts.length !== 2) {
954
+ // Treat as boolean variable reference with nested property support
955
+ const value = this.resolveConditionValue(condition, context);
956
+ return Boolean(value);
957
+ }
958
+ const left = this.resolveConditionValue(parts[0], context);
959
+ const right = this.parseValue(parts[1]);
960
+ switch (operator) {
961
+ case '==':
962
+ return left == right;
963
+ case '!=':
964
+ return left != right;
965
+ case '>':
966
+ return Number(left) > Number(right);
967
+ case '<':
968
+ return Number(left) < Number(right);
969
+ case '>=':
970
+ return Number(left) >= Number(right);
971
+ case '<=':
972
+ return Number(left) <= Number(right);
973
+ default:
974
+ return false;
975
+ }
976
+ }
977
+ /**
978
+ * Resolve a condition value with support for nested properties.
979
+ * Handles direct variable references and nested paths.
980
+ */
981
+ resolveConditionValue(path, context) {
982
+ // First try to parse as a literal value (true, false, numbers, etc.)
983
+ const parsedValue = this.parseValue(path);
984
+ // If parseValue returned the same string, try to resolve as a variable
985
+ if (parsedValue === path) {
986
+ const resolved = resolveVariablePath(path, context);
987
+ return resolved;
988
+ }
989
+ // Return the parsed literal value
990
+ return parsedValue;
991
+ }
992
+ /**
993
+ * Parse a value from a condition string.
994
+ */
995
+ parseValue(value) {
996
+ // Remove quotes
997
+ if ((value.startsWith('"') && value.endsWith('"')) ||
998
+ (value.startsWith("'") && value.endsWith("'"))) {
999
+ return value.slice(1, -1);
1000
+ }
1001
+ // Numbers
1002
+ if (!isNaN(Number(value))) {
1003
+ return Number(value);
1004
+ }
1005
+ // Booleans
1006
+ if (value === 'true')
1007
+ return true;
1008
+ if (value === 'false')
1009
+ return false;
1010
+ if (value === 'null')
1011
+ return null;
1012
+ return value;
1013
+ }
1014
+ /**
1015
+ * Build the final workflow result.
1016
+ */
1017
+ buildWorkflowResult(workflow, context, stepResults, startedAt, error) {
1018
+ const completedAt = new Date();
1019
+ // Use workflowOutputs if set by workflow.set_outputs, otherwise use all variables
1020
+ const output = context.workflowOutputs || context.variables;
1021
+ return {
1022
+ workflowId: workflow.metadata.id,
1023
+ runId: context.runId,
1024
+ status: context.status,
1025
+ stepResults,
1026
+ output,
1027
+ error,
1028
+ startedAt,
1029
+ completedAt,
1030
+ duration: completedAt.getTime() - startedAt.getTime(),
1031
+ };
1032
+ }
1033
+ /**
1034
+ * Reset all circuit breakers.
1035
+ */
1036
+ resetCircuitBreakers() {
1037
+ for (const breaker of this.circuitBreakers.values()) {
1038
+ breaker.reset();
1039
+ }
1040
+ }
1041
+ // ============================================================================
1042
+ // Control Flow Execution Methods
1043
+ // ============================================================================
1044
+ /**
1045
+ * Execute an if/else conditional step.
1046
+ */
1047
+ async executeIfStep(step, context, sdkRegistry, stepExecutor) {
1048
+ const startedAt = new Date();
1049
+ try {
1050
+ // Evaluate condition
1051
+ const conditionResult = this.evaluateCondition(step.condition, context);
1052
+ // Determine which branch to execute
1053
+ const branchSteps = conditionResult
1054
+ ? step.then || step.steps // 'steps' is alias for 'then'
1055
+ : step.else;
1056
+ if (!branchSteps || branchSteps.length === 0) {
1057
+ return createStepResult(step.id, StepStatus.SKIPPED, null, startedAt);
1058
+ }
1059
+ // Execute the branch steps
1060
+ const branchResults = [];
1061
+ for (const branchStep of branchSteps) {
1062
+ const result = await this.executeStep(branchStep, context, sdkRegistry, stepExecutor);
1063
+ if (result.status === StepStatus.COMPLETED && branchStep.outputVariable) {
1064
+ context.variables[branchStep.outputVariable] = result.output;
1065
+ branchResults.push(result.output);
1066
+ }
1067
+ if (result.status === StepStatus.FAILED) {
1068
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
1069
+ }
1070
+ }
1071
+ return createStepResult(step.id, StepStatus.COMPLETED, branchResults, startedAt);
1072
+ }
1073
+ catch (error) {
1074
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1075
+ }
1076
+ }
1077
+ /**
1078
+ * Execute a switch/case step.
1079
+ */
1080
+ async executeSwitchStep(step, context, sdkRegistry, stepExecutor) {
1081
+ const startedAt = new Date();
1082
+ try {
1083
+ // Resolve the switch expression
1084
+ const expressionValue = String(resolveTemplates(step.expression, context));
1085
+ // Find matching case
1086
+ const caseSteps = step.cases[expressionValue] || step.default;
1087
+ if (!caseSteps || caseSteps.length === 0) {
1088
+ return createStepResult(step.id, StepStatus.SKIPPED, null, startedAt);
1089
+ }
1090
+ // Execute case steps
1091
+ const caseResults = [];
1092
+ for (const caseStep of caseSteps) {
1093
+ const result = await this.executeStep(caseStep, context, sdkRegistry, stepExecutor);
1094
+ if (result.status === StepStatus.COMPLETED && caseStep.outputVariable) {
1095
+ context.variables[caseStep.outputVariable] = result.output;
1096
+ caseResults.push(result.output);
1097
+ }
1098
+ if (result.status === StepStatus.FAILED) {
1099
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
1100
+ }
1101
+ }
1102
+ return createStepResult(step.id, StepStatus.COMPLETED, caseResults, startedAt);
1103
+ }
1104
+ catch (error) {
1105
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1106
+ }
1107
+ }
1108
+ /**
1109
+ * Execute a for-each loop step.
1110
+ */
1111
+ async executeForEachStep(step, context, sdkRegistry, stepExecutor) {
1112
+ const startedAt = new Date();
1113
+ try {
1114
+ // Resolve items array
1115
+ const items = resolveTemplates(step.items, context);
1116
+ if (!Array.isArray(items)) {
1117
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Items must be an array');
1118
+ }
1119
+ if (items.length === 0) {
1120
+ return createStepResult(step.id, StepStatus.SKIPPED, [], startedAt);
1121
+ }
1122
+ // Execute steps for each item
1123
+ const results = [];
1124
+ for (let i = 0; i < items.length; i++) {
1125
+ // Inject loop variables
1126
+ context.variables[step.itemVariable] = items[i];
1127
+ context.variables['loop'] = {
1128
+ index: i,
1129
+ first: i === 0,
1130
+ last: i === items.length - 1,
1131
+ length: items.length,
1132
+ };
1133
+ if (step.indexVariable) {
1134
+ context.variables[step.indexVariable] = i;
1135
+ }
1136
+ // Execute iteration steps
1137
+ for (const iterStep of step.steps) {
1138
+ const result = await this.executeStep(iterStep, context, sdkRegistry, stepExecutor);
1139
+ if (result.status === StepStatus.COMPLETED && iterStep.outputVariable) {
1140
+ context.variables[iterStep.outputVariable] = result.output;
1141
+ }
1142
+ if (result.status === StepStatus.FAILED) {
1143
+ const errorAction = step.errorHandling?.action ?? 'stop';
1144
+ if (errorAction === 'stop') {
1145
+ // Clean up loop variables
1146
+ delete context.variables[step.itemVariable];
1147
+ delete context.variables['loop'];
1148
+ if (step.indexVariable)
1149
+ delete context.variables[step.indexVariable];
1150
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
1151
+ }
1152
+ // 'continue' - skip to next iteration
1153
+ break;
1154
+ }
1155
+ }
1156
+ results.push(context.variables[step.itemVariable]);
1157
+ }
1158
+ // Clean up loop variables
1159
+ delete context.variables[step.itemVariable];
1160
+ delete context.variables['loop'];
1161
+ if (step.indexVariable)
1162
+ delete context.variables[step.indexVariable];
1163
+ return createStepResult(step.id, StepStatus.COMPLETED, results, startedAt);
1164
+ }
1165
+ catch (error) {
1166
+ // Clean up loop variables on error
1167
+ delete context.variables[step.itemVariable];
1168
+ delete context.variables['loop'];
1169
+ if (step.indexVariable)
1170
+ delete context.variables[step.indexVariable];
1171
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1172
+ }
1173
+ }
1174
+ /**
1175
+ * Execute a while loop step.
1176
+ */
1177
+ async executeWhileStep(step, context, sdkRegistry, stepExecutor) {
1178
+ const startedAt = new Date();
1179
+ let iterations = 0;
1180
+ try {
1181
+ while (this.evaluateCondition(step.condition, context)) {
1182
+ if (iterations >= step.maxIterations) {
1183
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, `Max iterations (${step.maxIterations}) exceeded`);
1184
+ }
1185
+ // Execute iteration steps
1186
+ for (const iterStep of step.steps) {
1187
+ const result = await this.executeStep(iterStep, context, sdkRegistry, stepExecutor);
1188
+ if (result.status === StepStatus.COMPLETED && iterStep.outputVariable) {
1189
+ context.variables[iterStep.outputVariable] = result.output;
1190
+ }
1191
+ if (result.status === StepStatus.FAILED) {
1192
+ const errorAction = step.errorHandling?.action ?? 'stop';
1193
+ if (errorAction === 'stop') {
1194
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, result.error);
1195
+ }
1196
+ // 'continue' - skip to next iteration
1197
+ break;
1198
+ }
1199
+ }
1200
+ iterations++;
1201
+ }
1202
+ return createStepResult(step.id, StepStatus.COMPLETED, { iterations }, startedAt);
1203
+ }
1204
+ catch (error) {
1205
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1206
+ }
1207
+ }
1208
+ /**
1209
+ * Execute a map transformation step.
1210
+ */
1211
+ async executeMapStep(step, context, _sdkRegistry, _stepExecutor) {
1212
+ const startedAt = new Date();
1213
+ try {
1214
+ // Resolve items array
1215
+ const items = resolveTemplates(step.items, context);
1216
+ if (!Array.isArray(items)) {
1217
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Items must be an array');
1218
+ }
1219
+ // Map each item using the expression
1220
+ const mapped = items.map((item) => {
1221
+ context.variables[step.itemVariable] = item;
1222
+ const result = resolveTemplates(step.expression, context);
1223
+ delete context.variables[step.itemVariable];
1224
+ return result;
1225
+ });
1226
+ return createStepResult(step.id, StepStatus.COMPLETED, mapped, startedAt);
1227
+ }
1228
+ catch (error) {
1229
+ delete context.variables[step.itemVariable];
1230
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1231
+ }
1232
+ }
1233
+ /**
1234
+ * Execute a filter step.
1235
+ */
1236
+ async executeFilterStep(step, context, _sdkRegistry, _stepExecutor) {
1237
+ const startedAt = new Date();
1238
+ try {
1239
+ // Resolve items array
1240
+ const items = resolveTemplates(step.items, context);
1241
+ if (!Array.isArray(items)) {
1242
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Items must be an array');
1243
+ }
1244
+ // Filter items using the condition
1245
+ const filtered = items.filter((item) => {
1246
+ context.variables[step.itemVariable] = item;
1247
+ const result = this.evaluateCondition(step.condition, context);
1248
+ delete context.variables[step.itemVariable];
1249
+ return result;
1250
+ });
1251
+ return createStepResult(step.id, StepStatus.COMPLETED, filtered, startedAt);
1252
+ }
1253
+ catch (error) {
1254
+ delete context.variables[step.itemVariable];
1255
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1256
+ }
1257
+ }
1258
+ /**
1259
+ * Execute a reduce/aggregate step.
1260
+ */
1261
+ async executeReduceStep(step, context, _sdkRegistry, _stepExecutor) {
1262
+ const startedAt = new Date();
1263
+ try {
1264
+ // Resolve items array
1265
+ const items = resolveTemplates(step.items, context);
1266
+ if (!Array.isArray(items)) {
1267
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, 'Items must be an array');
1268
+ }
1269
+ // Reduce items using the expression
1270
+ let accumulator = step.initialValue ?? null;
1271
+ for (const item of items) {
1272
+ context.variables[step.itemVariable] = item;
1273
+ context.variables[step.accumulatorVariable] = accumulator;
1274
+ accumulator = resolveTemplates(step.expression, context);
1275
+ delete context.variables[step.itemVariable];
1276
+ delete context.variables[step.accumulatorVariable];
1277
+ }
1278
+ return createStepResult(step.id, StepStatus.COMPLETED, accumulator, startedAt);
1279
+ }
1280
+ catch (error) {
1281
+ delete context.variables[step.itemVariable];
1282
+ delete context.variables[step.accumulatorVariable];
1283
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1284
+ }
1285
+ }
1286
+ /**
1287
+ * Execute parallel branches.
1288
+ */
1289
+ async executeParallelStep(step, context, sdkRegistry, stepExecutor) {
1290
+ const startedAt = new Date();
1291
+ try {
1292
+ // Execute branches in parallel
1293
+ const branchPromises = step.branches.map(async (branch) => {
1294
+ // Clone context for isolation
1295
+ const branchContext = this.cloneContext(context);
1296
+ // Execute branch steps
1297
+ const branchResults = [];
1298
+ for (const branchStep of branch.steps) {
1299
+ const result = await this.executeStep(branchStep, branchContext, sdkRegistry, stepExecutor);
1300
+ if (result.status === StepStatus.COMPLETED && branchStep.outputVariable) {
1301
+ branchContext.variables[branchStep.outputVariable] = result.output;
1302
+ branchResults.push(result.output);
1303
+ }
1304
+ if (result.status === StepStatus.FAILED) {
1305
+ throw new Error(`Branch ${branch.id} failed: ${errorToString(result.error)}`);
1306
+ }
1307
+ }
1308
+ return { branchId: branch.id, context: branchContext, results: branchResults };
1309
+ });
1310
+ // Wait for all branches (or limited concurrency)
1311
+ const branchResults = step.maxConcurrent
1312
+ ? await this.executeConcurrentlyWithLimit(branchPromises, step.maxConcurrent)
1313
+ : await Promise.all(branchPromises);
1314
+ // Merge branch contexts back into main context
1315
+ for (const { branchId, context: branchContext } of branchResults) {
1316
+ this.mergeContexts(context, branchContext, branchId);
1317
+ }
1318
+ const outputs = branchResults.map((br) => br.results);
1319
+ return createStepResult(step.id, StepStatus.COMPLETED, outputs, startedAt);
1320
+ }
1321
+ catch (error) {
1322
+ if (step.onError === 'continue') {
1323
+ return createStepResult(step.id, StepStatus.COMPLETED, null, startedAt);
1324
+ }
1325
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1326
+ }
1327
+ }
1328
+ /**
1329
+ * Execute try/catch/finally step.
1330
+ */
1331
+ async executeTryStep(step, context, sdkRegistry, stepExecutor) {
1332
+ const startedAt = new Date();
1333
+ let tryError;
1334
+ try {
1335
+ // Execute try block
1336
+ for (const tryStep of step.try) {
1337
+ const result = await this.executeStep(tryStep, context, sdkRegistry, stepExecutor);
1338
+ if (result.status === StepStatus.COMPLETED && tryStep.outputVariable) {
1339
+ context.variables[tryStep.outputVariable] = result.output;
1340
+ }
1341
+ if (result.status === StepStatus.FAILED) {
1342
+ tryError = new Error(result.error ? errorToString(result.error) : 'Step failed');
1343
+ break;
1344
+ }
1345
+ }
1346
+ // If error occurred and catch block exists, execute catch
1347
+ let catchError;
1348
+ if (tryError && step.catch) {
1349
+ // Inject error object into context
1350
+ context.variables['error'] = {
1351
+ message: tryError.message,
1352
+ step: tryError,
1353
+ };
1354
+ for (const catchStep of step.catch) {
1355
+ const result = await this.executeStep(catchStep, context, sdkRegistry, stepExecutor);
1356
+ if (result.status === StepStatus.COMPLETED && catchStep.outputVariable) {
1357
+ context.variables[catchStep.outputVariable] = result.output;
1358
+ }
1359
+ if (result.status === StepStatus.FAILED) {
1360
+ catchError = new Error(result.error ? errorToString(result.error) : 'Catch block failed');
1361
+ break;
1362
+ }
1363
+ }
1364
+ delete context.variables['error'];
1365
+ }
1366
+ // Execute finally block (always runs)
1367
+ if (step.finally) {
1368
+ for (const finallyStep of step.finally) {
1369
+ const result = await this.executeStep(finallyStep, context, sdkRegistry, stepExecutor);
1370
+ if (result.status === StepStatus.COMPLETED && finallyStep.outputVariable) {
1371
+ context.variables[finallyStep.outputVariable] = result.output;
1372
+ }
1373
+ }
1374
+ }
1375
+ // Return success if catch handled the error, or error if not
1376
+ if (tryError && !step.catch) {
1377
+ // No catch block to handle error
1378
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, tryError.message);
1379
+ }
1380
+ if (catchError) {
1381
+ // Catch block also failed
1382
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, catchError.message);
1383
+ }
1384
+ return createStepResult(step.id, StepStatus.COMPLETED, null, startedAt);
1385
+ }
1386
+ catch (error) {
1387
+ // Execute finally even on unexpected error
1388
+ if (step.finally) {
1389
+ try {
1390
+ for (const finallyStep of step.finally) {
1391
+ const result = await this.executeStep(finallyStep, context, sdkRegistry, stepExecutor);
1392
+ if (result.status === StepStatus.COMPLETED && finallyStep.outputVariable) {
1393
+ context.variables[finallyStep.outputVariable] = result.output;
1394
+ }
1395
+ }
1396
+ }
1397
+ catch {
1398
+ // Ignore finally errors
1399
+ }
1400
+ }
1401
+ return createStepResult(step.id, StepStatus.FAILED, null, startedAt, 0, error instanceof Error ? error.message : String(error));
1402
+ }
1403
+ }
1404
+ // ============================================================================
1405
+ // Helper Methods for Control Flow
1406
+ // ============================================================================
1407
+ /**
1408
+ * Clone execution context for parallel branches.
1409
+ */
1410
+ cloneContext(context) {
1411
+ return {
1412
+ ...context,
1413
+ variables: { ...context.variables },
1414
+ inputs: { ...context.inputs },
1415
+ stepMetadata: { ...context.stepMetadata },
1416
+ };
1417
+ }
1418
+ /**
1419
+ * Merge branch context back into main context.
1420
+ */
1421
+ mergeContexts(mainContext, branchContext, branchId) {
1422
+ // Merge variables with branch prefix
1423
+ for (const [key, value] of Object.entries(branchContext.variables)) {
1424
+ mainContext.variables[`${branchId}.${key}`] = value;
1425
+ }
1426
+ }
1427
+ /**
1428
+ * Execute promises with concurrency limit.
1429
+ */
1430
+ async executeConcurrentlyWithLimit(promises, limit) {
1431
+ const results = [];
1432
+ const executing = [];
1433
+ for (const promise of promises) {
1434
+ const p = promise.then((result) => {
1435
+ results.push(result);
1436
+ });
1437
+ executing.push(p);
1438
+ if (executing.length >= limit) {
1439
+ await Promise.race(executing);
1440
+ executing.splice(executing.findIndex((x) => x === p), 1);
1441
+ }
1442
+ }
1443
+ await Promise.all(executing);
1444
+ return results;
1445
+ }
1446
+ }
1447
+ // ============================================================================
1448
+ // Helpers
1449
+ // ============================================================================
1450
+ function sleep(ms) {
1451
+ return new Promise((resolve) => setTimeout(resolve, ms));
1452
+ }
1453
+ //# sourceMappingURL=engine.js.map