yaml-flow 2.2.0 → 2.4.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.
@@ -0,0 +1,916 @@
1
+ // src/event-graph/constants.ts
2
+ var TASK_STATUS = {
3
+ NOT_STARTED: "not-started",
4
+ RUNNING: "running",
5
+ COMPLETED: "completed",
6
+ FAILED: "failed",
7
+ INACTIVATED: "inactivated"
8
+ };
9
+
10
+ // src/event-graph/graph-helpers.ts
11
+ function getProvides(task) {
12
+ if (!task) return [];
13
+ if (Array.isArray(task.provides)) return task.provides;
14
+ return [];
15
+ }
16
+ function getRequires(task) {
17
+ if (!task) return [];
18
+ if (Array.isArray(task.requires)) return task.requires;
19
+ return [];
20
+ }
21
+ function getAllTasks(graph) {
22
+ return graph.tasks ?? {};
23
+ }
24
+ function isNonActiveTask(taskState) {
25
+ if (!taskState) return false;
26
+ return taskState.status === TASK_STATUS.FAILED || taskState.status === TASK_STATUS.INACTIVATED;
27
+ }
28
+ function isRepeatableTask(taskConfig) {
29
+ return taskConfig.repeatable === true || typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null;
30
+ }
31
+ function getRepeatableMax(taskConfig) {
32
+ if (taskConfig.repeatable === true) return void 0;
33
+ if (typeof taskConfig.repeatable === "object" && taskConfig.repeatable !== null) {
34
+ return taskConfig.repeatable.max;
35
+ }
36
+ return void 0;
37
+ }
38
+ function computeAvailableOutputs(graph, taskStates) {
39
+ const outputs = /* @__PURE__ */ new Set();
40
+ for (const [taskName, taskState] of Object.entries(taskStates)) {
41
+ if (taskState.status === TASK_STATUS.COMPLETED) {
42
+ const taskConfig = graph.tasks[taskName];
43
+ if (taskConfig) {
44
+ const provides = getProvides(taskConfig);
45
+ provides.forEach((output) => outputs.add(output));
46
+ }
47
+ }
48
+ }
49
+ return Array.from(outputs);
50
+ }
51
+ function groupTasksByProvides(candidateTaskNames, tasks) {
52
+ const outputGroups = {};
53
+ candidateTaskNames.forEach((taskName) => {
54
+ const task = tasks[taskName];
55
+ if (!task) return;
56
+ const provides = getProvides(task);
57
+ provides.forEach((output) => {
58
+ if (!outputGroups[output]) {
59
+ outputGroups[output] = [];
60
+ }
61
+ outputGroups[output].push(taskName);
62
+ });
63
+ });
64
+ return outputGroups;
65
+ }
66
+
67
+ // src/event-graph/task-transitions.ts
68
+ function applyTaskStart(state, taskName) {
69
+ const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
70
+ const updatedTask = {
71
+ ...existingTask,
72
+ status: "running",
73
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
74
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
75
+ progress: 0,
76
+ error: void 0
77
+ };
78
+ return {
79
+ ...state,
80
+ tasks: { ...state.tasks, [taskName]: updatedTask },
81
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
82
+ };
83
+ }
84
+ function applyTaskCompletion(state, graph, taskName, result) {
85
+ const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
86
+ const taskConfig = graph.tasks[taskName];
87
+ if (!taskConfig) {
88
+ throw new Error(`Task "${taskName}" not found in graph`);
89
+ }
90
+ let outputTokens;
91
+ if (result && taskConfig.on && taskConfig.on[result]) {
92
+ outputTokens = taskConfig.on[result];
93
+ } else {
94
+ outputTokens = getProvides(taskConfig);
95
+ }
96
+ const updatedTask = {
97
+ ...existingTask,
98
+ status: "completed",
99
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
100
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
101
+ executionCount: existingTask.executionCount + 1,
102
+ lastEpoch: existingTask.executionCount + 1,
103
+ error: void 0
104
+ };
105
+ if (isRepeatableTask(taskConfig)) {
106
+ updatedTask.status = "not-started";
107
+ }
108
+ const newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...outputTokens])];
109
+ return {
110
+ ...state,
111
+ tasks: { ...state.tasks, [taskName]: updatedTask },
112
+ availableOutputs: newOutputs,
113
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
114
+ };
115
+ }
116
+ function applyTaskFailure(state, graph, taskName, error) {
117
+ const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
118
+ const taskConfig = graph.tasks[taskName];
119
+ if (taskConfig?.retry) {
120
+ const retryCount = existingTask.retryCount + 1;
121
+ if (retryCount <= taskConfig.retry.max_attempts) {
122
+ const updatedTask2 = {
123
+ ...existingTask,
124
+ status: "not-started",
125
+ retryCount,
126
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
127
+ error
128
+ };
129
+ return {
130
+ ...state,
131
+ tasks: { ...state.tasks, [taskName]: updatedTask2 },
132
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
133
+ };
134
+ }
135
+ }
136
+ const updatedTask = {
137
+ ...existingTask,
138
+ status: "failed",
139
+ failedAt: (/* @__PURE__ */ new Date()).toISOString(),
140
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
141
+ error,
142
+ executionCount: existingTask.executionCount + 1
143
+ };
144
+ let newOutputs = state.availableOutputs;
145
+ if (taskConfig?.on_failure && taskConfig.on_failure.length > 0) {
146
+ newOutputs = [.../* @__PURE__ */ new Set([...state.availableOutputs, ...taskConfig.on_failure])];
147
+ }
148
+ if (taskConfig?.circuit_breaker && updatedTask.executionCount >= taskConfig.circuit_breaker.max_executions) {
149
+ const breakTokens = taskConfig.circuit_breaker.on_break;
150
+ newOutputs = [.../* @__PURE__ */ new Set([...newOutputs, ...breakTokens])];
151
+ }
152
+ return {
153
+ ...state,
154
+ tasks: { ...state.tasks, [taskName]: updatedTask },
155
+ availableOutputs: newOutputs,
156
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
157
+ };
158
+ }
159
+ function applyTaskProgress(state, taskName, message, progress) {
160
+ const existingTask = state.tasks[taskName] ?? createDefaultTaskState();
161
+ const updatedTask = {
162
+ ...existingTask,
163
+ progress: typeof progress === "number" ? progress : existingTask.progress,
164
+ messages: [
165
+ ...existingTask.messages ?? [],
166
+ ...message ? [{ message, timestamp: (/* @__PURE__ */ new Date()).toISOString(), status: existingTask.status }] : []
167
+ ],
168
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
169
+ };
170
+ return {
171
+ ...state,
172
+ tasks: { ...state.tasks, [taskName]: updatedTask },
173
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
174
+ };
175
+ }
176
+ function createDefaultTaskState() {
177
+ return {
178
+ status: "not-started",
179
+ executionCount: 0,
180
+ retryCount: 0,
181
+ lastEpoch: 0,
182
+ messages: [],
183
+ progress: null
184
+ };
185
+ }
186
+
187
+ // src/continuous-event-graph/core.ts
188
+ function createLiveGraph(config, executionId) {
189
+ const id = executionId ?? `live-${Date.now()}`;
190
+ const tasks = {};
191
+ for (const taskName of Object.keys(config.tasks)) {
192
+ tasks[taskName] = createDefaultTaskState2();
193
+ }
194
+ const state = {
195
+ status: "running",
196
+ tasks,
197
+ availableOutputs: [],
198
+ stuckDetection: { is_stuck: false, stuck_description: null, outputs_unresolvable: [], tasks_blocked: [] },
199
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
200
+ executionId: id,
201
+ executionConfig: {
202
+ executionMode: config.settings.execution_mode ?? "eligibility-mode",
203
+ conflictStrategy: config.settings.conflict_strategy ?? "alphabetical",
204
+ completionStrategy: config.settings.completion
205
+ }
206
+ };
207
+ return { config, state };
208
+ }
209
+ function applyEvent(live, event) {
210
+ const { config, state } = live;
211
+ if ("executionId" in event && event.executionId && event.executionId !== state.executionId) {
212
+ return live;
213
+ }
214
+ let newState;
215
+ switch (event.type) {
216
+ case "task-started":
217
+ newState = applyTaskStart(state, event.taskName);
218
+ break;
219
+ case "task-completed":
220
+ newState = applyTaskCompletion(state, config, event.taskName, event.result);
221
+ break;
222
+ case "task-failed":
223
+ newState = applyTaskFailure(state, config, event.taskName, event.error);
224
+ break;
225
+ case "task-progress":
226
+ newState = applyTaskProgress(state, event.taskName, event.message, event.progress);
227
+ break;
228
+ case "inject-tokens":
229
+ newState = {
230
+ ...state,
231
+ availableOutputs: [.../* @__PURE__ */ new Set([...state.availableOutputs, ...event.tokens])],
232
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
233
+ };
234
+ break;
235
+ case "agent-action":
236
+ newState = applyAgentAction(state, event.action);
237
+ break;
238
+ default:
239
+ return live;
240
+ }
241
+ return { config, state: newState };
242
+ }
243
+ function addNode(live, name, taskConfig) {
244
+ if (live.config.tasks[name]) return live;
245
+ return {
246
+ config: {
247
+ ...live.config,
248
+ tasks: { ...live.config.tasks, [name]: taskConfig }
249
+ },
250
+ state: {
251
+ ...live.state,
252
+ tasks: { ...live.state.tasks, [name]: createDefaultTaskState2() },
253
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
254
+ }
255
+ };
256
+ }
257
+ function removeNode(live, name) {
258
+ if (!live.config.tasks[name]) return live;
259
+ const { [name]: _removedConfig, ...remainingTasks } = live.config.tasks;
260
+ const { [name]: _removedState, ...remainingStates } = live.state.tasks;
261
+ return {
262
+ config: {
263
+ ...live.config,
264
+ tasks: remainingTasks
265
+ },
266
+ state: {
267
+ ...live.state,
268
+ tasks: remainingStates,
269
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
270
+ }
271
+ };
272
+ }
273
+ function addRequires(live, nodeName, tokens) {
274
+ const task = live.config.tasks[nodeName];
275
+ if (!task) return live;
276
+ const current = getRequires(task);
277
+ const toAdd = tokens.filter((t) => !current.includes(t));
278
+ if (toAdd.length === 0) return live;
279
+ return {
280
+ config: {
281
+ ...live.config,
282
+ tasks: {
283
+ ...live.config.tasks,
284
+ [nodeName]: { ...task, requires: [...current, ...toAdd] }
285
+ }
286
+ },
287
+ state: live.state
288
+ };
289
+ }
290
+ function removeRequires(live, nodeName, tokens) {
291
+ const task = live.config.tasks[nodeName];
292
+ if (!task) return live;
293
+ const current = getRequires(task);
294
+ const remaining = current.filter((t) => !tokens.includes(t));
295
+ if (remaining.length === current.length) return live;
296
+ return {
297
+ config: {
298
+ ...live.config,
299
+ tasks: {
300
+ ...live.config.tasks,
301
+ [nodeName]: { ...task, requires: remaining }
302
+ }
303
+ },
304
+ state: live.state
305
+ };
306
+ }
307
+ function addProvides(live, nodeName, tokens) {
308
+ const task = live.config.tasks[nodeName];
309
+ if (!task) return live;
310
+ const current = getProvides(task);
311
+ const toAdd = tokens.filter((t) => !current.includes(t));
312
+ if (toAdd.length === 0) return live;
313
+ return {
314
+ config: {
315
+ ...live.config,
316
+ tasks: {
317
+ ...live.config.tasks,
318
+ [nodeName]: { ...task, provides: [...current, ...toAdd] }
319
+ }
320
+ },
321
+ state: live.state
322
+ };
323
+ }
324
+ function removeProvides(live, nodeName, tokens) {
325
+ const task = live.config.tasks[nodeName];
326
+ if (!task) return live;
327
+ const current = getProvides(task);
328
+ const remaining = current.filter((t) => !tokens.includes(t));
329
+ if (remaining.length === current.length) return live;
330
+ return {
331
+ config: {
332
+ ...live.config,
333
+ tasks: {
334
+ ...live.config.tasks,
335
+ [nodeName]: { ...task, provides: remaining }
336
+ }
337
+ },
338
+ state: live.state
339
+ };
340
+ }
341
+ function injectTokens(live, tokens) {
342
+ return applyEvent(live, {
343
+ type: "inject-tokens",
344
+ tokens,
345
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
346
+ });
347
+ }
348
+ function drainTokens(live, tokens) {
349
+ const toRemove = new Set(tokens);
350
+ const remaining = live.state.availableOutputs.filter((t) => !toRemove.has(t));
351
+ if (remaining.length === live.state.availableOutputs.length) return live;
352
+ return {
353
+ config: live.config,
354
+ state: {
355
+ ...live.state,
356
+ availableOutputs: remaining,
357
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
358
+ }
359
+ };
360
+ }
361
+ function resetNode(live, name) {
362
+ if (!live.config.tasks[name] || !live.state.tasks[name]) return live;
363
+ return {
364
+ config: live.config,
365
+ state: {
366
+ ...live.state,
367
+ tasks: {
368
+ ...live.state.tasks,
369
+ [name]: createDefaultTaskState2()
370
+ },
371
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
372
+ }
373
+ };
374
+ }
375
+ function disableNode(live, name) {
376
+ const taskState = live.state.tasks[name];
377
+ if (!taskState || taskState.status === "inactivated") return live;
378
+ return {
379
+ config: live.config,
380
+ state: {
381
+ ...live.state,
382
+ tasks: {
383
+ ...live.state.tasks,
384
+ [name]: { ...taskState, status: "inactivated", lastUpdated: (/* @__PURE__ */ new Date()).toISOString() }
385
+ },
386
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
387
+ }
388
+ };
389
+ }
390
+ function enableNode(live, name) {
391
+ const taskState = live.state.tasks[name];
392
+ if (!taskState || taskState.status !== "inactivated") return live;
393
+ return {
394
+ config: live.config,
395
+ state: {
396
+ ...live.state,
397
+ tasks: {
398
+ ...live.state.tasks,
399
+ [name]: { ...taskState, status: "not-started", lastUpdated: (/* @__PURE__ */ new Date()).toISOString() }
400
+ },
401
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
402
+ }
403
+ };
404
+ }
405
+ function getNode(live, name) {
406
+ const config = live.config.tasks[name];
407
+ if (!config) return void 0;
408
+ const state = live.state.tasks[name] ?? createDefaultTaskState2();
409
+ return { name, config, state };
410
+ }
411
+ function snapshot(live) {
412
+ return {
413
+ version: 1,
414
+ config: live.config,
415
+ state: live.state,
416
+ snapshotAt: (/* @__PURE__ */ new Date()).toISOString()
417
+ };
418
+ }
419
+ function restore(data) {
420
+ if (!data || typeof data !== "object") {
421
+ throw new Error("Invalid snapshot: expected an object");
422
+ }
423
+ const snap = data;
424
+ if (!snap.config || typeof snap.config !== "object") {
425
+ throw new Error('Invalid snapshot: missing or invalid "config"');
426
+ }
427
+ if (!snap.state || typeof snap.state !== "object") {
428
+ throw new Error('Invalid snapshot: missing or invalid "state"');
429
+ }
430
+ const config = snap.config;
431
+ const state = snap.state;
432
+ if (!config.settings || typeof config.settings !== "object") {
433
+ throw new Error("Invalid snapshot: config.settings missing");
434
+ }
435
+ if (!config.tasks || typeof config.tasks !== "object") {
436
+ throw new Error("Invalid snapshot: config.tasks missing");
437
+ }
438
+ if (!state.tasks || typeof state.tasks !== "object") {
439
+ throw new Error("Invalid snapshot: state.tasks missing");
440
+ }
441
+ if (!Array.isArray(state.availableOutputs)) {
442
+ throw new Error("Invalid snapshot: state.availableOutputs must be an array");
443
+ }
444
+ return { config, state };
445
+ }
446
+ function createDefaultTaskState2() {
447
+ return {
448
+ status: "not-started",
449
+ executionCount: 0,
450
+ retryCount: 0,
451
+ lastEpoch: 0,
452
+ messages: [],
453
+ progress: null
454
+ };
455
+ }
456
+ function applyAgentAction(state, action) {
457
+ const now = (/* @__PURE__ */ new Date()).toISOString();
458
+ switch (action) {
459
+ case "stop":
460
+ return { ...state, status: "stopped", lastUpdated: now };
461
+ case "pause":
462
+ return { ...state, status: "paused", lastUpdated: now };
463
+ case "resume":
464
+ return { ...state, status: "running", lastUpdated: now };
465
+ default:
466
+ return state;
467
+ }
468
+ }
469
+
470
+ // src/continuous-event-graph/schedule.ts
471
+ function schedule(live) {
472
+ const { config, state } = live;
473
+ const graphTasks = getAllTasks(config);
474
+ const taskNames = Object.keys(graphTasks);
475
+ if (taskNames.length === 0) {
476
+ return { eligible: [], pending: [], unresolved: [], blocked: [], conflicts: {} };
477
+ }
478
+ const producerMap = buildProducerMap(graphTasks);
479
+ const computedOutputs = computeAvailableOutputs(config, state.tasks);
480
+ const availableOutputs = /* @__PURE__ */ new Set([...computedOutputs, ...state.availableOutputs]);
481
+ const eligible = [];
482
+ const pending = [];
483
+ const unresolved = [];
484
+ const blocked = [];
485
+ for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
486
+ const taskState = state.tasks[taskName];
487
+ if (!isRepeatableTask(taskConfig)) {
488
+ if (taskState?.status === TASK_STATUS.COMPLETED || taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
489
+ continue;
490
+ }
491
+ } else {
492
+ if (taskState?.status === TASK_STATUS.RUNNING || isNonActiveTask(taskState)) {
493
+ continue;
494
+ }
495
+ const maxExec = getRepeatableMax(taskConfig);
496
+ if (maxExec !== void 0 && taskState && taskState.executionCount >= maxExec) {
497
+ continue;
498
+ }
499
+ if (taskConfig.circuit_breaker && taskState && taskState.executionCount >= taskConfig.circuit_breaker.max_executions) {
500
+ continue;
501
+ }
502
+ if (taskState?.status === TASK_STATUS.COMPLETED) {
503
+ const requires2 = getRequires(taskConfig);
504
+ if (requires2.length > 0) {
505
+ const hasRefreshed = requires2.some((req) => {
506
+ for (const [otherName, otherConfig] of Object.entries(graphTasks)) {
507
+ if (getProvides(otherConfig).includes(req)) {
508
+ const otherState = state.tasks[otherName];
509
+ if (otherState && otherState.executionCount > taskState.lastEpoch) return true;
510
+ }
511
+ }
512
+ return false;
513
+ });
514
+ if (!hasRefreshed) continue;
515
+ } else {
516
+ continue;
517
+ }
518
+ }
519
+ }
520
+ const requires = getRequires(taskConfig);
521
+ if (requires.length === 0) {
522
+ eligible.push(taskName);
523
+ continue;
524
+ }
525
+ const missingTokens = [];
526
+ const pendingTokens = [];
527
+ const failedTokenInfo = [];
528
+ for (const token of requires) {
529
+ if (availableOutputs.has(token)) continue;
530
+ const producers = producerMap[token] || [];
531
+ if (producers.length === 0) {
532
+ missingTokens.push(token);
533
+ } else {
534
+ const allFailed = producers.every((p) => isNonActiveTask(state.tasks[p]));
535
+ if (allFailed) {
536
+ failedTokenInfo.push({ token, failedProducer: producers[0] });
537
+ } else {
538
+ pendingTokens.push(token);
539
+ }
540
+ }
541
+ }
542
+ if (missingTokens.length > 0) {
543
+ unresolved.push({ taskName, missingTokens });
544
+ } else if (failedTokenInfo.length > 0) {
545
+ blocked.push({
546
+ taskName,
547
+ failedTokens: failedTokenInfo.map((f) => f.token),
548
+ failedProducers: [...new Set(failedTokenInfo.map((f) => f.failedProducer))]
549
+ });
550
+ } else if (pendingTokens.length > 0) {
551
+ pending.push({ taskName, waitingOn: pendingTokens });
552
+ } else {
553
+ eligible.push(taskName);
554
+ }
555
+ }
556
+ const conflicts = {};
557
+ if (eligible.length > 1) {
558
+ const outputGroups = groupTasksByProvides(eligible, graphTasks);
559
+ for (const [outputKey, groupTasks] of Object.entries(outputGroups)) {
560
+ if (groupTasks.length > 1) {
561
+ conflicts[outputKey] = groupTasks;
562
+ }
563
+ }
564
+ }
565
+ return { eligible, pending, unresolved, blocked, conflicts };
566
+ }
567
+ function buildProducerMap(tasks) {
568
+ const map = {};
569
+ for (const [name, config] of Object.entries(tasks)) {
570
+ for (const token of getProvides(config)) {
571
+ if (!map[token]) map[token] = [];
572
+ map[token].push(name);
573
+ }
574
+ if (config.on) {
575
+ for (const tokens of Object.values(config.on)) {
576
+ for (const token of tokens) {
577
+ if (!map[token]) map[token] = [];
578
+ if (!map[token].includes(name)) map[token].push(name);
579
+ }
580
+ }
581
+ }
582
+ if (config.on_failure) {
583
+ for (const token of config.on_failure) {
584
+ if (!map[token]) map[token] = [];
585
+ if (!map[token].includes(name)) map[token].push(name);
586
+ }
587
+ }
588
+ }
589
+ return map;
590
+ }
591
+
592
+ // src/continuous-event-graph/inspect.ts
593
+ function inspect(live) {
594
+ const { config, state } = live;
595
+ const graphTasks = getAllTasks(config);
596
+ const taskNames = Object.keys(graphTasks);
597
+ let running = 0, completed = 0, failed = 0, waiting = 0, notStarted = 0, disabled = 0;
598
+ for (const taskName of taskNames) {
599
+ const ts = state.tasks[taskName];
600
+ if (!ts || ts.status === TASK_STATUS.NOT_STARTED) {
601
+ notStarted++;
602
+ } else {
603
+ switch (ts.status) {
604
+ case TASK_STATUS.RUNNING:
605
+ running++;
606
+ break;
607
+ case TASK_STATUS.COMPLETED:
608
+ completed++;
609
+ break;
610
+ case TASK_STATUS.FAILED:
611
+ failed++;
612
+ break;
613
+ case "inactivated":
614
+ disabled++;
615
+ break;
616
+ default:
617
+ waiting++;
618
+ }
619
+ }
620
+ }
621
+ const producerMap = {};
622
+ for (const [name, taskConfig] of Object.entries(graphTasks)) {
623
+ for (const token of getProvides(taskConfig)) {
624
+ if (!producerMap[token]) producerMap[token] = [];
625
+ producerMap[token].push(name);
626
+ }
627
+ if (taskConfig.on) {
628
+ for (const tokens of Object.values(taskConfig.on)) {
629
+ for (const token of tokens) {
630
+ if (!producerMap[token]) producerMap[token] = [];
631
+ if (!producerMap[token].includes(name)) producerMap[token].push(name);
632
+ }
633
+ }
634
+ }
635
+ if (taskConfig.on_failure) {
636
+ for (const token of taskConfig.on_failure) {
637
+ if (!producerMap[token]) producerMap[token] = [];
638
+ if (!producerMap[token].includes(name)) producerMap[token].push(name);
639
+ }
640
+ }
641
+ }
642
+ const openDeps = /* @__PURE__ */ new Set();
643
+ let unresolvedCount = 0;
644
+ let blockedCount = 0;
645
+ for (const [taskName, taskConfig] of Object.entries(graphTasks)) {
646
+ const ts = state.tasks[taskName];
647
+ if (ts?.status === TASK_STATUS.COMPLETED || ts?.status === TASK_STATUS.RUNNING) continue;
648
+ let hasOpen = false;
649
+ let hasBlocked = false;
650
+ for (const token of getRequires(taskConfig)) {
651
+ const producers = producerMap[token] || [];
652
+ if (producers.length === 0) {
653
+ openDeps.add(token);
654
+ hasOpen = true;
655
+ } else {
656
+ const allFailed = producers.every((p) => {
657
+ const ps = state.tasks[p];
658
+ return ps?.status === TASK_STATUS.FAILED || ps?.status === "inactivated";
659
+ });
660
+ if (allFailed) hasBlocked = true;
661
+ }
662
+ }
663
+ if (hasOpen) unresolvedCount++;
664
+ if (hasBlocked && !hasOpen) blockedCount++;
665
+ }
666
+ const conflictTokens = [];
667
+ for (const [token, producers] of Object.entries(producerMap)) {
668
+ if (producers.length > 1) conflictTokens.push(token);
669
+ }
670
+ const deps = buildTaskDeps(graphTasks, producerMap);
671
+ const cycles = detectCycles(taskNames, deps);
672
+ return {
673
+ totalNodes: taskNames.length,
674
+ running,
675
+ completed,
676
+ failed,
677
+ waiting,
678
+ notStarted,
679
+ disabled,
680
+ unresolvedCount,
681
+ blockedCount,
682
+ openDependencies: [...openDeps],
683
+ cycles,
684
+ conflictTokens
685
+ };
686
+ }
687
+ function buildTaskDeps(tasks, producerMap) {
688
+ const deps = {};
689
+ for (const [name, config] of Object.entries(tasks)) {
690
+ deps[name] = /* @__PURE__ */ new Set();
691
+ for (const token of getRequires(config)) {
692
+ for (const producer of producerMap[token] || []) {
693
+ if (producer !== name) deps[name].add(producer);
694
+ }
695
+ }
696
+ }
697
+ return deps;
698
+ }
699
+ function detectCycles(taskNames, deps) {
700
+ const WHITE = 0, GRAY = 1, BLACK = 2;
701
+ const color = {};
702
+ const parent = {};
703
+ const cycles = [];
704
+ for (const name of taskNames) {
705
+ color[name] = WHITE;
706
+ parent[name] = null;
707
+ }
708
+ function dfs(node) {
709
+ color[node] = GRAY;
710
+ for (const dep of deps[node] || []) {
711
+ if (color[dep] === GRAY) {
712
+ const cycle = [dep];
713
+ let cur = node;
714
+ while (cur !== dep) {
715
+ cycle.push(cur);
716
+ cur = parent[cur];
717
+ }
718
+ cycle.push(dep);
719
+ cycle.reverse();
720
+ cycles.push(cycle);
721
+ } else if (color[dep] === WHITE) {
722
+ parent[dep] = node;
723
+ dfs(dep);
724
+ }
725
+ }
726
+ color[node] = BLACK;
727
+ }
728
+ for (const name of taskNames) {
729
+ if (color[name] === WHITE) dfs(name);
730
+ }
731
+ return cycles;
732
+ }
733
+ function buildProducerMap2(tasks) {
734
+ const map = {};
735
+ for (const [name, config] of Object.entries(tasks)) {
736
+ for (const token of getProvides(config)) {
737
+ if (!map[token]) map[token] = [];
738
+ map[token].push(name);
739
+ }
740
+ if (config.on) {
741
+ for (const tokens of Object.values(config.on)) {
742
+ for (const token of tokens) {
743
+ if (!map[token]) map[token] = [];
744
+ if (!map[token].includes(name)) map[token].push(name);
745
+ }
746
+ }
747
+ }
748
+ if (config.on_failure) {
749
+ for (const token of config.on_failure) {
750
+ if (!map[token]) map[token] = [];
751
+ if (!map[token].includes(name)) map[token].push(name);
752
+ }
753
+ }
754
+ }
755
+ return map;
756
+ }
757
+ function getUnreachableTokens(live) {
758
+ const { config, state } = live;
759
+ const graphTasks = getAllTasks(config);
760
+ const producerMap = buildProducerMap2(graphTasks);
761
+ const available = /* @__PURE__ */ new Set([...state.availableOutputs]);
762
+ for (const [taskName, taskState] of Object.entries(state.tasks)) {
763
+ if (taskState.status === "completed") {
764
+ const tc = graphTasks[taskName];
765
+ if (tc) getProvides(tc).forEach((t) => available.add(t));
766
+ }
767
+ }
768
+ const allRequired = /* @__PURE__ */ new Set();
769
+ for (const taskConfig of Object.values(graphTasks)) {
770
+ for (const token of getRequires(taskConfig)) {
771
+ allRequired.add(token);
772
+ }
773
+ }
774
+ const unreachable = /* @__PURE__ */ new Set();
775
+ const unreachableNodes = /* @__PURE__ */ new Set();
776
+ for (const token of allRequired) {
777
+ if (available.has(token)) continue;
778
+ const producers = producerMap[token] || [];
779
+ if (producers.length === 0) {
780
+ unreachable.add(token);
781
+ }
782
+ }
783
+ let changed = true;
784
+ while (changed) {
785
+ changed = false;
786
+ for (const [name, taskConfig] of Object.entries(graphTasks)) {
787
+ if (unreachableNodes.has(name)) continue;
788
+ const ts = state.tasks[name];
789
+ if (ts?.status === "completed") continue;
790
+ const isNonActive = isNonActiveTask(ts);
791
+ const requires = getRequires(taskConfig);
792
+ const hasUnreachableDep = requires.some((t) => unreachable.has(t));
793
+ if (isNonActive || hasUnreachableDep) {
794
+ if (!unreachableNodes.has(name)) {
795
+ unreachableNodes.add(name);
796
+ changed = true;
797
+ }
798
+ }
799
+ }
800
+ for (const token of allRequired) {
801
+ if (unreachable.has(token) || available.has(token)) continue;
802
+ const producers = producerMap[token] || [];
803
+ const allProducersUnreachable = producers.length > 0 && producers.every((p) => unreachableNodes.has(p) || isNonActiveTask(state.tasks[p]));
804
+ if (producers.length === 0 || allProducersUnreachable) {
805
+ if (!unreachable.has(token)) {
806
+ unreachable.add(token);
807
+ changed = true;
808
+ }
809
+ }
810
+ }
811
+ }
812
+ const tokens = [];
813
+ for (const token of unreachable) {
814
+ const producers = producerMap[token] || [];
815
+ let reason;
816
+ if (producers.length === 0) {
817
+ reason = "no-producer";
818
+ } else {
819
+ const allFailed = producers.every((p) => isNonActiveTask(state.tasks[p]));
820
+ reason = allFailed ? "all-producers-failed" : "transitive";
821
+ }
822
+ tokens.push({ token, reason, producers });
823
+ }
824
+ return { tokens };
825
+ }
826
+ function getUnreachableNodes(live) {
827
+ const { config, state } = live;
828
+ const graphTasks = getAllTasks(config);
829
+ const { tokens: unreachableTokens } = getUnreachableTokens(live);
830
+ const unreachableTokenSet = new Set(unreachableTokens.map((t) => t.token));
831
+ const nodes = [];
832
+ for (const [name, taskConfig] of Object.entries(graphTasks)) {
833
+ const ts = state.tasks[name];
834
+ if (ts?.status === "completed") continue;
835
+ const requires = getRequires(taskConfig);
836
+ const missingTokens = requires.filter((t) => unreachableTokenSet.has(t));
837
+ if (missingTokens.length > 0) {
838
+ nodes.push({ nodeName: name, missingTokens });
839
+ } else if (isNonActiveTask(ts)) {
840
+ nodes.push({ nodeName: name, missingTokens: [] });
841
+ }
842
+ }
843
+ return { nodes };
844
+ }
845
+ function getUpstream(live, nodeName) {
846
+ const graphTasks = getAllTasks(live.config);
847
+ if (!graphTasks[nodeName]) return { nodeName, nodes: [], tokens: [] };
848
+ const producerMap = buildProducerMap2(graphTasks);
849
+ const visited = /* @__PURE__ */ new Set();
850
+ const tokenSet = /* @__PURE__ */ new Set();
851
+ const nodeEntries = /* @__PURE__ */ new Map();
852
+ function walk(current) {
853
+ const taskConfig = graphTasks[current];
854
+ if (!taskConfig) return;
855
+ for (const token of getRequires(taskConfig)) {
856
+ const producers = producerMap[token] || [];
857
+ for (const producer of producers) {
858
+ if (producer === nodeName) continue;
859
+ tokenSet.add(token);
860
+ if (!nodeEntries.has(producer)) nodeEntries.set(producer, /* @__PURE__ */ new Set());
861
+ nodeEntries.get(producer).add(token);
862
+ if (!visited.has(producer)) {
863
+ visited.add(producer);
864
+ walk(producer);
865
+ }
866
+ }
867
+ }
868
+ }
869
+ walk(nodeName);
870
+ const nodes = [...nodeEntries.entries()].map(([name, tokens]) => ({
871
+ nodeName: name,
872
+ providesTokens: [...tokens]
873
+ }));
874
+ return { nodeName, nodes, tokens: [...tokenSet] };
875
+ }
876
+ function getDownstream(live, nodeName) {
877
+ const graphTasks = getAllTasks(live.config);
878
+ if (!graphTasks[nodeName]) return { nodeName, nodes: [], tokens: [] };
879
+ const consumerMap = {};
880
+ for (const [name, config] of Object.entries(graphTasks)) {
881
+ for (const token of getRequires(config)) {
882
+ if (!consumerMap[token]) consumerMap[token] = [];
883
+ consumerMap[token].push(name);
884
+ }
885
+ }
886
+ const visited = /* @__PURE__ */ new Set();
887
+ const tokenSet = /* @__PURE__ */ new Set();
888
+ const nodeEntries = /* @__PURE__ */ new Map();
889
+ function walk(current) {
890
+ const taskConfig = graphTasks[current];
891
+ if (!taskConfig) return;
892
+ for (const token of getProvides(taskConfig)) {
893
+ const consumers = consumerMap[token] || [];
894
+ for (const consumer of consumers) {
895
+ if (consumer === nodeName) continue;
896
+ tokenSet.add(token);
897
+ if (!nodeEntries.has(consumer)) nodeEntries.set(consumer, /* @__PURE__ */ new Set());
898
+ nodeEntries.get(consumer).add(token);
899
+ if (!visited.has(consumer)) {
900
+ visited.add(consumer);
901
+ walk(consumer);
902
+ }
903
+ }
904
+ }
905
+ }
906
+ walk(nodeName);
907
+ const nodes = [...nodeEntries.entries()].map(([name, tokens]) => ({
908
+ nodeName: name,
909
+ requiresTokens: [...tokens]
910
+ }));
911
+ return { nodeName, nodes, tokens: [...tokenSet] };
912
+ }
913
+
914
+ export { addNode, addProvides, addRequires, applyEvent, createLiveGraph, disableNode, drainTokens, enableNode, getDownstream, getNode, getUnreachableNodes, getUnreachableTokens, getUpstream, injectTokens, inspect, removeNode, removeProvides, removeRequires, resetNode, restore, schedule, snapshot };
915
+ //# sourceMappingURL=index.js.map
916
+ //# sourceMappingURL=index.js.map