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