yaml-flow 1.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +705 -225
  2. package/dist/batch/index.cjs +109 -0
  3. package/dist/batch/index.cjs.map +1 -0
  4. package/dist/batch/index.d.cts +126 -0
  5. package/dist/batch/index.d.ts +126 -0
  6. package/dist/batch/index.js +107 -0
  7. package/dist/batch/index.js.map +1 -0
  8. package/dist/config/index.cjs +80 -0
  9. package/dist/config/index.cjs.map +1 -0
  10. package/dist/config/index.d.cts +71 -0
  11. package/dist/config/index.d.ts +71 -0
  12. package/dist/config/index.js +77 -0
  13. package/dist/config/index.js.map +1 -0
  14. package/dist/constants-D1fTEbbM.d.cts +330 -0
  15. package/dist/constants-D1fTEbbM.d.ts +330 -0
  16. package/dist/event-graph/index.cjs +895 -0
  17. package/dist/event-graph/index.cjs.map +1 -0
  18. package/dist/event-graph/index.d.cts +53 -0
  19. package/dist/event-graph/index.d.ts +53 -0
  20. package/dist/event-graph/index.js +855 -0
  21. package/dist/event-graph/index.js.map +1 -0
  22. package/dist/index.cjs +1309 -312
  23. package/dist/index.cjs.map +1 -1
  24. package/dist/index.d.cts +5 -2
  25. package/dist/index.d.ts +5 -2
  26. package/dist/index.js +1271 -306
  27. package/dist/index.js.map +1 -1
  28. package/dist/step-machine/index.cjs +513 -0
  29. package/dist/step-machine/index.cjs.map +1 -0
  30. package/dist/step-machine/index.d.cts +77 -0
  31. package/dist/step-machine/index.d.ts +77 -0
  32. package/dist/step-machine/index.js +502 -0
  33. package/dist/step-machine/index.js.map +1 -0
  34. package/dist/stores/file.cjs.map +1 -1
  35. package/dist/stores/file.d.cts +4 -4
  36. package/dist/stores/file.d.ts +4 -4
  37. package/dist/stores/file.js.map +1 -1
  38. package/dist/stores/index.cjs +232 -0
  39. package/dist/stores/index.cjs.map +1 -0
  40. package/dist/stores/index.d.cts +4 -0
  41. package/dist/stores/index.d.ts +4 -0
  42. package/dist/stores/index.js +228 -0
  43. package/dist/stores/index.js.map +1 -0
  44. package/dist/stores/localStorage.cjs.map +1 -1
  45. package/dist/stores/localStorage.d.cts +4 -4
  46. package/dist/stores/localStorage.d.ts +4 -4
  47. package/dist/stores/localStorage.js.map +1 -1
  48. package/dist/stores/memory.cjs.map +1 -1
  49. package/dist/stores/memory.d.cts +4 -4
  50. package/dist/stores/memory.d.ts +4 -4
  51. package/dist/stores/memory.js.map +1 -1
  52. package/dist/types-FZ_eyErS.d.cts +115 -0
  53. package/dist/types-FZ_eyErS.d.ts +115 -0
  54. package/package.json +26 -6
  55. package/dist/core/index.cjs +0 -557
  56. package/dist/core/index.cjs.map +0 -1
  57. package/dist/core/index.d.cts +0 -102
  58. package/dist/core/index.d.ts +0 -102
  59. package/dist/core/index.js +0 -549
  60. package/dist/core/index.js.map +0 -1
  61. package/dist/types-BoWndaAJ.d.cts +0 -237
  62. package/dist/types-BoWndaAJ.d.ts +0 -237
package/dist/index.js CHANGED
@@ -1,3 +1,136 @@
1
+ // src/step-machine/reducer.ts
2
+ function applyStepResult(flow, state, stepName, stepResult) {
3
+ const stepConfig = flow.steps[stepName];
4
+ if (!stepConfig) {
5
+ throw new Error(`Step "${stepName}" not found in flow configuration`);
6
+ }
7
+ if (stepResult.result === "failure" && stepConfig.retry) {
8
+ const retryCount = state.retryCounts[stepName] ?? 0;
9
+ if (retryCount < stepConfig.retry.max_attempts) {
10
+ return {
11
+ newState: {
12
+ ...state,
13
+ retryCounts: {
14
+ ...state.retryCounts,
15
+ [stepName]: retryCount + 1
16
+ },
17
+ updatedAt: Date.now()
18
+ },
19
+ nextStep: stepName,
20
+ isTerminal: false,
21
+ isCircuitBroken: false,
22
+ shouldRetry: true
23
+ };
24
+ }
25
+ }
26
+ const nextStep = stepConfig.transitions[stepResult.result];
27
+ if (!nextStep) {
28
+ throw new Error(
29
+ `No transition defined for result "${stepResult.result}" in step "${stepName}"`
30
+ );
31
+ }
32
+ const isTerminal = !!flow.terminal_states[nextStep];
33
+ return {
34
+ newState: {
35
+ ...state,
36
+ currentStep: nextStep,
37
+ stepHistory: [...state.stepHistory, stepName],
38
+ retryCounts: {
39
+ ...state.retryCounts,
40
+ [stepName]: 0
41
+ },
42
+ updatedAt: Date.now()
43
+ },
44
+ nextStep,
45
+ isTerminal,
46
+ isCircuitBroken: false,
47
+ shouldRetry: false
48
+ };
49
+ }
50
+ function checkCircuitBreaker(flow, state, stepName) {
51
+ const stepConfig = flow.steps[stepName];
52
+ if (!stepConfig?.circuit_breaker) {
53
+ return {
54
+ broken: false,
55
+ newState: {
56
+ ...state,
57
+ iterationCounts: {
58
+ ...state.iterationCounts,
59
+ [stepName]: (state.iterationCounts[stepName] ?? 0) + 1
60
+ },
61
+ updatedAt: Date.now()
62
+ }
63
+ };
64
+ }
65
+ const count = state.iterationCounts[stepName] ?? 0;
66
+ if (count >= stepConfig.circuit_breaker.max_iterations) {
67
+ return {
68
+ broken: true,
69
+ redirectStep: stepConfig.circuit_breaker.on_open,
70
+ newState: {
71
+ ...state,
72
+ currentStep: stepConfig.circuit_breaker.on_open,
73
+ updatedAt: Date.now()
74
+ }
75
+ };
76
+ }
77
+ return {
78
+ broken: false,
79
+ newState: {
80
+ ...state,
81
+ iterationCounts: {
82
+ ...state.iterationCounts,
83
+ [stepName]: count + 1
84
+ },
85
+ updatedAt: Date.now()
86
+ }
87
+ };
88
+ }
89
+ function computeStepInput(flow, stepName, allData) {
90
+ const stepConfig = flow.steps[stepName];
91
+ if (!stepConfig) {
92
+ throw new Error(`Step "${stepName}" not found`);
93
+ }
94
+ if (stepConfig.expects_data) {
95
+ const input = {};
96
+ for (const key of stepConfig.expects_data) {
97
+ input[key] = allData[key];
98
+ }
99
+ return input;
100
+ }
101
+ return { ...allData };
102
+ }
103
+ function extractReturnData(returnArtifacts, allData) {
104
+ if (returnArtifacts === false || returnArtifacts === void 0) {
105
+ return {};
106
+ }
107
+ if (typeof returnArtifacts === "string") {
108
+ return { [returnArtifacts]: allData[returnArtifacts] };
109
+ }
110
+ if (Array.isArray(returnArtifacts)) {
111
+ const result = {};
112
+ for (const key of returnArtifacts) {
113
+ result[key] = allData[key];
114
+ }
115
+ return result;
116
+ }
117
+ return {};
118
+ }
119
+ function createInitialState(flow, runId) {
120
+ const now = Date.now();
121
+ return {
122
+ runId,
123
+ flowId: flow.id ?? "unnamed",
124
+ currentStep: flow.settings.start_step,
125
+ status: "running",
126
+ stepHistory: [],
127
+ iterationCounts: {},
128
+ retryCounts: {},
129
+ startedAt: now,
130
+ updatedAt: now
131
+ };
132
+ }
133
+
1
134
  // src/stores/memory.ts
2
135
  var MemoryStore = class {
3
136
  runs = /* @__PURE__ */ new Map();
@@ -41,7 +174,7 @@ var MemoryStore = class {
41
174
  }
42
175
  };
43
176
 
44
- // src/core/engine.ts
177
+ // src/step-machine/StepMachine.ts
45
178
  function generateRunId() {
46
179
  if (typeof crypto !== "undefined" && crypto.randomUUID) {
47
180
  return crypto.randomUUID();
@@ -52,7 +185,7 @@ function generateRunId() {
52
185
  return v.toString(16);
53
186
  });
54
187
  }
55
- var FlowEngine = class {
188
+ var StepMachine = class {
56
189
  flow;
57
190
  handlers;
58
191
  store;
@@ -73,9 +206,6 @@ var FlowEngine = class {
73
206
  }
74
207
  this.validateFlow();
75
208
  }
76
- /**
77
- * Validate the flow configuration
78
- */
79
209
  validateFlow() {
80
210
  const { settings, steps, terminal_states } = this.flow;
81
211
  if (!settings?.start_step) {
@@ -88,7 +218,7 @@ var FlowEngine = class {
88
218
  throw new Error("Flow must have at least one terminal_state defined");
89
219
  }
90
220
  if (!steps[settings.start_step] && !terminal_states[settings.start_step]) {
91
- throw new Error(`Start step "${settings.start_step}" not found in steps or terminal_states`);
221
+ throw new Error(`Start step "${settings.start_step}" not found`);
92
222
  }
93
223
  for (const [stepName, stepConfig] of Object.entries(steps)) {
94
224
  for (const [result, target] of Object.entries(stepConfig.transitions)) {
@@ -100,23 +230,32 @@ var FlowEngine = class {
100
230
  }
101
231
  }
102
232
  }
103
- /**
104
- * Run the flow from the start
105
- */
233
+ on(eventType, listener) {
234
+ if (!this.listeners.has(eventType)) {
235
+ this.listeners.set(eventType, /* @__PURE__ */ new Set());
236
+ }
237
+ this.listeners.get(eventType).add(listener);
238
+ }
239
+ off(eventType, listener) {
240
+ this.listeners.get(eventType)?.delete(listener);
241
+ }
242
+ emit(event) {
243
+ const typeListeners = this.listeners.get(event.type);
244
+ if (typeListeners) {
245
+ for (const listener of typeListeners) {
246
+ try {
247
+ listener(event);
248
+ } catch {
249
+ }
250
+ }
251
+ }
252
+ }
253
+ sleep(ms) {
254
+ return new Promise((resolve) => setTimeout(resolve, ms));
255
+ }
106
256
  async run(initialData) {
107
257
  const runId = generateRunId();
108
- const startedAt = Date.now();
109
- const runState = {
110
- runId,
111
- flowId: this.flow.id ?? "unnamed",
112
- currentStep: this.flow.settings.start_step,
113
- status: "running",
114
- stepHistory: [],
115
- iterationCounts: {},
116
- retryCounts: {},
117
- startedAt,
118
- updatedAt: startedAt
119
- };
258
+ let runState = createInitialState(this.flow, runId);
120
259
  await this.store.saveRunState(runId, runState);
121
260
  if (initialData) {
122
261
  for (const [key, value] of Object.entries(initialData)) {
@@ -126,22 +265,16 @@ var FlowEngine = class {
126
265
  this.emit({
127
266
  type: "flow:start",
128
267
  runId,
129
- timestamp: startedAt,
130
- data: { initialData }
268
+ timestamp: runState.startedAt,
269
+ data: { initialData: initialData ?? {} }
131
270
  });
132
271
  try {
133
- return await this.executeLoop(runId, runState, startedAt);
272
+ return await this.executeLoop(runId, runState);
134
273
  } catch (error) {
135
274
  const err = error instanceof Error ? error : new Error(String(error));
136
- this.emit({
137
- type: "flow:error",
138
- runId,
139
- timestamp: Date.now(),
140
- data: { error: err.message }
141
- });
275
+ this.emit({ type: "flow:error", runId, timestamp: Date.now(), data: { error: err.message } });
142
276
  this.options.onError?.(err);
143
- runState.status = "failed";
144
- runState.updatedAt = Date.now();
277
+ runState = { ...runState, status: "failed", updatedAt: Date.now() };
145
278
  await this.store.saveRunState(runId, runState);
146
279
  return {
147
280
  runId,
@@ -149,309 +282,135 @@ var FlowEngine = class {
149
282
  data: await this.store.getAllData(runId),
150
283
  finalStep: runState.currentStep,
151
284
  stepHistory: runState.stepHistory,
152
- durationMs: Date.now() - startedAt,
285
+ durationMs: Date.now() - runState.startedAt,
153
286
  error: err
154
287
  };
155
288
  }
156
289
  }
157
- /**
158
- * Resume a paused or interrupted flow
159
- */
160
290
  async resume(runId) {
161
291
  const runState = await this.store.loadRunState(runId);
162
- if (!runState) {
163
- throw new Error(`No run found with ID: ${runId}`);
164
- }
292
+ if (!runState) throw new Error(`No run found with ID: ${runId}`);
165
293
  if (runState.status === "completed" || runState.status === "failed") {
166
294
  throw new Error(`Cannot resume a ${runState.status} run`);
167
295
  }
168
- const startedAt = runState.startedAt;
169
- runState.status = "running";
170
- runState.pausedAt = void 0;
171
- runState.updatedAt = Date.now();
172
- await this.store.saveRunState(runId, runState);
173
- this.emit({
174
- type: "flow:resumed",
175
- runId,
176
- timestamp: Date.now(),
177
- data: { currentStep: runState.currentStep }
178
- });
179
- return this.executeLoop(runId, runState, startedAt);
296
+ const updated = { ...runState, status: "running", pausedAt: void 0, updatedAt: Date.now() };
297
+ await this.store.saveRunState(runId, updated);
298
+ this.emit({ type: "flow:resumed", runId, timestamp: Date.now(), data: { currentStep: updated.currentStep } });
299
+ return this.executeLoop(runId, updated);
180
300
  }
181
- /**
182
- * Pause a running flow
183
- */
184
301
  async pause(runId) {
185
302
  const runState = await this.store.loadRunState(runId);
186
- if (!runState) {
187
- throw new Error(`No run found with ID: ${runId}`);
188
- }
189
- runState.status = "paused";
190
- runState.pausedAt = Date.now();
191
- runState.updatedAt = Date.now();
192
- await this.store.saveRunState(runId, runState);
193
- this.emit({
194
- type: "flow:paused",
195
- runId,
196
- timestamp: Date.now(),
197
- data: { currentStep: runState.currentStep }
198
- });
303
+ if (!runState) throw new Error(`No run found with ID: ${runId}`);
304
+ const updated = { ...runState, status: "paused", pausedAt: Date.now(), updatedAt: Date.now() };
305
+ await this.store.saveRunState(runId, updated);
306
+ this.emit({ type: "flow:paused", runId, timestamp: Date.now(), data: { currentStep: updated.currentStep } });
199
307
  }
200
- /**
201
- * Main execution loop
202
- */
203
- async executeLoop(runId, runState, startedAt) {
308
+ async executeLoop(runId, runState) {
204
309
  const maxSteps = this.flow.settings.max_total_steps ?? 100;
205
310
  const timeoutMs = this.flow.settings.timeout_ms;
311
+ let current = runState;
206
312
  let iterations = 0;
207
313
  while (iterations < maxSteps) {
208
314
  if (this.aborted) {
209
- runState.status = "cancelled";
210
- runState.updatedAt = Date.now();
211
- await this.store.saveRunState(runId, runState);
212
- return {
213
- runId,
214
- status: "cancelled",
215
- data: await this.store.getAllData(runId),
216
- finalStep: runState.currentStep,
217
- stepHistory: runState.stepHistory,
218
- durationMs: Date.now() - startedAt
219
- };
315
+ current = { ...current, status: "cancelled", updatedAt: Date.now() };
316
+ await this.store.saveRunState(runId, current);
317
+ return { runId, status: "cancelled", data: await this.store.getAllData(runId), finalStep: current.currentStep, stepHistory: current.stepHistory, durationMs: Date.now() - current.startedAt };
220
318
  }
221
- if (timeoutMs && Date.now() - startedAt > timeoutMs) {
222
- runState.status = "completed";
223
- runState.updatedAt = Date.now();
224
- await this.store.saveRunState(runId, runState);
225
- return {
226
- runId,
227
- status: "timeout",
228
- intent: "timeout",
229
- data: await this.store.getAllData(runId),
230
- finalStep: runState.currentStep,
231
- stepHistory: runState.stepHistory,
232
- durationMs: Date.now() - startedAt
233
- };
319
+ if (timeoutMs && Date.now() - current.startedAt > timeoutMs) {
320
+ current = { ...current, status: "completed", updatedAt: Date.now() };
321
+ await this.store.saveRunState(runId, current);
322
+ return { runId, status: "timeout", intent: "timeout", data: await this.store.getAllData(runId), finalStep: current.currentStep, stepHistory: current.stepHistory, durationMs: Date.now() - current.startedAt };
234
323
  }
235
- const currentStep = runState.currentStep;
236
- const terminalState = this.flow.terminal_states[currentStep];
324
+ const stepName = current.currentStep;
325
+ const terminalState = this.flow.terminal_states[stepName];
237
326
  if (terminalState) {
238
- runState.status = "completed";
239
- runState.updatedAt = Date.now();
240
- await this.store.saveRunState(runId, runState);
241
- const allData = await this.store.getAllData(runId);
242
- const returnData = this.extractReturnData(terminalState.return_artifacts, allData);
327
+ current = { ...current, status: "completed", updatedAt: Date.now() };
328
+ await this.store.saveRunState(runId, current);
329
+ const allData2 = await this.store.getAllData(runId);
243
330
  const result = {
244
331
  runId,
245
332
  status: "completed",
246
333
  intent: terminalState.return_intent,
247
- data: returnData,
248
- finalStep: currentStep,
249
- stepHistory: runState.stepHistory,
250
- durationMs: Date.now() - startedAt
334
+ data: extractReturnData(terminalState.return_artifacts, allData2),
335
+ finalStep: stepName,
336
+ stepHistory: current.stepHistory,
337
+ durationMs: Date.now() - current.startedAt
251
338
  };
252
- this.emit({
253
- type: "flow:complete",
254
- runId,
255
- timestamp: Date.now(),
256
- data: { ...result }
257
- });
339
+ this.emit({ type: "flow:complete", runId, timestamp: Date.now(), data: { ...result } });
258
340
  this.options.onComplete?.(result);
259
341
  return result;
260
342
  }
261
- const stepConfig = this.flow.steps[currentStep];
262
- if (!stepConfig) {
263
- throw new Error(`Step "${currentStep}" not found in flow configuration`);
264
- }
265
- if (stepConfig.circuit_breaker) {
266
- const count = runState.iterationCounts[currentStep] ?? 0;
267
- if (count >= stepConfig.circuit_breaker.max_iterations) {
268
- runState.currentStep = stepConfig.circuit_breaker.on_open;
269
- runState.updatedAt = Date.now();
270
- await this.store.saveRunState(runId, runState);
271
- iterations++;
272
- continue;
273
- }
274
- }
275
- runState.iterationCounts[currentStep] = (runState.iterationCounts[currentStep] ?? 0) + 1;
276
- const stepResult = await this.executeStep(runId, currentStep, stepConfig);
277
- if (stepResult.result === "failure" && stepConfig.retry) {
278
- const retryCount = runState.retryCounts[currentStep] ?? 0;
279
- if (retryCount < stepConfig.retry.max_attempts) {
280
- runState.retryCounts[currentStep] = retryCount + 1;
281
- if (stepConfig.retry.delay_ms) {
282
- const delay = stepConfig.retry.backoff_multiplier ? stepConfig.retry.delay_ms * Math.pow(stepConfig.retry.backoff_multiplier, retryCount) : stepConfig.retry.delay_ms;
283
- await this.sleep(delay);
284
- }
285
- iterations++;
286
- continue;
287
- }
288
- }
289
- const nextStep = stepConfig.transitions[stepResult.result];
290
- if (!nextStep) {
291
- throw new Error(
292
- `No transition defined for result "${stepResult.result}" in step "${currentStep}"`
293
- );
343
+ const cbResult = checkCircuitBreaker(this.flow, current, stepName);
344
+ if (cbResult.broken) {
345
+ current = cbResult.newState;
346
+ await this.store.saveRunState(runId, current);
347
+ iterations++;
348
+ continue;
294
349
  }
295
- runState.stepHistory.push(currentStep);
296
- runState.currentStep = nextStep;
297
- runState.updatedAt = Date.now();
298
- runState.retryCounts[currentStep] = 0;
299
- await this.store.saveRunState(runId, runState);
300
- this.emit({
301
- type: "transition",
350
+ current = cbResult.newState;
351
+ const allData = await this.store.getAllData(runId);
352
+ const input = computeStepInput(this.flow, stepName, allData);
353
+ const context = {
302
354
  runId,
303
- timestamp: Date.now(),
304
- data: { from: currentStep, to: nextStep, result: stepResult.result }
305
- });
306
- this.options.onTransition?.(currentStep, nextStep);
307
- iterations++;
308
- }
309
- runState.status = "completed";
310
- runState.updatedAt = Date.now();
311
- await this.store.saveRunState(runId, runState);
312
- return {
313
- runId,
314
- status: "max_iterations",
315
- intent: "max_iterations",
316
- data: await this.store.getAllData(runId),
317
- finalStep: runState.currentStep,
318
- stepHistory: runState.stepHistory,
319
- durationMs: Date.now() - startedAt
320
- };
321
- }
322
- /**
323
- * Execute a single step
324
- */
325
- async executeStep(runId, stepName, stepConfig) {
326
- const handler = this.handlers.get(stepName);
327
- if (!handler) {
328
- throw new Error(`No handler registered for step "${stepName}"`);
329
- }
330
- const allData = await this.store.getAllData(runId);
331
- const input = {};
332
- if (stepConfig.expects_data) {
333
- for (const key of stepConfig.expects_data) {
334
- input[key] = allData[key];
335
- }
336
- } else {
337
- Object.assign(input, allData);
338
- }
339
- const context = {
340
- runId,
341
- stepName,
342
- components: this.components,
343
- store: this.store,
344
- signal: this.options.signal,
345
- emit: (event, data) => {
346
- this.emit({
347
- type: "step:complete",
348
- // Custom events map to step:complete
349
- runId,
350
- timestamp: Date.now(),
351
- data: { event, payload: data }
352
- });
355
+ stepName,
356
+ components: this.components,
357
+ store: this.store,
358
+ signal: this.options.signal,
359
+ emit: (event, data) => {
360
+ this.emit({ type: "step:complete", runId, timestamp: Date.now(), data: { event, payload: data } });
361
+ }
362
+ };
363
+ this.emit({ type: "step:start", runId, timestamp: Date.now(), data: { step: stepName, input } });
364
+ let stepResult;
365
+ try {
366
+ const handler = this.handlers.get(stepName);
367
+ if (!handler) throw new Error(`No handler registered for step "${stepName}"`);
368
+ stepResult = await handler(input, context);
369
+ } catch (error) {
370
+ const err = error instanceof Error ? error : new Error(String(error));
371
+ this.emit({ type: "step:error", runId, timestamp: Date.now(), data: { step: stepName, error: err.message } });
372
+ stepResult = { result: "failure", data: { error: err.message } };
353
373
  }
354
- };
355
- this.emit({
356
- type: "step:start",
357
- runId,
358
- timestamp: Date.now(),
359
- data: { step: stepName, input }
360
- });
361
- try {
362
- const result = await handler(input, context);
363
- if (result.data) {
364
- for (const [key, value] of Object.entries(result.data)) {
374
+ if (stepResult.data) {
375
+ for (const [key, value] of Object.entries(stepResult.data)) {
365
376
  await this.store.setData(runId, key, value);
366
377
  }
367
378
  }
368
- this.emit({
369
- type: "step:complete",
370
- runId,
371
- timestamp: Date.now(),
372
- data: { step: stepName, result: result.result, outputKeys: Object.keys(result.data ?? {}) }
373
- });
374
- this.options.onStep?.(stepName, result);
375
- return result;
376
- } catch (error) {
377
- const err = error instanceof Error ? error : new Error(String(error));
378
- this.emit({
379
- type: "step:error",
380
- runId,
381
- timestamp: Date.now(),
382
- data: { step: stepName, error: err.message }
383
- });
384
- return { result: "failure", data: { error: err.message } };
385
- }
386
- }
387
- /**
388
- * Extract data to return based on return_artifacts configuration
389
- */
390
- extractReturnData(returnArtifacts, allData) {
391
- if (returnArtifacts === false || returnArtifacts === void 0) {
392
- return {};
393
- }
394
- if (typeof returnArtifacts === "string") {
395
- return { [returnArtifacts]: allData[returnArtifacts] };
396
- }
397
- if (Array.isArray(returnArtifacts)) {
398
- const result = {};
399
- for (const key of returnArtifacts) {
400
- result[key] = allData[key];
401
- }
402
- return result;
403
- }
404
- return allData;
405
- }
406
- /**
407
- * Sleep helper
408
- */
409
- sleep(ms) {
410
- return new Promise((resolve) => setTimeout(resolve, ms));
411
- }
412
- /**
413
- * Subscribe to flow events
414
- */
415
- on(type, listener) {
416
- if (!this.listeners.has(type)) {
417
- this.listeners.set(type, /* @__PURE__ */ new Set());
418
- }
419
- this.listeners.get(type).add(listener);
420
- return () => {
421
- this.listeners.get(type)?.delete(listener);
422
- };
423
- }
424
- /**
425
- * Emit an event
426
- */
427
- emit(event) {
428
- const typeListeners = this.listeners.get(event.type);
429
- if (typeListeners) {
430
- for (const listener of typeListeners) {
431
- try {
432
- listener(event);
433
- } catch {
379
+ this.emit({ type: "step:complete", runId, timestamp: Date.now(), data: { step: stepName, result: stepResult.result } });
380
+ this.options.onStep?.(stepName, stepResult);
381
+ const reducerResult = applyStepResult(this.flow, current, stepName, stepResult);
382
+ current = reducerResult.newState;
383
+ if (reducerResult.shouldRetry) {
384
+ await this.store.saveRunState(runId, current);
385
+ const stepConfig = this.flow.steps[stepName];
386
+ if (stepConfig.retry?.delay_ms) {
387
+ const retryCount = current.retryCounts[stepName] ?? 0;
388
+ const delay = stepConfig.retry.backoff_multiplier ? stepConfig.retry.delay_ms * Math.pow(stepConfig.retry.backoff_multiplier, retryCount - 1) : stepConfig.retry.delay_ms;
389
+ await this.sleep(delay);
434
390
  }
391
+ iterations++;
392
+ continue;
435
393
  }
394
+ await this.store.saveRunState(runId, current);
395
+ this.emit({ type: "transition", runId, timestamp: Date.now(), data: { from: stepName, to: current.currentStep, result: stepResult.result } });
396
+ this.options.onTransition?.(stepName, current.currentStep);
397
+ iterations++;
436
398
  }
437
- }
438
- /**
439
- * Get the current store instance
440
- */
441
- getStore() {
442
- return this.store;
399
+ current = { ...current, status: "completed", updatedAt: Date.now() };
400
+ await this.store.saveRunState(runId, current);
401
+ return { runId, status: "max_iterations", intent: "max_iterations", data: await this.store.getAllData(runId), finalStep: current.currentStep, stepHistory: current.stepHistory, durationMs: Date.now() - current.startedAt };
443
402
  }
444
403
  };
445
- function createEngine(flow, handlers, options) {
446
- return new FlowEngine(flow, handlers, options);
404
+ function createStepMachine(flow, handlers, options) {
405
+ return new StepMachine(flow, handlers, options);
447
406
  }
448
407
 
449
- // src/core/loader.ts
450
- async function parseYaml(yamlString) {
408
+ // src/step-machine/loader.ts
409
+ async function parseStepFlowYaml(yamlString) {
451
410
  const yaml = await import('yaml');
452
411
  return yaml.parse(yamlString);
453
412
  }
454
- async function loadFlowFromUrl(url) {
413
+ async function loadStepFlowFromUrl(url) {
455
414
  const response = await fetch(url);
456
415
  if (!response.ok) {
457
416
  throw new Error(`Failed to load flow from ${url}: ${response.statusText}`);
@@ -461,17 +420,17 @@ async function loadFlowFromUrl(url) {
461
420
  if (contentType.includes("json") || url.endsWith(".json")) {
462
421
  return JSON.parse(text);
463
422
  }
464
- return parseYaml(text);
423
+ return parseStepFlowYaml(text);
465
424
  }
466
- async function loadFlowFromFile(filePath) {
425
+ async function loadStepFlowFromFile(filePath) {
467
426
  const fs = await import('fs/promises');
468
427
  const text = await fs.readFile(filePath, "utf-8");
469
428
  if (filePath.endsWith(".json")) {
470
429
  return JSON.parse(text);
471
430
  }
472
- return parseYaml(text);
431
+ return parseStepFlowYaml(text);
473
432
  }
474
- function validateFlowConfig(flow) {
433
+ function validateStepFlowConfig(flow) {
475
434
  const errors = [];
476
435
  if (!flow || typeof flow !== "object") {
477
436
  return ["Flow must be an object"];
@@ -484,12 +443,6 @@ function validateFlowConfig(flow) {
484
443
  if (typeof settings.start_step !== "string") {
485
444
  errors.push("settings.start_step must be a string");
486
445
  }
487
- if (settings.max_total_steps !== void 0 && typeof settings.max_total_steps !== "number") {
488
- errors.push("settings.max_total_steps must be a number");
489
- }
490
- if (settings.timeout_ms !== void 0 && typeof settings.timeout_ms !== "number") {
491
- errors.push("settings.timeout_ms must be a number");
492
- }
493
446
  }
494
447
  if (!f.steps || typeof f.steps !== "object") {
495
448
  errors.push('Flow must have a "steps" object');
@@ -523,27 +476,861 @@ function validateFlowConfig(flow) {
523
476
  }
524
477
  return errors;
525
478
  }
526
- async function loadFlow(source) {
479
+ async function loadStepFlow(source) {
527
480
  let flow;
528
481
  if (typeof source === "string") {
529
482
  if (source.startsWith("http://") || source.startsWith("https://")) {
530
- flow = await loadFlowFromUrl(source);
483
+ flow = await loadStepFlowFromUrl(source);
531
484
  } else if (source.includes("{")) {
532
485
  flow = JSON.parse(source);
533
486
  } else {
534
- flow = await loadFlowFromFile(source);
487
+ flow = await loadStepFlowFromFile(source);
535
488
  }
536
489
  } else {
537
490
  flow = source;
538
491
  }
539
- const errors = validateFlowConfig(flow);
492
+ const errors = validateStepFlowConfig(flow);
540
493
  if (errors.length > 0) {
541
- throw new Error(`Invalid flow configuration:
494
+ throw new Error(`Invalid step flow configuration:
542
495
  - ${errors.join("\n- ")}`);
543
496
  }
544
497
  return flow;
545
498
  }
546
499
 
500
+ // src/event-graph/constants.ts
501
+ var TASK_STATUS = {
502
+ NOT_STARTED: "not-started",
503
+ RUNNING: "running",
504
+ COMPLETED: "completed",
505
+ FAILED: "failed",
506
+ INACTIVATED: "inactivated"
507
+ };
508
+ var EXECUTION_STATUS = {
509
+ CREATED: "created",
510
+ RUNNING: "running",
511
+ PAUSED: "paused",
512
+ STOPPED: "stopped",
513
+ COMPLETED: "completed",
514
+ FAILED: "failed"
515
+ };
516
+ var COMPLETION_STRATEGIES = {
517
+ ALL_TASKS_DONE: "all-tasks-done",
518
+ ALL_OUTPUTS_DONE: "all-outputs-done",
519
+ ONLY_RESOLVED: "only-resolved",
520
+ GOAL_REACHED: "goal-reached",
521
+ MANUAL: "manual"
522
+ };
523
+ var EXECUTION_MODES = {
524
+ DEPENDENCY_MODE: "dependency-mode",
525
+ ELIGIBILITY_MODE: "eligibility-mode"
526
+ };
527
+ var CONFLICT_STRATEGIES = {
528
+ ALPHABETICAL: "alphabetical",
529
+ PRIORITY_FIRST: "priority-first",
530
+ DURATION_FIRST: "duration-first",
531
+ COST_OPTIMIZED: "cost-optimized",
532
+ RESOURCE_AWARE: "resource-aware",
533
+ RANDOM_SELECT: "random-select",
534
+ USER_CHOICE: "user-choice",
535
+ PARALLEL_ALL: "parallel-all",
536
+ SKIP_CONFLICTS: "skip-conflicts",
537
+ ROUND_ROBIN: "round-robin"
538
+ };
539
+ var DEFAULTS = {
540
+ EXECUTION_MODE: "eligibility-mode",
541
+ CONFLICT_STRATEGY: "alphabetical",
542
+ COMPLETION_STRATEGY: "all-outputs-done",
543
+ MAX_ITERATIONS: 1e3
544
+ };
545
+
546
+ // src/event-graph/graph-helpers.ts
547
+ function getProvides(task) {
548
+ if (!task) return [];
549
+ if (Array.isArray(task.provides)) return task.provides;
550
+ return [];
551
+ }
552
+ function getRequires(task) {
553
+ if (!task) return [];
554
+ if (Array.isArray(task.requires)) return task.requires;
555
+ return [];
556
+ }
557
+ function getAllTasks(graph) {
558
+ return graph.tasks ?? {};
559
+ }
560
+ function getTask(graph, taskName) {
561
+ return graph.tasks[taskName];
562
+ }
563
+ function hasTask(graph, taskName) {
564
+ return taskName in graph.tasks;
565
+ }
566
+ function isNonActiveTask(taskState) {
567
+ if (!taskState) return false;
568
+ return taskState.status === TASK_STATUS.FAILED || taskState.status === TASK_STATUS.INACTIVATED;
569
+ }
570
+ function isTaskCompleted(taskState) {
571
+ return taskState?.status === TASK_STATUS.COMPLETED;
572
+ }
573
+ function isTaskRunning(taskState) {
574
+ return taskState?.status === TASK_STATUS.RUNNING;
575
+ }
576
+ function isRepeatableTask(taskConfig) {
577
+ return taskConfig.repeatable === true || typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null;
578
+ }
579
+ function getRepeatableMax(taskConfig) {
580
+ if (taskConfig.repeatable === true) return void 0;
581
+ if (typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null) {
582
+ return taskConfig.repeatable.max;
583
+ }
584
+ return void 0;
585
+ }
586
+ function computeAvailableOutputs(graph, taskStates) {
587
+ const outputs = /* @__PURE__ */ new Set();
588
+ for (const [taskName, taskState] of Object.entries(taskStates)) {
589
+ if (taskState.status === TASK_STATUS.COMPLETED) {
590
+ const taskConfig = graph.tasks[taskName];
591
+ if (taskConfig) {
592
+ const provides = getProvides(taskConfig);
593
+ provides.forEach((output) => outputs.add(output));
594
+ }
595
+ }
596
+ }
597
+ return Array.from(outputs);
598
+ }
599
+ function groupTasksByProvides(candidateTaskNames, tasks) {
600
+ const outputGroups = {};
601
+ candidateTaskNames.forEach((taskName) => {
602
+ const task = tasks[taskName];
603
+ if (!task) return;
604
+ const provides = getProvides(task);
605
+ provides.forEach((output) => {
606
+ if (!outputGroups[output]) {
607
+ outputGroups[output] = [];
608
+ }
609
+ outputGroups[output].push(taskName);
610
+ });
611
+ });
612
+ return outputGroups;
613
+ }
614
+ function hasOutputConflict(taskName, taskProvides, candidates, tasks) {
615
+ for (const otherName of candidates) {
616
+ if (otherName === taskName) continue;
617
+ const otherProvides = getProvides(tasks[otherName]);
618
+ const overlapping = taskProvides.some((output) => otherProvides.includes(output));
619
+ if (overlapping) return true;
620
+ }
621
+ return false;
622
+ }
623
+ function addDynamicTask(graph, taskName, taskConfig) {
624
+ return {
625
+ ...graph,
626
+ tasks: {
627
+ ...graph.tasks,
628
+ [taskName]: taskConfig
629
+ }
630
+ };
631
+ }
632
+ function createDefaultTaskState() {
633
+ return {
634
+ status: "not-started",
635
+ executionCount: 0,
636
+ retryCount: 0,
637
+ lastEpoch: 0,
638
+ messages: [],
639
+ progress: null
640
+ };
641
+ }
642
+ function createInitialExecutionState(graph, executionId) {
643
+ const tasks = {};
644
+ for (const taskName of Object.keys(graph.tasks)) {
645
+ tasks[taskName] = createDefaultTaskState();
646
+ }
647
+ return {
648
+ status: "running",
649
+ tasks,
650
+ availableOutputs: [],
651
+ stuckDetection: { is_stuck: false, stuck_description: null, outputs_unresolvable: [], tasks_blocked: [] },
652
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
653
+ executionId,
654
+ executionConfig: {
655
+ executionMode: graph.settings.execution_mode ?? "eligibility-mode",
656
+ conflictStrategy: graph.settings.conflict_strategy ?? "alphabetical",
657
+ completionStrategy: graph.settings.completion
658
+ }
659
+ };
660
+ }
661
+
662
+ // src/event-graph/conflict-resolution.ts
663
+ function selectBestAlternative(alternatives, graphTasks, _executionState, strategy) {
664
+ switch (strategy) {
665
+ case "alphabetical":
666
+ return selectByAlphabetical(alternatives);
667
+ case "priority-first":
668
+ return selectByPriorityFirst(alternatives, graphTasks);
669
+ case "duration-first":
670
+ return selectByDurationFirst(alternatives, graphTasks);
671
+ case "cost-optimized":
672
+ return selectByCostOptimized(alternatives, graphTasks);
673
+ case "resource-aware":
674
+ return selectByResourceAware(alternatives, graphTasks);
675
+ case "round-robin":
676
+ return selectByRoundRobin(alternatives, _executionState);
677
+ default:
678
+ return selectByAlphabetical(alternatives);
679
+ }
680
+ }
681
+ function selectByAlphabetical(alternatives) {
682
+ return [...alternatives].sort((a, b) => a.localeCompare(b))[0];
683
+ }
684
+ function selectByPriorityFirst(alternatives, graphTasks) {
685
+ return [...alternatives].sort((a, b) => {
686
+ const pA = graphTasks[a]?.priority ?? 0;
687
+ const pB = graphTasks[b]?.priority ?? 0;
688
+ if (pA !== pB) return pB - pA;
689
+ const dA = getEstimatedDuration(graphTasks[a]);
690
+ const dB = getEstimatedDuration(graphTasks[b]);
691
+ if (dA !== dB) return dA - dB;
692
+ return a.localeCompare(b);
693
+ })[0];
694
+ }
695
+ function selectByDurationFirst(alternatives, graphTasks) {
696
+ return [...alternatives].sort((a, b) => {
697
+ const dA = getEstimatedDuration(graphTasks[a]);
698
+ const dB = getEstimatedDuration(graphTasks[b]);
699
+ if (dA !== dB) return dA - dB;
700
+ const pA = graphTasks[a]?.priority ?? 0;
701
+ const pB = graphTasks[b]?.priority ?? 0;
702
+ if (pA !== pB) return pB - pA;
703
+ return a.localeCompare(b);
704
+ })[0];
705
+ }
706
+ function selectByCostOptimized(alternatives, graphTasks) {
707
+ return [...alternatives].sort((a, b) => {
708
+ const cA = graphTasks[a]?.estimatedCost ?? 0;
709
+ const cB = graphTasks[b]?.estimatedCost ?? 0;
710
+ if (cA !== cB) return cA - cB;
711
+ const pA = graphTasks[a]?.priority ?? 0;
712
+ const pB = graphTasks[b]?.priority ?? 0;
713
+ if (pA !== pB) return pB - pA;
714
+ return a.localeCompare(b);
715
+ })[0];
716
+ }
717
+ function selectByResourceAware(alternatives, graphTasks) {
718
+ return [...alternatives].sort((a, b) => {
719
+ const rA = graphTasks[a]?.estimatedResources?.cpu ?? 1;
720
+ const rB = graphTasks[b]?.estimatedResources?.cpu ?? 1;
721
+ if (rA !== rB) return rA - rB;
722
+ const pA = graphTasks[a]?.priority ?? 0;
723
+ const pB = graphTasks[b]?.priority ?? 0;
724
+ if (pA !== pB) return pB - pA;
725
+ return a.localeCompare(b);
726
+ })[0];
727
+ }
728
+ function selectByRoundRobin(alternatives, executionState) {
729
+ const totalExecs = Object.values(executionState.tasks).reduce((sum, t) => sum + t.executionCount, 0);
730
+ const sorted = [...alternatives].sort();
731
+ return sorted[totalExecs % sorted.length];
732
+ }
733
+ function getEstimatedDuration(taskConfig) {
734
+ return taskConfig?.estimatedDuration ?? Infinity;
735
+ }
736
+ function getNonConflictingTasks(candidates, graphTasks) {
737
+ return candidates.filter((taskName) => {
738
+ const provides = getProvides(graphTasks[taskName]);
739
+ return !hasOutputConflict(taskName, provides, candidates, graphTasks);
740
+ });
741
+ }
742
+ function selectRandomTasks(candidates, graphTasks) {
743
+ const outputGroups = groupTasksByProvides(candidates, graphTasks);
744
+ const selected = [];
745
+ for (const groupTasks of Object.values(outputGroups)) {
746
+ if (groupTasks.length === 1) {
747
+ selected.push(...groupTasks);
748
+ } else {
749
+ const idx = Math.floor(Math.random() * groupTasks.length);
750
+ selected.push(groupTasks[idx]);
751
+ }
752
+ }
753
+ const nonConflicting = getNonConflictingTasks(candidates, graphTasks);
754
+ nonConflicting.forEach((t) => {
755
+ if (!selected.includes(t)) selected.push(t);
756
+ });
757
+ return selected;
758
+ }
759
+
760
+ // src/event-graph/completion.ts
761
+ function isExecutionComplete(graph, state) {
762
+ const strategy = state.executionConfig.completionStrategy;
763
+ switch (strategy) {
764
+ case "all-tasks-done":
765
+ return checkAllTasksDone(graph, state);
766
+ case "all-outputs-done":
767
+ return checkAllOutputsDone(graph, state);
768
+ case "only-resolved":
769
+ return checkOnlyResolved(graph, state);
770
+ case "goal-reached":
771
+ return checkGoalReached(graph, state);
772
+ case "manual":
773
+ return { isComplete: false, expectedCompletion: { taskNames: [], outputs: [] } };
774
+ default:
775
+ return checkAllOutputsDone(graph, state);
776
+ }
777
+ }
778
+ function checkAllTasksDone(graph, state) {
779
+ const graphTasks = getAllTasks(graph);
780
+ const allTaskNames = Object.keys(graphTasks);
781
+ if (allTaskNames.length === 0) {
782
+ return { isComplete: true, expectedCompletion: { taskNames: [], outputs: [] } };
783
+ }
784
+ const allDone = allTaskNames.every((taskName) => {
785
+ const taskState = state.tasks[taskName];
786
+ return taskState?.status === TASK_STATUS.COMPLETED || isNonActiveTask(taskState);
787
+ });
788
+ return {
789
+ isComplete: allDone,
790
+ expectedCompletion: { taskNames: allTaskNames, outputs: [] }
791
+ };
792
+ }
793
+ function checkAllOutputsDone(graph, state) {
794
+ const graphTasks = getAllTasks(graph);
795
+ const allPossibleOutputs = /* @__PURE__ */ new Set();
796
+ for (const taskConfig of Object.values(graphTasks)) {
797
+ getProvides(taskConfig).forEach((output) => allPossibleOutputs.add(output));
798
+ }
799
+ const availableOutputs = computeAvailableOutputs(graph, state.tasks);
800
+ const allProduced = [...allPossibleOutputs].every((output) => availableOutputs.includes(output));
801
+ return {
802
+ isComplete: allProduced,
803
+ expectedCompletion: { taskNames: [], outputs: [...allPossibleOutputs] }
804
+ };
805
+ }
806
+ function checkOnlyResolved(graph, state) {
807
+ const graphTasks = getAllTasks(graph);
808
+ const availableOutputs = computeAvailableOutputs(graph, state.tasks);
809
+ const allPossibleOutputs = /* @__PURE__ */ new Set();
810
+ const tasksByOutput = {};
811
+ for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
812
+ const provides = getProvides(taskConfig);
813
+ provides.forEach((output) => {
814
+ allPossibleOutputs.add(output);
815
+ if (!tasksByOutput[output]) tasksByOutput[output] = [];
816
+ tasksByOutput[output].push(taskName);
817
+ });
818
+ }
819
+ for (const output of allPossibleOutputs) {
820
+ if (availableOutputs.includes(output)) continue;
821
+ const producers = tasksByOutput[output] ?? [];
822
+ const canStillProduce = producers.some((taskName) => {
823
+ const taskState = state.tasks[taskName];
824
+ if (taskState?.status === TASK_STATUS.COMPLETED || isNonActiveTask(taskState)) {
825
+ return false;
826
+ }
827
+ const taskConfig = graphTasks[taskName];
828
+ const requires = getRequires(taskConfig);
829
+ return requires.every((req) => availableOutputs.includes(req));
830
+ });
831
+ if (canStillProduce) {
832
+ return { isComplete: false, expectedCompletion: { taskNames: [], outputs: [] } };
833
+ }
834
+ }
835
+ const eligibleTasks = getEligibleCandidates(graph, state);
836
+ if (eligibleTasks.length > 0) {
837
+ return { isComplete: false, expectedCompletion: { taskNames: eligibleTasks, outputs: [] } };
838
+ }
839
+ const completedCount = Object.values(state.tasks).filter((t) => t.status === TASK_STATUS.COMPLETED).length;
840
+ return {
841
+ isComplete: completedCount > 0 || availableOutputs.length > 0,
842
+ expectedCompletion: { taskNames: [], outputs: [] }
843
+ };
844
+ }
845
+ function checkGoalReached(graph, state) {
846
+ const goal = graph.settings.goal ?? [];
847
+ if (goal.length === 0) {
848
+ return checkAllOutputsDone(graph, state);
849
+ }
850
+ const availableOutputs = computeAvailableOutputs(graph, state.tasks);
851
+ const goalReached = goal.every((output) => availableOutputs.includes(output));
852
+ return {
853
+ isComplete: goalReached,
854
+ expectedCompletion: { taskNames: [], outputs: goal }
855
+ };
856
+ }
857
+ function getEligibleCandidates(graph, state) {
858
+ const graphTasks = getAllTasks(graph);
859
+ const availableOutputs = computeAvailableOutputs(graph, state.tasks);
860
+ const candidates = [];
861
+ for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
862
+ const taskState = state.tasks[taskName];
863
+ if (taskState?.status === TASK_STATUS.COMPLETED || taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
864
+ continue;
865
+ }
866
+ const requires = getRequires(taskConfig);
867
+ if (requires.every((req) => availableOutputs.includes(req))) {
868
+ const provides = getProvides(taskConfig);
869
+ const allAlreadyAvailable = provides.length > 0 && provides.every((output) => availableOutputs.includes(output));
870
+ if (!allAlreadyAvailable) {
871
+ candidates.push(taskName);
872
+ }
873
+ }
874
+ }
875
+ return candidates;
876
+ }
877
+
878
+ // src/event-graph/stuck-detection.ts
879
+ function detectStuckState(params) {
880
+ const { graph, state, eligibleTasks, completionResult } = params;
881
+ const tasks = state.tasks;
882
+ const graphTasks = getAllTasks(graph);
883
+ const availableOutputs = computeAvailableOutputs(graph, tasks);
884
+ if (eligibleTasks.length > 0) {
885
+ return { is_stuck: false, stuck_description: null, outputs_unresolvable: [], tasks_blocked: [] };
886
+ }
887
+ const hasRunningTasks = Object.values(tasks).some((t) => t.status === TASK_STATUS.RUNNING);
888
+ if (hasRunningTasks) {
889
+ return { is_stuck: false, stuck_description: null, outputs_unresolvable: [], tasks_blocked: [] };
890
+ }
891
+ if (completionResult?.expectedCompletion) {
892
+ const { taskNames = [], outputs = [] } = completionResult.expectedCompletion;
893
+ if (taskNames.length > 0) {
894
+ const expectedFailed = taskNames.filter((tn) => isNonActiveTask(tasks[tn]));
895
+ if (expectedFailed.length > 0 && expectedFailed.length === taskNames.length) {
896
+ return {
897
+ is_stuck: true,
898
+ stuck_description: `Completion expects tasks ${taskNames.join(", ")} but all are failed`,
899
+ tasks_blocked: expectedFailed,
900
+ outputs_unresolvable: outputs
901
+ };
902
+ }
903
+ }
904
+ if (outputs.length > 0 && state.executionConfig.completionStrategy !== "only-resolved") {
905
+ const missingOutputs2 = outputs.filter((o) => !availableOutputs.includes(o));
906
+ if (missingOutputs2.length > 0) {
907
+ const unprovidable = [];
908
+ for (const output of missingOutputs2) {
909
+ const providers = Object.entries(graphTasks).filter(([, config]) => getProvides(config).includes(output)).map(([name]) => name);
910
+ const viable = providers.filter((p) => !isNonActiveTask(tasks[p]));
911
+ if (viable.length === 0) {
912
+ unprovidable.push(output);
913
+ }
914
+ }
915
+ if (unprovidable.length > 0) {
916
+ return {
917
+ is_stuck: true,
918
+ stuck_description: `Completion expects outputs '${unprovidable.join("', '")}' but no viable tasks can provide them`,
919
+ tasks_blocked: [],
920
+ outputs_unresolvable: unprovidable
921
+ };
922
+ }
923
+ }
924
+ }
925
+ }
926
+ const blockedTasks = [];
927
+ const missingOutputs = /* @__PURE__ */ new Set();
928
+ for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
929
+ const taskState = tasks[taskName];
930
+ if (taskState?.status === TASK_STATUS.COMPLETED || isNonActiveTask(taskState) || taskState?.status === TASK_STATUS.RUNNING) {
931
+ continue;
932
+ }
933
+ const requires = getRequires(taskConfig);
934
+ const unmet = requires.filter((req) => !availableOutputs.includes(req));
935
+ if (unmet.length > 0) {
936
+ const canBeProvided = unmet.every((req) => {
937
+ const providers = Object.entries(graphTasks).filter(([, config]) => getProvides(config).includes(req)).map(([name]) => name);
938
+ return providers.some((p) => !isNonActiveTask(tasks[p]) && tasks[p]?.status !== TASK_STATUS.COMPLETED);
939
+ });
940
+ if (!canBeProvided) {
941
+ blockedTasks.push(taskName);
942
+ unmet.forEach((u) => missingOutputs.add(u));
943
+ }
944
+ }
945
+ }
946
+ if (blockedTasks.length > 0) {
947
+ return {
948
+ is_stuck: true,
949
+ stuck_description: `Tasks [${blockedTasks.join(", ")}] blocked by unresolvable dependencies: ${[...missingOutputs].join(", ")}`,
950
+ tasks_blocked: blockedTasks,
951
+ outputs_unresolvable: [...missingOutputs]
952
+ };
953
+ }
954
+ return { is_stuck: false, stuck_description: null, outputs_unresolvable: [], tasks_blocked: [] };
955
+ }
956
+
957
+ // src/event-graph/scheduler.ts
958
+ function next(graph, state) {
959
+ const processingLog = [];
960
+ const graphTasks = getAllTasks(graph);
961
+ if (Object.keys(graphTasks).length === 0) {
962
+ return {
963
+ eligibleTasks: [],
964
+ isComplete: true,
965
+ stuckDetection: { is_stuck: false, stuck_description: null, outputs_unresolvable: [], tasks_blocked: [] },
966
+ hasConflicts: false,
967
+ conflicts: {},
968
+ strategy: state.executionConfig.conflictStrategy,
969
+ processingLog: ["No tasks defined"]
970
+ };
971
+ }
972
+ const mode = state.executionConfig.executionMode;
973
+ const conflictStrategy = state.executionConfig.conflictStrategy;
974
+ const candidates = getCandidateTasks(graph, state);
975
+ processingLog.push(`Found ${candidates.length} candidate tasks: ${candidates.join(", ") || "none"}`);
976
+ let eligible;
977
+ let hasConflicts = false;
978
+ let conflicts = {};
979
+ if (mode === "dependency-mode") {
980
+ eligible = candidates;
981
+ } else {
982
+ const selection = selectOptimalTasks(candidates, graph, state, conflictStrategy);
983
+ eligible = selection.eligibleTasks;
984
+ hasConflicts = selection.hasConflicts;
985
+ conflicts = selection.conflicts;
986
+ }
987
+ processingLog.push(`Eligible after conflict resolution: ${eligible.join(", ") || "none"}`);
988
+ const completionResult = isExecutionComplete(graph, state);
989
+ processingLog.push(`Execution complete: ${completionResult.isComplete}`);
990
+ const stuckDetection = detectStuckState({
991
+ graph,
992
+ state,
993
+ eligibleTasks: eligible,
994
+ completionResult
995
+ });
996
+ if (stuckDetection.is_stuck) {
997
+ processingLog.push(`STUCK: ${stuckDetection.stuck_description}`);
998
+ }
999
+ return {
1000
+ eligibleTasks: eligible,
1001
+ isComplete: completionResult.isComplete,
1002
+ stuckDetection,
1003
+ hasConflicts,
1004
+ conflicts,
1005
+ strategy: conflictStrategy,
1006
+ processingLog
1007
+ };
1008
+ }
1009
+ function getCandidateTasks(graph, state) {
1010
+ const graphTasks = getAllTasks(graph);
1011
+ const computedOutputs = computeAvailableOutputs(graph, state.tasks);
1012
+ const availableOutputs = [.../* @__PURE__ */ new Set([...computedOutputs, ...state.availableOutputs])];
1013
+ const candidates = [];
1014
+ for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
1015
+ const taskState = state.tasks[taskName];
1016
+ if (!isRepeatableTask(taskConfig)) {
1017
+ if (taskState?.status === TASK_STATUS.COMPLETED || taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
1018
+ continue;
1019
+ }
1020
+ } else {
1021
+ if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
1022
+ continue;
1023
+ }
1024
+ const maxExec = getRepeatableMax(taskConfig);
1025
+ if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
1026
+ continue;
1027
+ }
1028
+ if (taskConfig.circuit_breaker) {
1029
+ if (taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
1030
+ continue;
1031
+ }
1032
+ }
1033
+ if (taskState?.status === TASK_STATUS.COMPLETED) {
1034
+ const requires2 = getRequires(taskConfig);
1035
+ if (requires2.length > 0) {
1036
+ const hasRefreshedInputs = requires2.some((req) => {
1037
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
1038
+ if (getProvides(otherConfig).includes(req)) {
1039
+ const otherState = state.tasks[otherName];
1040
+ if (otherState && otherState.executionCount > taskState.lastEpoch) {
1041
+ return true;
1042
+ }
1043
+ }
1044
+ }
1045
+ return false;
1046
+ });
1047
+ if (!hasRefreshedInputs) continue;
1048
+ } else {
1049
+ continue;
1050
+ }
1051
+ }
1052
+ }
1053
+ const requires = getRequires(taskConfig);
1054
+ if (!requires.every((req) => availableOutputs.includes(req))) {
1055
+ continue;
1056
+ }
1057
+ if (!isRepeatableTask(taskConfig)) {
1058
+ const provides = getProvides(taskConfig);
1059
+ const allAlreadyAvailable = provides.length > 0 && provides.every((output) => availableOutputs.includes(output));
1060
+ if (allAlreadyAvailable) continue;
1061
+ }
1062
+ candidates.push(taskName);
1063
+ }
1064
+ return candidates;
1065
+ }
1066
+ function selectOptimalTasks(candidates, graph, state, conflictStrategy) {
1067
+ const result = { eligibleTasks: [], hasConflicts: false, conflicts: {} };
1068
+ if (candidates.length === 0) return result;
1069
+ const graphTasks = getAllTasks(graph);
1070
+ switch (conflictStrategy) {
1071
+ case "parallel-all":
1072
+ result.eligibleTasks = candidates;
1073
+ return result;
1074
+ case "user-choice": {
1075
+ result.eligibleTasks = candidates;
1076
+ if (candidates.length > 1) {
1077
+ const outputGroups2 = groupTasksByProvides(candidates, graphTasks);
1078
+ for (const [outputKey, groupTasks] of Object.entries(outputGroups2)) {
1079
+ if (groupTasks.length > 1) {
1080
+ result.conflicts[outputKey] = groupTasks;
1081
+ result.hasConflicts = true;
1082
+ }
1083
+ }
1084
+ }
1085
+ return result;
1086
+ }
1087
+ case "skip-conflicts":
1088
+ result.eligibleTasks = getNonConflictingTasks(candidates, graphTasks);
1089
+ return result;
1090
+ case "random-select":
1091
+ result.eligibleTasks = selectRandomTasks(candidates, graphTasks);
1092
+ return result;
1093
+ }
1094
+ const outputGroups = groupTasksByProvides(candidates, graphTasks);
1095
+ const runningOutputs = /* @__PURE__ */ new Set();
1096
+ for (const [taskName, taskState] of Object.entries(state.tasks)) {
1097
+ if (taskState.status === TASK_STATUS.RUNNING) {
1098
+ const taskConfig = graph.tasks[taskName];
1099
+ if (taskConfig) {
1100
+ getProvides(taskConfig).forEach((o) => runningOutputs.add(o));
1101
+ }
1102
+ }
1103
+ }
1104
+ const selectedTasks = [];
1105
+ const tasksInConflictGroups = /* @__PURE__ */ new Set();
1106
+ for (const [outputKey, groupTasks] of Object.entries(outputGroups)) {
1107
+ if (runningOutputs.has(outputKey)) continue;
1108
+ if (groupTasks.length === 1) {
1109
+ selectedTasks.push(groupTasks[0]);
1110
+ } else {
1111
+ const selected = selectBestAlternative(groupTasks, graphTasks, state, conflictStrategy);
1112
+ selectedTasks.push(selected);
1113
+ }
1114
+ groupTasks.forEach((t) => tasksInConflictGroups.add(t));
1115
+ }
1116
+ const nonConflicting = candidates.filter((t) => !tasksInConflictGroups.has(t));
1117
+ nonConflicting.forEach((t) => {
1118
+ if (!selectedTasks.includes(t)) selectedTasks.push(t);
1119
+ });
1120
+ result.eligibleTasks = selectedTasks;
1121
+ return result;
1122
+ }
1123
+
1124
+ // src/event-graph/task-transitions.ts
1125
+ function applyTaskStart(state, taskName) {
1126
+ const existingTask = state.tasks[taskName] ?? createDefaultTaskState2();
1127
+ const updatedTask = {
1128
+ ...existingTask,
1129
+ status: "running",
1130
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
1131
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1132
+ progress: 0,
1133
+ error: void 0
1134
+ };
1135
+ return {
1136
+ ...state,
1137
+ tasks: { ...state.tasks, [taskName]: updatedTask },
1138
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1139
+ };
1140
+ }
1141
+ function applyTaskCompletion(state, graph, taskName, result) {
1142
+ const existingTask = state.tasks[taskName] ?? createDefaultTaskState2();
1143
+ const taskConfig = graph.tasks[taskName];
1144
+ if (!taskConfig) {
1145
+ throw new Error(`Task "${taskName}" not found in graph`);
1146
+ }
1147
+ let outputTokens;
1148
+ if (result && taskConfig.on && taskConfig.on[result]) {
1149
+ outputTokens = taskConfig.on[result];
1150
+ } else {
1151
+ outputTokens = getProvides(taskConfig);
1152
+ }
1153
+ const updatedTask = {
1154
+ ...existingTask,
1155
+ status: "completed",
1156
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1157
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1158
+ executionCount: existingTask.executionCount + 1,
1159
+ lastEpoch: existingTask.executionCount + 1,
1160
+ error: void 0
1161
+ };
1162
+ if (isRepeatableTask(taskConfig)) {
1163
+ updatedTask.status = "not-started";
1164
+ }
1165
+ const newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...outputTokens])];
1166
+ return {
1167
+ ...state,
1168
+ tasks: { ...state.tasks, [taskName]: updatedTask },
1169
+ availableOutputs: newOutputs,
1170
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1171
+ };
1172
+ }
1173
+ function applyTaskFailure(state, graph, taskName, error) {
1174
+ const existingTask = state.tasks[taskName] ?? createDefaultTaskState2();
1175
+ const taskConfig = graph.tasks[taskName];
1176
+ if (taskConfig?.retry) {
1177
+ const retryCount = existingTask.retryCount + 1;
1178
+ if (retryCount <= taskConfig.retry.max_attempts) {
1179
+ const updatedTask2 = {
1180
+ ...existingTask,
1181
+ status: "not-started",
1182
+ retryCount,
1183
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1184
+ error
1185
+ };
1186
+ return {
1187
+ ...state,
1188
+ tasks: { ...state.tasks, [taskName]: updatedTask2 },
1189
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1190
+ };
1191
+ }
1192
+ }
1193
+ const updatedTask = {
1194
+ ...existingTask,
1195
+ status: "failed",
1196
+ failedAt: (/* @__PURE__ */ new Date()).toISOString(),
1197
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
1198
+ error,
1199
+ executionCount: existingTask.executionCount + 1
1200
+ };
1201
+ let newOutputs = state.availableOutputs;
1202
+ if (taskConfig?.on_failure && taskConfig.on_failure.length > 0) {
1203
+ newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...taskConfig.on_failure])];
1204
+ }
1205
+ if (taskConfig?.circuit_breaker && updatedTask.executionCount >= taskConfig.circuit_breaker.max_executions) {
1206
+ const breakTokens = taskConfig.circuit_breaker.on_break;
1207
+ newOutputs = [.../* @__PURE__ */ new Set([...newOutputs, ...breakTokens])];
1208
+ }
1209
+ return {
1210
+ ...state,
1211
+ tasks: { ...state.tasks, [taskName]: updatedTask },
1212
+ availableOutputs: newOutputs,
1213
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1214
+ };
1215
+ }
1216
+ function applyTaskProgress(state, taskName, message, progress) {
1217
+ const existingTask = state.tasks[taskName] ?? createDefaultTaskState2();
1218
+ const updatedTask = {
1219
+ ...existingTask,
1220
+ progress: typeof progress === "number" ? progress : existingTask.progress,
1221
+ messages: [
1222
+ ...existingTask.messages ?? [],
1223
+ ...message ? [{ message, timestamp: (/* @__PURE__ */ new Date()).toISOString(), status: existingTask.status }] : []
1224
+ ],
1225
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1226
+ };
1227
+ return {
1228
+ ...state,
1229
+ tasks: { ...state.tasks, [taskName]: updatedTask },
1230
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1231
+ };
1232
+ }
1233
+ function createDefaultTaskState2() {
1234
+ return {
1235
+ status: "not-started",
1236
+ executionCount: 0,
1237
+ retryCount: 0,
1238
+ lastEpoch: 0,
1239
+ messages: [],
1240
+ progress: null
1241
+ };
1242
+ }
1243
+
1244
+ // src/event-graph/reducer.ts
1245
+ function apply(state, event, graph) {
1246
+ if ("executionId" in event && event.executionId && event.executionId !== state.executionId) {
1247
+ return state;
1248
+ }
1249
+ switch (event.type) {
1250
+ case "task-started":
1251
+ return applyTaskStart(state, event.taskName);
1252
+ case "task-completed":
1253
+ return applyTaskCompletion(state, graph, event.taskName, event.result);
1254
+ case "task-failed":
1255
+ return applyTaskFailure(state, graph, event.taskName, event.error);
1256
+ case "task-progress":
1257
+ return applyTaskProgress(state, event.taskName, event.message, event.progress);
1258
+ case "inject-tokens":
1259
+ return applyInjectTokens(state, event.tokens);
1260
+ case "agent-action":
1261
+ return applyAgentAction(state, event.action, graph, event.config);
1262
+ case "task-creation":
1263
+ return applyTaskCreation(state, event.taskName, event.taskConfig);
1264
+ default:
1265
+ return state;
1266
+ }
1267
+ }
1268
+ function applyAll(state, events, graph) {
1269
+ return events.reduce((s, e) => apply(s, e, graph), state);
1270
+ }
1271
+ function applyInjectTokens(state, tokens) {
1272
+ return {
1273
+ ...state,
1274
+ availableOutputs: [.../* @__PURE__ */ new Set([...state.availableOutputs, ...tokens])],
1275
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1276
+ };
1277
+ }
1278
+ function applyAgentAction(state, action, graph, config) {
1279
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1280
+ switch (action) {
1281
+ case "start": {
1282
+ const executionId = `exec-${Date.now()}`;
1283
+ const fresh = createInitialExecutionState(graph, executionId);
1284
+ if (config) {
1285
+ if (config.executionMode) {
1286
+ fresh.executionConfig.executionMode = config.executionMode;
1287
+ }
1288
+ if (config.conflictStrategy) {
1289
+ fresh.executionConfig.conflictStrategy = config.conflictStrategy;
1290
+ }
1291
+ if (config.completionStrategy) {
1292
+ fresh.executionConfig.completionStrategy = config.completionStrategy;
1293
+ }
1294
+ }
1295
+ return fresh;
1296
+ }
1297
+ case "stop":
1298
+ return {
1299
+ ...state,
1300
+ status: "stopped",
1301
+ executionId: null,
1302
+ lastUpdated: now
1303
+ };
1304
+ case "pause":
1305
+ return {
1306
+ ...state,
1307
+ status: "paused",
1308
+ lastUpdated: now
1309
+ };
1310
+ case "resume":
1311
+ return {
1312
+ ...state,
1313
+ status: "running",
1314
+ lastUpdated: now
1315
+ };
1316
+ default:
1317
+ return state;
1318
+ }
1319
+ }
1320
+ function applyTaskCreation(state, taskName, taskConfig) {
1321
+ if (!taskName || !taskConfig || !Array.isArray(taskConfig.provides)) {
1322
+ return state;
1323
+ }
1324
+ return {
1325
+ ...state,
1326
+ tasks: {
1327
+ ...state.tasks,
1328
+ [taskName]: createDefaultTaskState()
1329
+ },
1330
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
1331
+ };
1332
+ }
1333
+
547
1334
  // src/stores/localStorage.ts
548
1335
  var LocalStorageStore = class {
549
1336
  prefix;
@@ -726,6 +1513,184 @@ var FileStore = class {
726
1513
  }
727
1514
  };
728
1515
 
729
- export { FileStore, FlowEngine, LocalStorageStore, MemoryStore, createEngine, loadFlow, loadFlowFromFile, loadFlowFromUrl, parseYaml, validateFlowConfig };
1516
+ // src/batch/runner.ts
1517
+ async function batch(items, options) {
1518
+ const {
1519
+ concurrency = 5,
1520
+ processor,
1521
+ onItemComplete,
1522
+ onItemError,
1523
+ onProgress,
1524
+ signal
1525
+ } = options;
1526
+ const total = items.length;
1527
+ const results = new Array(total);
1528
+ const batchStart = Date.now();
1529
+ let completed = 0;
1530
+ let failed = 0;
1531
+ let nextIndex = 0;
1532
+ function makeProgress(active) {
1533
+ const done = completed + failed;
1534
+ return {
1535
+ completed,
1536
+ failed,
1537
+ active,
1538
+ pending: total - done - active,
1539
+ total,
1540
+ percent: total === 0 ? 100 : Math.round(done / total * 100),
1541
+ elapsedMs: Date.now() - batchStart
1542
+ };
1543
+ }
1544
+ if (total === 0) {
1545
+ return { items: [], completed: 0, failed: 0, total: 0, durationMs: 0 };
1546
+ }
1547
+ return new Promise((resolve) => {
1548
+ let active = 0;
1549
+ function tryStartNext() {
1550
+ while (active < concurrency && nextIndex < total) {
1551
+ if (signal?.aborted) {
1552
+ while (nextIndex < total) {
1553
+ const idx2 = nextIndex++;
1554
+ results[idx2] = {
1555
+ item: items[idx2],
1556
+ index: idx2,
1557
+ status: "failed",
1558
+ error: new Error("Batch aborted"),
1559
+ durationMs: 0
1560
+ };
1561
+ failed++;
1562
+ }
1563
+ if (active === 0 && completed + failed === total) {
1564
+ resolve({
1565
+ items: results,
1566
+ completed,
1567
+ failed,
1568
+ total,
1569
+ durationMs: Date.now() - batchStart
1570
+ });
1571
+ }
1572
+ break;
1573
+ }
1574
+ const idx = nextIndex++;
1575
+ const item = items[idx];
1576
+ active++;
1577
+ const itemStart = Date.now();
1578
+ processor(item, idx).then((result) => {
1579
+ results[idx] = {
1580
+ item,
1581
+ index: idx,
1582
+ status: "completed",
1583
+ result,
1584
+ durationMs: Date.now() - itemStart
1585
+ };
1586
+ completed++;
1587
+ onItemComplete?.(item, result, idx);
1588
+ }).catch((err) => {
1589
+ const error = err instanceof Error ? err : new Error(String(err));
1590
+ results[idx] = {
1591
+ item,
1592
+ index: idx,
1593
+ status: "failed",
1594
+ error,
1595
+ durationMs: Date.now() - itemStart
1596
+ };
1597
+ failed++;
1598
+ onItemError?.(item, error, idx);
1599
+ }).finally(() => {
1600
+ active--;
1601
+ onProgress?.(makeProgress(active));
1602
+ if (completed + failed === total) {
1603
+ resolve({
1604
+ items: results,
1605
+ completed,
1606
+ failed,
1607
+ total,
1608
+ durationMs: Date.now() - batchStart
1609
+ });
1610
+ } else {
1611
+ tryStartNext();
1612
+ }
1613
+ });
1614
+ }
1615
+ }
1616
+ tryStartNext();
1617
+ });
1618
+ }
1619
+
1620
+ // src/config/resolve-variables.ts
1621
+ function interpolateString(template, vars) {
1622
+ return template.replace(/\$\{([^}]+)\}/g, (match, key) => {
1623
+ const value = vars[key.trim()];
1624
+ return value !== void 0 ? String(value) : match;
1625
+ });
1626
+ }
1627
+ function walkAndInterpolate(value, vars) {
1628
+ if (typeof value === "string") {
1629
+ return interpolateString(value, vars);
1630
+ }
1631
+ if (Array.isArray(value)) {
1632
+ return value.map((item) => walkAndInterpolate(item, vars));
1633
+ }
1634
+ if (value !== null && typeof value === "object") {
1635
+ const result = {};
1636
+ for (const [k, v] of Object.entries(value)) {
1637
+ result[k] = walkAndInterpolate(v, vars);
1638
+ }
1639
+ return result;
1640
+ }
1641
+ return value;
1642
+ }
1643
+ function resolveVariables(config, variables) {
1644
+ return walkAndInterpolate(config, variables);
1645
+ }
1646
+
1647
+ // src/config/resolve-config-templates.ts
1648
+ function mergeConfigs(template, taskConfig) {
1649
+ const merged = { ...template };
1650
+ for (const [key, value] of Object.entries(taskConfig)) {
1651
+ if (key === "config-template") continue;
1652
+ if (value !== null && typeof value === "object" && !Array.isArray(value) && merged[key] !== null && typeof merged[key] === "object" && !Array.isArray(merged[key])) {
1653
+ merged[key] = {
1654
+ ...merged[key],
1655
+ ...value
1656
+ };
1657
+ } else {
1658
+ merged[key] = value;
1659
+ }
1660
+ }
1661
+ return merged;
1662
+ }
1663
+ function resolveConfigTemplates(config) {
1664
+ const templates = config["configTemplates"] ?? config["config-templates"] ?? {};
1665
+ const tasksKey = "tasks" in config ? "tasks" : "steps" in config ? "steps" : null;
1666
+ if (!tasksKey) return config;
1667
+ const tasks = config[tasksKey];
1668
+ if (!tasks || typeof tasks !== "object") return config;
1669
+ const resolvedTasks = {};
1670
+ for (const [name, task] of Object.entries(tasks)) {
1671
+ const taskConfig = task["config"];
1672
+ const templateName = taskConfig?.["config-template"];
1673
+ if (!templateName || !taskConfig) {
1674
+ resolvedTasks[name] = task;
1675
+ continue;
1676
+ }
1677
+ const template = templates[templateName];
1678
+ if (!template) {
1679
+ const { "config-template": _, ...rest } = taskConfig;
1680
+ resolvedTasks[name] = { ...task, config: rest };
1681
+ continue;
1682
+ }
1683
+ resolvedTasks[name] = {
1684
+ ...task,
1685
+ config: mergeConfigs(template, taskConfig)
1686
+ };
1687
+ }
1688
+ const result = { ...config, [tasksKey]: resolvedTasks };
1689
+ delete result["configTemplates"];
1690
+ delete result["config-templates"];
1691
+ return result;
1692
+ }
1693
+
1694
+ export { COMPLETION_STRATEGIES, CONFLICT_STRATEGIES, DEFAULTS, EXECUTION_MODES, EXECUTION_STATUS, FileStore, StepMachine as FlowEngine, LocalStorageStore, MemoryStore, StepMachine, TASK_STATUS, addDynamicTask, apply, applyAll, applyStepResult, batch, checkCircuitBreaker, computeAvailableOutputs, computeStepInput, createDefaultTaskState, createStepMachine as createEngine, createInitialExecutionState, createInitialState, createStepMachine, detectStuckState, extractReturnData, getAllTasks, getCandidateTasks, getProvides, getRequires, getTask, hasTask, isExecutionComplete, isNonActiveTask, isRepeatableTask, isTaskCompleted, isTaskRunning, loadStepFlow, next, resolveConfigTemplates, resolveVariables, validateStepFlowConfig };
730
1695
  //# sourceMappingURL=index.js.map
731
1696
  //# sourceMappingURL=index.js.map