@marktoflow/core 2.0.0-alpha.3
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/bundle.d.ts +43 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +202 -0
- package/dist/bundle.js.map +1 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +27 -0
- package/dist/config.js.map +1 -0
- package/dist/costs.d.ts +182 -0
- package/dist/costs.d.ts.map +1 -0
- package/dist/costs.js +464 -0
- package/dist/costs.js.map +1 -0
- package/dist/credentials.d.ts +162 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +646 -0
- package/dist/credentials.js.map +1 -0
- package/dist/engine.d.ts +137 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +514 -0
- package/dist/engine.js.map +1 -0
- package/dist/env.d.ts +59 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +256 -0
- package/dist/env.js.map +1 -0
- package/dist/failover.d.ts +43 -0
- package/dist/failover.d.ts.map +1 -0
- package/dist/failover.js +53 -0
- package/dist/failover.js.map +1 -0
- package/dist/filewatcher.d.ts +32 -0
- package/dist/filewatcher.d.ts.map +1 -0
- package/dist/filewatcher.js +92 -0
- package/dist/filewatcher.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +69 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +62 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +211 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp-loader.d.ts +29 -0
- package/dist/mcp-loader.d.ts.map +1 -0
- package/dist/mcp-loader.js +60 -0
- package/dist/mcp-loader.js.map +1 -0
- package/dist/metrics.d.ts +19 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +65 -0
- package/dist/metrics.js.map +1 -0
- package/dist/models.d.ts +419 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +111 -0
- package/dist/models.js.map +1 -0
- package/dist/parser.d.ts +40 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +287 -0
- package/dist/parser.js.map +1 -0
- package/dist/plugins.d.ts +105 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +182 -0
- package/dist/plugins.js.map +1 -0
- package/dist/queue.d.ts +114 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +385 -0
- package/dist/queue.js.map +1 -0
- package/dist/rollback.d.ts +117 -0
- package/dist/rollback.d.ts.map +1 -0
- package/dist/rollback.js +374 -0
- package/dist/rollback.js.map +1 -0
- package/dist/routing.d.ts +144 -0
- package/dist/routing.d.ts.map +1 -0
- package/dist/routing.js +457 -0
- package/dist/routing.js.map +1 -0
- package/dist/scheduler.d.ts +91 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +259 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/script-tool.d.ts +22 -0
- package/dist/script-tool.d.ts.map +1 -0
- package/dist/script-tool.js +90 -0
- package/dist/script-tool.js.map +1 -0
- package/dist/sdk-registry.d.ts +81 -0
- package/dist/sdk-registry.d.ts.map +1 -0
- package/dist/sdk-registry.js +264 -0
- package/dist/sdk-registry.js.map +1 -0
- package/dist/security.d.ts +155 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +362 -0
- package/dist/security.js.map +1 -0
- package/dist/state.d.ts +67 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +276 -0
- package/dist/state.js.map +1 -0
- package/dist/templates.d.ts +70 -0
- package/dist/templates.d.ts.map +1 -0
- package/dist/templates.js +244 -0
- package/dist/templates.js.map +1 -0
- package/dist/tool-base.d.ts +54 -0
- package/dist/tool-base.d.ts.map +1 -0
- package/dist/tool-base.js +43 -0
- package/dist/tool-base.js.map +1 -0
- package/dist/tool-registry.d.ts +24 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +164 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tools/custom-tool.d.ts +16 -0
- package/dist/tools/custom-tool.d.ts.map +1 -0
- package/dist/tools/custom-tool.js +85 -0
- package/dist/tools/custom-tool.js.map +1 -0
- package/dist/tools/mcp-tool.d.ts +16 -0
- package/dist/tools/mcp-tool.d.ts.map +1 -0
- package/dist/tools/mcp-tool.js +98 -0
- package/dist/tools/mcp-tool.js.map +1 -0
- package/dist/tools/openapi-tool.d.ts +17 -0
- package/dist/tools/openapi-tool.d.ts.map +1 -0
- package/dist/tools/openapi-tool.js +165 -0
- package/dist/tools/openapi-tool.js.map +1 -0
- package/dist/trigger-manager.d.ts +26 -0
- package/dist/trigger-manager.d.ts.map +1 -0
- package/dist/trigger-manager.js +107 -0
- package/dist/trigger-manager.js.map +1 -0
- package/dist/webhook.d.ts +95 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +261 -0
- package/dist/webhook.js.map +1 -0
- package/package.json +60 -0
package/dist/engine.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
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 { Workflow, WorkflowStep, ExecutionContext, StepResult, WorkflowResult } from './models.js';
|
|
8
|
+
import { StateStore } from './state.js';
|
|
9
|
+
import { AgentHealthTracker, type FailoverConfig, type FailoverEvent } from './failover.js';
|
|
10
|
+
import { RollbackRegistry } from './rollback.js';
|
|
11
|
+
export interface EngineConfig {
|
|
12
|
+
/** Default timeout for steps in milliseconds */
|
|
13
|
+
defaultTimeout?: number;
|
|
14
|
+
/** Maximum retries for failed steps */
|
|
15
|
+
maxRetries?: number;
|
|
16
|
+
/** Base delay for retry backoff in milliseconds */
|
|
17
|
+
retryBaseDelay?: number;
|
|
18
|
+
/** Maximum delay for retry backoff in milliseconds */
|
|
19
|
+
retryMaxDelay?: number;
|
|
20
|
+
/** Optional rollback registry for rollback error handling */
|
|
21
|
+
rollbackRegistry?: RollbackRegistry;
|
|
22
|
+
/** Failover configuration for step execution */
|
|
23
|
+
failoverConfig?: Partial<FailoverConfig>;
|
|
24
|
+
/** Optional agent health tracker */
|
|
25
|
+
healthTracker?: AgentHealthTracker;
|
|
26
|
+
}
|
|
27
|
+
export interface SDKRegistryLike {
|
|
28
|
+
/** Load an SDK by name */
|
|
29
|
+
load(sdkName: string): Promise<unknown>;
|
|
30
|
+
/** Check if SDK is available */
|
|
31
|
+
has(sdkName: string): boolean;
|
|
32
|
+
}
|
|
33
|
+
export type StepExecutor = (step: WorkflowStep, context: ExecutionContext, sdkRegistry: SDKRegistryLike) => Promise<unknown>;
|
|
34
|
+
export interface EngineEvents {
|
|
35
|
+
onStepStart?: (step: WorkflowStep, context: ExecutionContext) => void;
|
|
36
|
+
onStepComplete?: (step: WorkflowStep, result: StepResult) => void;
|
|
37
|
+
onStepError?: (step: WorkflowStep, error: Error, retryCount: number) => void;
|
|
38
|
+
onWorkflowStart?: (workflow: Workflow, context: ExecutionContext) => void;
|
|
39
|
+
onWorkflowComplete?: (workflow: Workflow, result: WorkflowResult) => void;
|
|
40
|
+
}
|
|
41
|
+
export declare class RetryPolicy {
|
|
42
|
+
readonly maxRetries: number;
|
|
43
|
+
readonly baseDelay: number;
|
|
44
|
+
readonly maxDelay: number;
|
|
45
|
+
readonly exponentialBase: number;
|
|
46
|
+
readonly jitter: number;
|
|
47
|
+
constructor(maxRetries?: number, baseDelay?: number, maxDelay?: number, exponentialBase?: number, jitter?: number);
|
|
48
|
+
/**
|
|
49
|
+
* Calculate delay for a given retry attempt.
|
|
50
|
+
*/
|
|
51
|
+
getDelay(attempt: number): number;
|
|
52
|
+
}
|
|
53
|
+
export type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
|
|
54
|
+
export declare class CircuitBreaker {
|
|
55
|
+
readonly failureThreshold: number;
|
|
56
|
+
readonly recoveryTimeout: number;
|
|
57
|
+
readonly halfOpenMaxCalls: number;
|
|
58
|
+
private state;
|
|
59
|
+
private failures;
|
|
60
|
+
private lastFailureTime;
|
|
61
|
+
private halfOpenCalls;
|
|
62
|
+
constructor(failureThreshold?: number, recoveryTimeout?: number, halfOpenMaxCalls?: number);
|
|
63
|
+
canExecute(): boolean;
|
|
64
|
+
recordSuccess(): void;
|
|
65
|
+
recordFailure(): void;
|
|
66
|
+
getState(): CircuitState;
|
|
67
|
+
reset(): void;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve template variables in a value.
|
|
71
|
+
* Supports {{variable}} and {{inputs.name}} syntax.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveTemplates(value: unknown, context: ExecutionContext): unknown;
|
|
74
|
+
/**
|
|
75
|
+
* Resolve a variable path from context.
|
|
76
|
+
* First checks inputs.*, then variables, then stepMetadata, then direct context properties.
|
|
77
|
+
* Exported to allow access from condition evaluation.
|
|
78
|
+
*/
|
|
79
|
+
export declare function resolveVariablePath(path: string, context: ExecutionContext): unknown;
|
|
80
|
+
export declare class WorkflowEngine {
|
|
81
|
+
private config;
|
|
82
|
+
private retryPolicy;
|
|
83
|
+
private circuitBreakers;
|
|
84
|
+
private events;
|
|
85
|
+
private stateStore?;
|
|
86
|
+
private rollbackRegistry?;
|
|
87
|
+
private failoverConfig;
|
|
88
|
+
private healthTracker;
|
|
89
|
+
private failoverEvents;
|
|
90
|
+
constructor(config?: EngineConfig, events?: EngineEvents, stateStore?: StateStore);
|
|
91
|
+
/**
|
|
92
|
+
* Execute a workflow.
|
|
93
|
+
*/
|
|
94
|
+
execute(workflow: Workflow, inputs: Record<string, unknown> | undefined, sdkRegistry: SDKRegistryLike, stepExecutor: StepExecutor): Promise<WorkflowResult>;
|
|
95
|
+
getFailoverHistory(): FailoverEvent[];
|
|
96
|
+
/**
|
|
97
|
+
* Execute a step with retry logic.
|
|
98
|
+
*/
|
|
99
|
+
private executeStepWithRetry;
|
|
100
|
+
/**
|
|
101
|
+
* Execute a step with retry + failover support.
|
|
102
|
+
*/
|
|
103
|
+
private executeStepWithFailover;
|
|
104
|
+
/**
|
|
105
|
+
* Execute a function with a timeout.
|
|
106
|
+
*/
|
|
107
|
+
private executeWithTimeout;
|
|
108
|
+
/**
|
|
109
|
+
* Evaluate step conditions.
|
|
110
|
+
*/
|
|
111
|
+
private evaluateConditions;
|
|
112
|
+
/**
|
|
113
|
+
* Evaluate a single condition.
|
|
114
|
+
* Supports: ==, !=, >, <, >=, <=
|
|
115
|
+
* Also supports nested property access (e.g., check_result.success)
|
|
116
|
+
* and step status checks (e.g., step_id.status == 'failed')
|
|
117
|
+
*/
|
|
118
|
+
private evaluateCondition;
|
|
119
|
+
/**
|
|
120
|
+
* Resolve a condition value with support for nested properties.
|
|
121
|
+
* Handles direct variable references and nested paths.
|
|
122
|
+
*/
|
|
123
|
+
private resolveConditionValue;
|
|
124
|
+
/**
|
|
125
|
+
* Parse a value from a condition string.
|
|
126
|
+
*/
|
|
127
|
+
private parseValue;
|
|
128
|
+
/**
|
|
129
|
+
* Build the final workflow result.
|
|
130
|
+
*/
|
|
131
|
+
private buildWorkflowResult;
|
|
132
|
+
/**
|
|
133
|
+
* Reset all circuit breakers.
|
|
134
|
+
*/
|
|
135
|
+
resetCircuitBreakers(): void;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/engine.js
ADDED
|
@@ -0,0 +1,514 @@
|
|
|
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, } from './models.js';
|
|
8
|
+
import { DEFAULT_FAILOVER_CONFIG, AgentHealthTracker, FailoverReason, } from './failover.js';
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Retry Policy
|
|
11
|
+
// ============================================================================
|
|
12
|
+
export class RetryPolicy {
|
|
13
|
+
maxRetries;
|
|
14
|
+
baseDelay;
|
|
15
|
+
maxDelay;
|
|
16
|
+
exponentialBase;
|
|
17
|
+
jitter;
|
|
18
|
+
constructor(maxRetries = 3, baseDelay = 1000, maxDelay = 30000, exponentialBase = 2, jitter = 0.1) {
|
|
19
|
+
this.maxRetries = maxRetries;
|
|
20
|
+
this.baseDelay = baseDelay;
|
|
21
|
+
this.maxDelay = maxDelay;
|
|
22
|
+
this.exponentialBase = exponentialBase;
|
|
23
|
+
this.jitter = jitter;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Calculate delay for a given retry attempt.
|
|
27
|
+
*/
|
|
28
|
+
getDelay(attempt) {
|
|
29
|
+
const exponentialDelay = this.baseDelay * Math.pow(this.exponentialBase, attempt);
|
|
30
|
+
const clampedDelay = Math.min(exponentialDelay, this.maxDelay);
|
|
31
|
+
// Add jitter
|
|
32
|
+
const jitterAmount = clampedDelay * this.jitter * (Math.random() * 2 - 1);
|
|
33
|
+
return Math.max(0, clampedDelay + jitterAmount);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export class CircuitBreaker {
|
|
37
|
+
failureThreshold;
|
|
38
|
+
recoveryTimeout;
|
|
39
|
+
halfOpenMaxCalls;
|
|
40
|
+
state = 'CLOSED';
|
|
41
|
+
failures = 0;
|
|
42
|
+
lastFailureTime = 0;
|
|
43
|
+
halfOpenCalls = 0;
|
|
44
|
+
constructor(failureThreshold = 5, recoveryTimeout = 30000, halfOpenMaxCalls = 3) {
|
|
45
|
+
this.failureThreshold = failureThreshold;
|
|
46
|
+
this.recoveryTimeout = recoveryTimeout;
|
|
47
|
+
this.halfOpenMaxCalls = halfOpenMaxCalls;
|
|
48
|
+
}
|
|
49
|
+
canExecute() {
|
|
50
|
+
if (this.state === 'CLOSED') {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (this.state === 'OPEN') {
|
|
54
|
+
const timeSinceFailure = Date.now() - this.lastFailureTime;
|
|
55
|
+
if (timeSinceFailure >= this.recoveryTimeout) {
|
|
56
|
+
this.state = 'HALF_OPEN';
|
|
57
|
+
this.halfOpenCalls = 0;
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
// HALF_OPEN
|
|
63
|
+
return this.halfOpenCalls < this.halfOpenMaxCalls;
|
|
64
|
+
}
|
|
65
|
+
recordSuccess() {
|
|
66
|
+
this.failures = 0;
|
|
67
|
+
this.state = 'CLOSED';
|
|
68
|
+
}
|
|
69
|
+
recordFailure() {
|
|
70
|
+
this.failures++;
|
|
71
|
+
this.lastFailureTime = Date.now();
|
|
72
|
+
if (this.state === 'HALF_OPEN') {
|
|
73
|
+
this.state = 'OPEN';
|
|
74
|
+
}
|
|
75
|
+
else if (this.failures >= this.failureThreshold) {
|
|
76
|
+
this.state = 'OPEN';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
getState() {
|
|
80
|
+
return this.state;
|
|
81
|
+
}
|
|
82
|
+
reset() {
|
|
83
|
+
this.state = 'CLOSED';
|
|
84
|
+
this.failures = 0;
|
|
85
|
+
this.halfOpenCalls = 0;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Variable Resolution
|
|
90
|
+
// ============================================================================
|
|
91
|
+
/**
|
|
92
|
+
* Resolve template variables in a value.
|
|
93
|
+
* Supports {{variable}} and {{inputs.name}} syntax.
|
|
94
|
+
*/
|
|
95
|
+
export function resolveTemplates(value, context) {
|
|
96
|
+
if (typeof value === 'string') {
|
|
97
|
+
return value.replace(/\{\{([^}]+)\}\}/g, (_, varPath) => {
|
|
98
|
+
const path = varPath.trim();
|
|
99
|
+
const resolved = resolveVariablePath(path, context);
|
|
100
|
+
return resolved !== undefined ? String(resolved) : '';
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (Array.isArray(value)) {
|
|
104
|
+
return value.map((v) => resolveTemplates(v, context));
|
|
105
|
+
}
|
|
106
|
+
if (value && typeof value === 'object') {
|
|
107
|
+
const result = {};
|
|
108
|
+
for (const [k, v] of Object.entries(value)) {
|
|
109
|
+
result[k] = resolveTemplates(v, context);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a variable path from context.
|
|
117
|
+
* First checks inputs.*, then variables, then stepMetadata, then direct context properties.
|
|
118
|
+
* Exported to allow access from condition evaluation.
|
|
119
|
+
*/
|
|
120
|
+
export function resolveVariablePath(path, context) {
|
|
121
|
+
// Handle inputs.* prefix
|
|
122
|
+
if (path.startsWith('inputs.')) {
|
|
123
|
+
const inputPath = path.slice(7); // Remove 'inputs.'
|
|
124
|
+
return getNestedValue(context.inputs, inputPath);
|
|
125
|
+
}
|
|
126
|
+
// Check variables first (most common case)
|
|
127
|
+
const fromVars = getNestedValue(context.variables, path);
|
|
128
|
+
if (fromVars !== undefined) {
|
|
129
|
+
return fromVars;
|
|
130
|
+
}
|
|
131
|
+
// Check step metadata (for status checks like: step_id.status)
|
|
132
|
+
const fromStepMeta = getNestedValue(context.stepMetadata, path);
|
|
133
|
+
if (fromStepMeta !== undefined) {
|
|
134
|
+
return fromStepMeta;
|
|
135
|
+
}
|
|
136
|
+
// Fall back to direct context access
|
|
137
|
+
return getNestedValue(context, path);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get a nested value from an object using dot notation.
|
|
141
|
+
*/
|
|
142
|
+
function getNestedValue(obj, path) {
|
|
143
|
+
if (obj === null || obj === undefined) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
const parts = path.split('.');
|
|
147
|
+
let current = obj;
|
|
148
|
+
for (const part of parts) {
|
|
149
|
+
if (current === null || current === undefined) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
if (typeof current === 'object') {
|
|
153
|
+
current = current[part];
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return current;
|
|
160
|
+
}
|
|
161
|
+
export class WorkflowEngine {
|
|
162
|
+
config;
|
|
163
|
+
retryPolicy;
|
|
164
|
+
circuitBreakers = new Map();
|
|
165
|
+
events;
|
|
166
|
+
stateStore;
|
|
167
|
+
rollbackRegistry;
|
|
168
|
+
failoverConfig;
|
|
169
|
+
healthTracker;
|
|
170
|
+
failoverEvents = [];
|
|
171
|
+
constructor(config = {}, events = {}, stateStore) {
|
|
172
|
+
this.config = {
|
|
173
|
+
defaultTimeout: config.defaultTimeout ?? 60000,
|
|
174
|
+
maxRetries: config.maxRetries ?? 3,
|
|
175
|
+
retryBaseDelay: config.retryBaseDelay ?? 1000,
|
|
176
|
+
retryMaxDelay: config.retryMaxDelay ?? 30000,
|
|
177
|
+
};
|
|
178
|
+
this.retryPolicy = new RetryPolicy(this.config.maxRetries, this.config.retryBaseDelay, this.config.retryMaxDelay);
|
|
179
|
+
this.events = events;
|
|
180
|
+
this.stateStore = stateStore;
|
|
181
|
+
this.rollbackRegistry = config.rollbackRegistry;
|
|
182
|
+
this.failoverConfig = { ...DEFAULT_FAILOVER_CONFIG, ...(config.failoverConfig ?? {}) };
|
|
183
|
+
this.healthTracker = config.healthTracker ?? new AgentHealthTracker();
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Execute a workflow.
|
|
187
|
+
*/
|
|
188
|
+
async execute(workflow, inputs = {}, sdkRegistry, stepExecutor) {
|
|
189
|
+
const context = createExecutionContext(workflow, inputs);
|
|
190
|
+
const stepResults = [];
|
|
191
|
+
const startedAt = new Date();
|
|
192
|
+
context.status = WorkflowStatus.RUNNING;
|
|
193
|
+
this.events.onWorkflowStart?.(workflow, context);
|
|
194
|
+
if (this.stateStore) {
|
|
195
|
+
this.stateStore.createExecution({
|
|
196
|
+
runId: context.runId,
|
|
197
|
+
workflowId: workflow.metadata.id,
|
|
198
|
+
workflowPath: 'unknown',
|
|
199
|
+
status: WorkflowStatus.RUNNING,
|
|
200
|
+
startedAt: startedAt,
|
|
201
|
+
completedAt: null,
|
|
202
|
+
currentStep: 0,
|
|
203
|
+
totalSteps: workflow.steps.length,
|
|
204
|
+
inputs: inputs,
|
|
205
|
+
outputs: null,
|
|
206
|
+
error: null,
|
|
207
|
+
metadata: null,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
for (let i = 0; i < workflow.steps.length; i++) {
|
|
212
|
+
const step = workflow.steps[i];
|
|
213
|
+
context.currentStepIndex = i;
|
|
214
|
+
// Check conditions
|
|
215
|
+
if (step.conditions && !this.evaluateConditions(step.conditions, context)) {
|
|
216
|
+
const skipResult = createStepResult(step.id, StepStatus.SKIPPED, null, new Date());
|
|
217
|
+
stepResults.push(skipResult);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
// Execute step with retry
|
|
221
|
+
const result = await this.executeStepWithFailover(step, context, sdkRegistry, stepExecutor);
|
|
222
|
+
stepResults.push(result);
|
|
223
|
+
// Store step metadata (status, error, etc.) in separate field for condition evaluation
|
|
224
|
+
// This allows conditions like: step_id.status == 'failed'
|
|
225
|
+
context.stepMetadata[step.id] = {
|
|
226
|
+
status: result.status.toLowerCase(),
|
|
227
|
+
retryCount: result.retryCount,
|
|
228
|
+
...(result.error ? { error: result.error } : {}),
|
|
229
|
+
};
|
|
230
|
+
// Store output variable
|
|
231
|
+
if (step.outputVariable && result.status === StepStatus.COMPLETED) {
|
|
232
|
+
context.variables[step.outputVariable] = result.output;
|
|
233
|
+
}
|
|
234
|
+
// Handle failure
|
|
235
|
+
if (result.status === StepStatus.FAILED) {
|
|
236
|
+
const errorAction = step.errorHandling?.action ?? 'stop';
|
|
237
|
+
if (errorAction === 'stop') {
|
|
238
|
+
context.status = WorkflowStatus.FAILED;
|
|
239
|
+
const workflowError = result.error || `Step ${step.id} failed`;
|
|
240
|
+
const workflowResult = this.buildWorkflowResult(workflow, context, stepResults, startedAt, workflowError);
|
|
241
|
+
this.events.onWorkflowComplete?.(workflow, workflowResult);
|
|
242
|
+
return workflowResult;
|
|
243
|
+
}
|
|
244
|
+
// 'continue' - keep going
|
|
245
|
+
if (errorAction === 'rollback') {
|
|
246
|
+
if (this.rollbackRegistry) {
|
|
247
|
+
await this.rollbackRegistry.rollbackAllAsync({
|
|
248
|
+
context,
|
|
249
|
+
inputs: context.inputs,
|
|
250
|
+
variables: context.variables,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
context.status = WorkflowStatus.FAILED;
|
|
254
|
+
const workflowError = result.error || `Step ${step.id} failed`;
|
|
255
|
+
const workflowResult = this.buildWorkflowResult(workflow, context, stepResults, startedAt, workflowError);
|
|
256
|
+
this.events.onWorkflowComplete?.(workflow, workflowResult);
|
|
257
|
+
return workflowResult;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Determine final status
|
|
262
|
+
context.status = WorkflowStatus.COMPLETED;
|
|
263
|
+
}
|
|
264
|
+
catch (error) {
|
|
265
|
+
context.status = WorkflowStatus.FAILED;
|
|
266
|
+
if (this.stateStore) {
|
|
267
|
+
this.stateStore.updateExecution(context.runId, {
|
|
268
|
+
status: WorkflowStatus.FAILED,
|
|
269
|
+
completedAt: new Date(),
|
|
270
|
+
error: error instanceof Error ? error.message : String(error),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
const workflowResult = this.buildWorkflowResult(workflow, context, stepResults, startedAt, error instanceof Error ? error.message : String(error));
|
|
274
|
+
this.events.onWorkflowComplete?.(workflow, workflowResult);
|
|
275
|
+
return workflowResult;
|
|
276
|
+
}
|
|
277
|
+
const workflowResult = this.buildWorkflowResult(workflow, context, stepResults, startedAt);
|
|
278
|
+
if (this.stateStore) {
|
|
279
|
+
this.stateStore.updateExecution(context.runId, {
|
|
280
|
+
status: context.status,
|
|
281
|
+
completedAt: new Date(),
|
|
282
|
+
outputs: context.variables,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
this.events.onWorkflowComplete?.(workflow, workflowResult);
|
|
286
|
+
return workflowResult;
|
|
287
|
+
}
|
|
288
|
+
getFailoverHistory() {
|
|
289
|
+
return [...this.failoverEvents];
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Execute a step with retry logic.
|
|
293
|
+
*/
|
|
294
|
+
async executeStepWithRetry(step, context, sdkRegistry, stepExecutor) {
|
|
295
|
+
const maxRetries = step.errorHandling?.maxRetries ?? this.config.maxRetries;
|
|
296
|
+
const startedAt = new Date();
|
|
297
|
+
let lastError;
|
|
298
|
+
// Get or create circuit breaker for this step's action
|
|
299
|
+
const [serviceName] = step.action.split('.');
|
|
300
|
+
let circuitBreaker = this.circuitBreakers.get(serviceName);
|
|
301
|
+
if (!circuitBreaker) {
|
|
302
|
+
circuitBreaker = new CircuitBreaker();
|
|
303
|
+
this.circuitBreakers.set(serviceName, circuitBreaker);
|
|
304
|
+
}
|
|
305
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
306
|
+
// Check circuit breaker
|
|
307
|
+
if (!circuitBreaker.canExecute()) {
|
|
308
|
+
return createStepResult(step.id, StepStatus.FAILED, null, startedAt, attempt, `Circuit breaker open for service: ${serviceName}`);
|
|
309
|
+
}
|
|
310
|
+
this.events.onStepStart?.(step, context);
|
|
311
|
+
try {
|
|
312
|
+
// Resolve templates in inputs
|
|
313
|
+
const resolvedInputs = resolveTemplates(step.inputs, context);
|
|
314
|
+
const stepWithResolvedInputs = { ...step, inputs: resolvedInputs };
|
|
315
|
+
// Execute step
|
|
316
|
+
const output = await this.executeWithTimeout(() => stepExecutor(stepWithResolvedInputs, context, sdkRegistry), step.timeout ?? this.config.defaultTimeout);
|
|
317
|
+
circuitBreaker.recordSuccess();
|
|
318
|
+
const result = createStepResult(step.id, StepStatus.COMPLETED, output, startedAt, attempt);
|
|
319
|
+
this.events.onStepComplete?.(step, result);
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
324
|
+
circuitBreaker.recordFailure();
|
|
325
|
+
this.events.onStepError?.(step, lastError, attempt);
|
|
326
|
+
// Wait before retry (unless last attempt)
|
|
327
|
+
if (attempt < maxRetries) {
|
|
328
|
+
const delay = this.retryPolicy.getDelay(attempt);
|
|
329
|
+
await sleep(delay);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// All retries exhausted
|
|
334
|
+
const result = createStepResult(step.id, StepStatus.FAILED, null, startedAt, maxRetries, lastError?.message);
|
|
335
|
+
this.events.onStepComplete?.(step, result);
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Execute a step with retry + failover support.
|
|
340
|
+
*/
|
|
341
|
+
async executeStepWithFailover(step, context, sdkRegistry, stepExecutor) {
|
|
342
|
+
const primaryResult = await this.executeStepWithRetry(step, context, sdkRegistry, stepExecutor);
|
|
343
|
+
const [primaryTool, ...methodParts] = step.action.split('.');
|
|
344
|
+
const method = methodParts.join('.');
|
|
345
|
+
if (primaryResult.status === StepStatus.COMPLETED) {
|
|
346
|
+
this.healthTracker.markHealthy(primaryTool);
|
|
347
|
+
return primaryResult;
|
|
348
|
+
}
|
|
349
|
+
const errorMessage = primaryResult.error ?? '';
|
|
350
|
+
const isTimeout = errorMessage.includes('timed out');
|
|
351
|
+
if (isTimeout && !this.failoverConfig.failoverOnTimeout) {
|
|
352
|
+
this.healthTracker.markUnhealthy(primaryTool, errorMessage);
|
|
353
|
+
return primaryResult;
|
|
354
|
+
}
|
|
355
|
+
if (!isTimeout && !this.failoverConfig.failoverOnStepFailure) {
|
|
356
|
+
this.healthTracker.markUnhealthy(primaryTool, errorMessage);
|
|
357
|
+
return primaryResult;
|
|
358
|
+
}
|
|
359
|
+
if (!method || this.failoverConfig.fallbackAgents.length === 0) {
|
|
360
|
+
this.healthTracker.markUnhealthy(primaryTool, errorMessage);
|
|
361
|
+
return primaryResult;
|
|
362
|
+
}
|
|
363
|
+
let attempts = 0;
|
|
364
|
+
for (const fallbackTool of this.failoverConfig.fallbackAgents) {
|
|
365
|
+
if (fallbackTool === primaryTool)
|
|
366
|
+
continue;
|
|
367
|
+
if (attempts >= this.failoverConfig.maxFailoverAttempts)
|
|
368
|
+
break;
|
|
369
|
+
const fallbackStep = { ...step, action: `${fallbackTool}.${method}` };
|
|
370
|
+
const result = await this.executeStepWithRetry(fallbackStep, context, sdkRegistry, stepExecutor);
|
|
371
|
+
this.failoverEvents.push({
|
|
372
|
+
timestamp: new Date(),
|
|
373
|
+
fromAgent: primaryTool,
|
|
374
|
+
toAgent: fallbackTool,
|
|
375
|
+
reason: isTimeout ? FailoverReason.TIMEOUT : FailoverReason.STEP_EXECUTION_FAILED,
|
|
376
|
+
stepIndex: context.currentStepIndex,
|
|
377
|
+
error: errorMessage || undefined,
|
|
378
|
+
});
|
|
379
|
+
attempts += 1;
|
|
380
|
+
if (result.status === StepStatus.COMPLETED) {
|
|
381
|
+
this.healthTracker.markHealthy(fallbackTool);
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
this.healthTracker.markUnhealthy(primaryTool, errorMessage);
|
|
386
|
+
return primaryResult;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Execute a function with a timeout.
|
|
390
|
+
*/
|
|
391
|
+
async executeWithTimeout(fn, timeoutMs) {
|
|
392
|
+
return Promise.race([
|
|
393
|
+
fn(),
|
|
394
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Step timed out after ${timeoutMs}ms`)), timeoutMs)),
|
|
395
|
+
]);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Evaluate step conditions.
|
|
399
|
+
*/
|
|
400
|
+
evaluateConditions(conditions, context) {
|
|
401
|
+
for (const condition of conditions) {
|
|
402
|
+
if (!this.evaluateCondition(condition, context)) {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Evaluate a single condition.
|
|
410
|
+
* Supports: ==, !=, >, <, >=, <=
|
|
411
|
+
* Also supports nested property access (e.g., check_result.success)
|
|
412
|
+
* and step status checks (e.g., step_id.status == 'failed')
|
|
413
|
+
*/
|
|
414
|
+
evaluateCondition(condition, context) {
|
|
415
|
+
// Simple expression parsing
|
|
416
|
+
const operators = ['==', '!=', '>=', '<=', '>', '<'];
|
|
417
|
+
let operator;
|
|
418
|
+
let parts = [];
|
|
419
|
+
for (const op of operators) {
|
|
420
|
+
if (condition.includes(op)) {
|
|
421
|
+
operator = op;
|
|
422
|
+
parts = condition.split(op).map((s) => s.trim());
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
if (!operator || parts.length !== 2) {
|
|
427
|
+
// Treat as boolean variable reference with nested property support
|
|
428
|
+
const value = this.resolveConditionValue(condition, context);
|
|
429
|
+
return Boolean(value);
|
|
430
|
+
}
|
|
431
|
+
const left = this.resolveConditionValue(parts[0], context);
|
|
432
|
+
const right = this.parseValue(parts[1]);
|
|
433
|
+
switch (operator) {
|
|
434
|
+
case '==':
|
|
435
|
+
return left == right;
|
|
436
|
+
case '!=':
|
|
437
|
+
return left != right;
|
|
438
|
+
case '>':
|
|
439
|
+
return Number(left) > Number(right);
|
|
440
|
+
case '<':
|
|
441
|
+
return Number(left) < Number(right);
|
|
442
|
+
case '>=':
|
|
443
|
+
return Number(left) >= Number(right);
|
|
444
|
+
case '<=':
|
|
445
|
+
return Number(left) <= Number(right);
|
|
446
|
+
default:
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Resolve a condition value with support for nested properties.
|
|
452
|
+
* Handles direct variable references and nested paths.
|
|
453
|
+
*/
|
|
454
|
+
resolveConditionValue(path, context) {
|
|
455
|
+
// Try to resolve the path directly from variables
|
|
456
|
+
const resolved = resolveVariablePath(path, context);
|
|
457
|
+
// Return the resolved value directly
|
|
458
|
+
return resolved;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Parse a value from a condition string.
|
|
462
|
+
*/
|
|
463
|
+
parseValue(value) {
|
|
464
|
+
// Remove quotes
|
|
465
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
466
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
467
|
+
return value.slice(1, -1);
|
|
468
|
+
}
|
|
469
|
+
// Numbers
|
|
470
|
+
if (!isNaN(Number(value))) {
|
|
471
|
+
return Number(value);
|
|
472
|
+
}
|
|
473
|
+
// Booleans
|
|
474
|
+
if (value === 'true')
|
|
475
|
+
return true;
|
|
476
|
+
if (value === 'false')
|
|
477
|
+
return false;
|
|
478
|
+
if (value === 'null')
|
|
479
|
+
return null;
|
|
480
|
+
return value;
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Build the final workflow result.
|
|
484
|
+
*/
|
|
485
|
+
buildWorkflowResult(workflow, context, stepResults, startedAt, error) {
|
|
486
|
+
const completedAt = new Date();
|
|
487
|
+
return {
|
|
488
|
+
workflowId: workflow.metadata.id,
|
|
489
|
+
runId: context.runId,
|
|
490
|
+
status: context.status,
|
|
491
|
+
stepResults,
|
|
492
|
+
output: context.variables,
|
|
493
|
+
error,
|
|
494
|
+
startedAt,
|
|
495
|
+
completedAt,
|
|
496
|
+
duration: completedAt.getTime() - startedAt.getTime(),
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Reset all circuit breakers.
|
|
501
|
+
*/
|
|
502
|
+
resetCircuitBreakers() {
|
|
503
|
+
for (const breaker of this.circuitBreakers.values()) {
|
|
504
|
+
breaker.reset();
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
// ============================================================================
|
|
509
|
+
// Helpers
|
|
510
|
+
// ============================================================================
|
|
511
|
+
function sleep(ms) {
|
|
512
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
513
|
+
}
|
|
514
|
+
//# sourceMappingURL=engine.js.map
|