agentflow-core 0.1.4 → 0.2.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,933 @@
1
+ // src/graph-builder.ts
2
+ import { randomUUID } from "crypto";
3
+ function deepFreeze(obj) {
4
+ if (obj === null || typeof obj !== "object") return obj;
5
+ if (obj instanceof Map) {
6
+ Object.freeze(obj);
7
+ for (const value of obj.values()) {
8
+ deepFreeze(value);
9
+ }
10
+ return obj;
11
+ }
12
+ Object.freeze(obj);
13
+ const record = obj;
14
+ for (const key of Object.keys(record)) {
15
+ const value = record[key];
16
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
17
+ deepFreeze(value);
18
+ }
19
+ }
20
+ return obj;
21
+ }
22
+ function createCounterIdGenerator() {
23
+ let counter = 0;
24
+ return () => {
25
+ counter++;
26
+ return `node_${String(counter).padStart(3, "0")}`;
27
+ };
28
+ }
29
+ function createGraphBuilder(config) {
30
+ const generateId = config?.idGenerator ?? createCounterIdGenerator();
31
+ const agentId = config?.agentId ?? "unknown";
32
+ const trigger = config?.trigger ?? "manual";
33
+ const spanId = randomUUID();
34
+ const traceId = config?.traceId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_TRACE_ID : void 0) ?? randomUUID();
35
+ const parentSpanId = config?.parentSpanId ?? (typeof process !== "undefined" ? process.env?.AGENTFLOW_PARENT_SPAN_ID : void 0) ?? null;
36
+ const graphId = generateId();
37
+ const startTime = Date.now();
38
+ const nodes = /* @__PURE__ */ new Map();
39
+ const edges = [];
40
+ const events = [];
41
+ const parentStack = [];
42
+ let rootNodeId = null;
43
+ let built = false;
44
+ function assertNotBuilt() {
45
+ if (built) {
46
+ throw new Error("GraphBuilder: cannot mutate after build() has been called");
47
+ }
48
+ }
49
+ function getNode2(nodeId) {
50
+ const node = nodes.get(nodeId);
51
+ if (!node) {
52
+ throw new Error(`GraphBuilder: node "${nodeId}" does not exist`);
53
+ }
54
+ return node;
55
+ }
56
+ function recordEvent(nodeId, eventType, data = {}) {
57
+ events.push({
58
+ timestamp: Date.now(),
59
+ eventType,
60
+ nodeId,
61
+ data
62
+ });
63
+ }
64
+ function buildGraph() {
65
+ if (rootNodeId === null) {
66
+ throw new Error("GraphBuilder: cannot build a graph with no nodes");
67
+ }
68
+ let graphStatus = "completed";
69
+ for (const node of nodes.values()) {
70
+ if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
71
+ graphStatus = "failed";
72
+ break;
73
+ }
74
+ if (node.status === "running") {
75
+ graphStatus = "running";
76
+ }
77
+ }
78
+ const endTime = graphStatus === "running" ? null : Date.now();
79
+ const frozenNodes = new Map(
80
+ [...nodes.entries()].map(([id, mNode]) => [
81
+ id,
82
+ {
83
+ id: mNode.id,
84
+ type: mNode.type,
85
+ name: mNode.name,
86
+ startTime: mNode.startTime,
87
+ endTime: mNode.endTime,
88
+ status: mNode.status,
89
+ parentId: mNode.parentId,
90
+ children: [...mNode.children],
91
+ metadata: { ...mNode.metadata },
92
+ state: { ...mNode.state }
93
+ }
94
+ ])
95
+ );
96
+ const graph = {
97
+ id: graphId,
98
+ rootNodeId,
99
+ nodes: frozenNodes,
100
+ edges: [...edges],
101
+ startTime,
102
+ endTime,
103
+ status: graphStatus,
104
+ trigger,
105
+ agentId,
106
+ events: [...events],
107
+ traceId,
108
+ spanId,
109
+ parentSpanId
110
+ };
111
+ return deepFreeze(graph);
112
+ }
113
+ const builder = {
114
+ get graphId() {
115
+ return graphId;
116
+ },
117
+ get traceContext() {
118
+ return { traceId, spanId };
119
+ },
120
+ startNode(opts) {
121
+ assertNotBuilt();
122
+ const id = generateId();
123
+ const parentId = opts.parentId ?? parentStack[parentStack.length - 1] ?? null;
124
+ if (parentId !== null && !nodes.has(parentId)) {
125
+ throw new Error(`GraphBuilder: parent node "${parentId}" does not exist`);
126
+ }
127
+ const node = {
128
+ id,
129
+ type: opts.type,
130
+ name: opts.name,
131
+ startTime: Date.now(),
132
+ endTime: null,
133
+ status: "running",
134
+ parentId,
135
+ children: [],
136
+ metadata: opts.metadata ? { ...opts.metadata } : {},
137
+ state: {}
138
+ };
139
+ nodes.set(id, node);
140
+ if (parentId !== null) {
141
+ const parent = nodes.get(parentId);
142
+ if (parent) {
143
+ parent.children.push(id);
144
+ }
145
+ edges.push({ from: parentId, to: id, type: "spawned" });
146
+ }
147
+ if (rootNodeId === null) {
148
+ rootNodeId = id;
149
+ }
150
+ recordEvent(id, "agent_start", { type: opts.type, name: opts.name });
151
+ return id;
152
+ },
153
+ endNode(nodeId, status = "completed") {
154
+ assertNotBuilt();
155
+ const node = getNode2(nodeId);
156
+ if (node.endTime !== null) {
157
+ throw new Error(
158
+ `GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
159
+ );
160
+ }
161
+ node.endTime = Date.now();
162
+ node.status = status;
163
+ recordEvent(nodeId, "agent_end", { status });
164
+ },
165
+ failNode(nodeId, error) {
166
+ assertNotBuilt();
167
+ const node = getNode2(nodeId);
168
+ if (node.endTime !== null) {
169
+ throw new Error(
170
+ `GraphBuilder: node "${nodeId}" has already ended (status: ${node.status})`
171
+ );
172
+ }
173
+ const errorMessage = error instanceof Error ? error.message : error;
174
+ const errorStack = error instanceof Error ? error.stack : void 0;
175
+ node.endTime = Date.now();
176
+ node.status = "failed";
177
+ node.metadata.error = errorMessage;
178
+ if (errorStack) {
179
+ node.metadata.errorStack = errorStack;
180
+ }
181
+ recordEvent(nodeId, "tool_error", { error: errorMessage });
182
+ },
183
+ addEdge(from, to, type) {
184
+ assertNotBuilt();
185
+ getNode2(from);
186
+ getNode2(to);
187
+ edges.push({ from, to, type });
188
+ recordEvent(from, "custom", { to, type, action: "edge_add" });
189
+ },
190
+ pushEvent(event) {
191
+ assertNotBuilt();
192
+ getNode2(event.nodeId);
193
+ events.push({
194
+ ...event,
195
+ timestamp: Date.now()
196
+ });
197
+ },
198
+ updateState(nodeId, state) {
199
+ assertNotBuilt();
200
+ const node = getNode2(nodeId);
201
+ Object.assign(node.state, state);
202
+ recordEvent(nodeId, "custom", { action: "state_update", ...state });
203
+ },
204
+ withParent(parentId, fn) {
205
+ assertNotBuilt();
206
+ getNode2(parentId);
207
+ parentStack.push(parentId);
208
+ try {
209
+ return fn();
210
+ } finally {
211
+ parentStack.pop();
212
+ }
213
+ },
214
+ getSnapshot() {
215
+ return buildGraph();
216
+ },
217
+ build() {
218
+ assertNotBuilt();
219
+ const graph = buildGraph();
220
+ built = true;
221
+ return graph;
222
+ }
223
+ };
224
+ return builder;
225
+ }
226
+
227
+ // src/loader.ts
228
+ function toNodesMap(raw) {
229
+ if (raw instanceof Map) return raw;
230
+ if (Array.isArray(raw)) {
231
+ return new Map(raw);
232
+ }
233
+ if (raw !== null && typeof raw === "object") {
234
+ return new Map(Object.entries(raw));
235
+ }
236
+ return /* @__PURE__ */ new Map();
237
+ }
238
+ function loadGraph(input) {
239
+ const raw = typeof input === "string" ? JSON.parse(input) : input;
240
+ const nodes = toNodesMap(raw.nodes);
241
+ return {
242
+ id: raw.id ?? "",
243
+ rootNodeId: raw.rootNodeId ?? raw.rootId ?? "",
244
+ nodes,
245
+ edges: raw.edges ?? [],
246
+ startTime: raw.startTime ?? 0,
247
+ endTime: raw.endTime ?? null,
248
+ status: raw.status ?? "completed",
249
+ trigger: raw.trigger ?? "unknown",
250
+ agentId: raw.agentId ?? "unknown",
251
+ events: raw.events ?? [],
252
+ traceId: raw.traceId,
253
+ spanId: raw.spanId,
254
+ parentSpanId: raw.parentSpanId
255
+ };
256
+ }
257
+ function graphToJson(graph) {
258
+ const nodesObj = {};
259
+ for (const [id, node] of graph.nodes) {
260
+ nodesObj[id] = node;
261
+ }
262
+ return {
263
+ id: graph.id,
264
+ rootNodeId: graph.rootNodeId,
265
+ nodes: nodesObj,
266
+ edges: graph.edges,
267
+ startTime: graph.startTime,
268
+ endTime: graph.endTime,
269
+ status: graph.status,
270
+ trigger: graph.trigger,
271
+ agentId: graph.agentId,
272
+ events: graph.events,
273
+ traceId: graph.traceId,
274
+ spanId: graph.spanId,
275
+ parentSpanId: graph.parentSpanId
276
+ };
277
+ }
278
+
279
+ // src/runner.ts
280
+ import { spawnSync } from "child_process";
281
+ import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from "fs";
282
+ import { basename, join, resolve } from "path";
283
+ function globToRegex(pattern) {
284
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
285
+ return new RegExp(`^${escaped}$`);
286
+ }
287
+ function snapshotDir(dir, patterns) {
288
+ const result = /* @__PURE__ */ new Map();
289
+ if (!existsSync(dir)) return result;
290
+ for (const entry of readdirSync(dir)) {
291
+ if (!patterns.some((re) => re.test(entry))) continue;
292
+ const full = join(dir, entry);
293
+ try {
294
+ const stat = statSync(full);
295
+ if (stat.isFile()) {
296
+ result.set(full, stat.mtimeMs);
297
+ }
298
+ } catch {
299
+ }
300
+ }
301
+ return result;
302
+ }
303
+ function agentIdFromFilename(filePath) {
304
+ const base = basename(filePath, ".json");
305
+ const cleaned = base.replace(/-state$/, "");
306
+ return `alfred-${cleaned}`;
307
+ }
308
+ function deriveAgentId(command) {
309
+ return "orchestrator";
310
+ }
311
+ function fileTimestamp() {
312
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "");
313
+ }
314
+ async function runTraced(config) {
315
+ const {
316
+ command,
317
+ agentId = deriveAgentId(command),
318
+ trigger = "cli",
319
+ tracesDir = "./traces",
320
+ watchDirs = [],
321
+ watchPatterns = ["*.json"]
322
+ } = config;
323
+ if (command.length === 0) {
324
+ throw new Error("runTraced: command must not be empty");
325
+ }
326
+ const resolvedTracesDir = resolve(tracesDir);
327
+ const patterns = watchPatterns.map(globToRegex);
328
+ const orchestrator = createGraphBuilder({ agentId, trigger });
329
+ const { traceId, spanId } = orchestrator.traceContext;
330
+ const beforeSnapshots = /* @__PURE__ */ new Map();
331
+ for (const dir of watchDirs) {
332
+ beforeSnapshots.set(dir, snapshotDir(dir, patterns));
333
+ }
334
+ const rootId = orchestrator.startNode({ type: "agent", name: agentId });
335
+ const dispatchId = orchestrator.startNode({
336
+ type: "tool",
337
+ name: "dispatch-command",
338
+ parentId: rootId
339
+ });
340
+ orchestrator.updateState(dispatchId, { command: command.join(" ") });
341
+ const monitorId = orchestrator.startNode({
342
+ type: "tool",
343
+ name: "state-monitor",
344
+ parentId: rootId
345
+ });
346
+ orchestrator.updateState(monitorId, {
347
+ watchDirs,
348
+ watchPatterns
349
+ });
350
+ const startMs = Date.now();
351
+ const execCmd = command[0] ?? "";
352
+ const execArgs = command.slice(1);
353
+ process.env.AGENTFLOW_TRACE_ID = traceId;
354
+ process.env.AGENTFLOW_PARENT_SPAN_ID = spanId;
355
+ const result = spawnSync(execCmd, execArgs, { stdio: "inherit" });
356
+ delete process.env.AGENTFLOW_TRACE_ID;
357
+ delete process.env.AGENTFLOW_PARENT_SPAN_ID;
358
+ const exitCode = result.status ?? 1;
359
+ const duration = (Date.now() - startMs) / 1e3;
360
+ const stateChanges = [];
361
+ for (const dir of watchDirs) {
362
+ const before = beforeSnapshots.get(dir) ?? /* @__PURE__ */ new Map();
363
+ const after = snapshotDir(dir, patterns);
364
+ for (const [filePath, mtime] of after) {
365
+ const prevMtime = before.get(filePath);
366
+ if (prevMtime === void 0 || mtime > prevMtime) {
367
+ stateChanges.push(filePath);
368
+ }
369
+ }
370
+ }
371
+ orchestrator.updateState(monitorId, { stateChanges });
372
+ orchestrator.endNode(monitorId);
373
+ if (exitCode === 0) {
374
+ orchestrator.endNode(dispatchId);
375
+ } else {
376
+ orchestrator.failNode(dispatchId, `Command exited with code ${exitCode}`);
377
+ }
378
+ orchestrator.updateState(rootId, {
379
+ exitCode,
380
+ duration,
381
+ stateChanges
382
+ });
383
+ if (exitCode === 0) {
384
+ orchestrator.endNode(rootId);
385
+ } else {
386
+ orchestrator.failNode(rootId, `Command exited with code ${exitCode}`);
387
+ }
388
+ const orchestratorGraph = orchestrator.build();
389
+ const allGraphs = [orchestratorGraph];
390
+ for (const filePath of stateChanges) {
391
+ const childAgentId = agentIdFromFilename(filePath);
392
+ const childBuilder = createGraphBuilder({
393
+ agentId: childAgentId,
394
+ trigger: "state-change",
395
+ traceId,
396
+ parentSpanId: spanId
397
+ });
398
+ const childRootId = childBuilder.startNode({
399
+ type: "agent",
400
+ name: childAgentId
401
+ });
402
+ childBuilder.updateState(childRootId, {
403
+ stateFile: filePath,
404
+ detectedBy: "runner-state-monitor"
405
+ });
406
+ childBuilder.endNode(childRootId);
407
+ allGraphs.push(childBuilder.build());
408
+ }
409
+ if (!existsSync(resolvedTracesDir)) {
410
+ mkdirSync(resolvedTracesDir, { recursive: true });
411
+ }
412
+ const ts = fileTimestamp();
413
+ const tracePaths = [];
414
+ for (const graph of allGraphs) {
415
+ const filename = `${graph.agentId}-${ts}.json`;
416
+ const outPath = join(resolvedTracesDir, filename);
417
+ writeFileSync(outPath, JSON.stringify(graphToJson(graph), null, 2), "utf-8");
418
+ tracePaths.push(outPath);
419
+ }
420
+ return {
421
+ exitCode,
422
+ traceId,
423
+ spanId,
424
+ tracePaths,
425
+ stateChanges,
426
+ duration
427
+ };
428
+ }
429
+
430
+ // src/graph-query.ts
431
+ function getNode(graph, nodeId) {
432
+ return graph.nodes.get(nodeId);
433
+ }
434
+ function getChildren(graph, nodeId) {
435
+ const node = graph.nodes.get(nodeId);
436
+ if (!node) return [];
437
+ const result = [];
438
+ for (const childId of node.children) {
439
+ const child = graph.nodes.get(childId);
440
+ if (child) result.push(child);
441
+ }
442
+ return result;
443
+ }
444
+ function getParent(graph, nodeId) {
445
+ const node = graph.nodes.get(nodeId);
446
+ if (!node || node.parentId === null) return void 0;
447
+ return graph.nodes.get(node.parentId);
448
+ }
449
+ function getFailures(graph) {
450
+ const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
451
+ return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
452
+ }
453
+ function getHungNodes(graph) {
454
+ return [...graph.nodes.values()].filter(
455
+ (node) => node.status === "running" && node.endTime === null
456
+ );
457
+ }
458
+ function getCriticalPath(graph) {
459
+ const root = graph.nodes.get(graph.rootNodeId);
460
+ if (!root) return [];
461
+ function nodeDuration(node) {
462
+ const end = node.endTime ?? Date.now();
463
+ return end - node.startTime;
464
+ }
465
+ function dfs(node) {
466
+ if (node.children.length === 0) {
467
+ return { duration: nodeDuration(node), path: [node] };
468
+ }
469
+ let bestChild = { duration: -1, path: [] };
470
+ for (const childId of node.children) {
471
+ const child = graph.nodes.get(childId);
472
+ if (!child) continue;
473
+ const result = dfs(child);
474
+ if (result.duration > bestChild.duration) {
475
+ bestChild = result;
476
+ }
477
+ }
478
+ return {
479
+ duration: nodeDuration(node) + bestChild.duration,
480
+ path: [node, ...bestChild.path]
481
+ };
482
+ }
483
+ return dfs(root).path;
484
+ }
485
+ function findWaitingOn(graph, nodeId) {
486
+ const results = [];
487
+ for (const edge of graph.edges) {
488
+ if (edge.from === nodeId && edge.type === "waited_on") {
489
+ const node = graph.nodes.get(edge.to);
490
+ if (node) results.push(node);
491
+ }
492
+ }
493
+ return results;
494
+ }
495
+ function getSubtree(graph, nodeId) {
496
+ const startNode = graph.nodes.get(nodeId);
497
+ if (!startNode) return [];
498
+ const result = [];
499
+ const queue = [...startNode.children];
500
+ while (queue.length > 0) {
501
+ const currentId = queue.shift();
502
+ if (currentId === void 0) break;
503
+ const current = graph.nodes.get(currentId);
504
+ if (!current) continue;
505
+ result.push(current);
506
+ queue.push(...current.children);
507
+ }
508
+ return result;
509
+ }
510
+ function getDuration(graph) {
511
+ const end = graph.endTime ?? Date.now();
512
+ return end - graph.startTime;
513
+ }
514
+ function getDepth(graph) {
515
+ const root = graph.nodes.get(graph.rootNodeId);
516
+ if (!root) return -1;
517
+ function dfs(node, depth) {
518
+ if (node.children.length === 0) return depth;
519
+ let maxDepth = depth;
520
+ for (const childId of node.children) {
521
+ const child = graph.nodes.get(childId);
522
+ if (!child) continue;
523
+ const childDepth = dfs(child, depth + 1);
524
+ if (childDepth > maxDepth) maxDepth = childDepth;
525
+ }
526
+ return maxDepth;
527
+ }
528
+ return dfs(root, 0);
529
+ }
530
+ function getStats(graph) {
531
+ const byStatus = {
532
+ running: 0,
533
+ completed: 0,
534
+ failed: 0,
535
+ hung: 0,
536
+ timeout: 0
537
+ };
538
+ const byType = {
539
+ agent: 0,
540
+ tool: 0,
541
+ subagent: 0,
542
+ wait: 0,
543
+ decision: 0,
544
+ custom: 0
545
+ };
546
+ let failureCount = 0;
547
+ let hungCount = 0;
548
+ for (const node of graph.nodes.values()) {
549
+ byStatus[node.status]++;
550
+ byType[node.type]++;
551
+ if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
552
+ failureCount++;
553
+ }
554
+ if (node.status === "running" && node.endTime === null) {
555
+ hungCount++;
556
+ }
557
+ }
558
+ return {
559
+ totalNodes: graph.nodes.size,
560
+ byStatus,
561
+ byType,
562
+ depth: getDepth(graph),
563
+ duration: getDuration(graph),
564
+ failureCount,
565
+ hungCount
566
+ };
567
+ }
568
+
569
+ // src/graph-stitch.ts
570
+ function groupByTraceId(graphs) {
571
+ const groups = /* @__PURE__ */ new Map();
572
+ for (const g of graphs) {
573
+ if (!g.traceId) continue;
574
+ const arr = groups.get(g.traceId) ?? [];
575
+ arr.push(g);
576
+ groups.set(g.traceId, arr);
577
+ }
578
+ return groups;
579
+ }
580
+ function stitchTrace(graphs) {
581
+ if (graphs.length === 0) throw new Error("No graphs to stitch");
582
+ const traceId = graphs[0].traceId ?? "";
583
+ const graphsBySpan = /* @__PURE__ */ new Map();
584
+ const childMap = /* @__PURE__ */ new Map();
585
+ let rootGraph = null;
586
+ for (const g of graphs) {
587
+ if (g.spanId) graphsBySpan.set(g.spanId, g);
588
+ if (!g.parentSpanId) {
589
+ if (!rootGraph || g.startTime < rootGraph.startTime) rootGraph = g;
590
+ }
591
+ if (g.parentSpanId) {
592
+ const siblings = childMap.get(g.parentSpanId) ?? [];
593
+ if (g.spanId) siblings.push(g.spanId);
594
+ childMap.set(g.parentSpanId, siblings);
595
+ }
596
+ }
597
+ if (!rootGraph) rootGraph = graphs[0];
598
+ let status = "completed";
599
+ let endTime = 0;
600
+ let startTime = Infinity;
601
+ for (const g of graphs) {
602
+ startTime = Math.min(startTime, g.startTime);
603
+ if (g.status === "failed") status = "failed";
604
+ else if (g.status === "running" && status !== "failed") status = "running";
605
+ if (g.endTime === null) endTime = null;
606
+ else if (endTime !== null) endTime = Math.max(endTime, g.endTime);
607
+ }
608
+ const frozenChildMap = /* @__PURE__ */ new Map();
609
+ for (const [k, v] of childMap) frozenChildMap.set(k, Object.freeze([...v]));
610
+ return Object.freeze({
611
+ traceId,
612
+ graphs: graphsBySpan,
613
+ rootGraph,
614
+ childMap: frozenChildMap,
615
+ startTime,
616
+ endTime,
617
+ status
618
+ });
619
+ }
620
+ function getTraceTree(trace) {
621
+ const result = [];
622
+ function walk(spanId) {
623
+ const graph = trace.graphs.get(spanId);
624
+ if (graph) result.push(graph);
625
+ const children = trace.childMap.get(spanId) ?? [];
626
+ for (const childSpan of children) walk(childSpan);
627
+ }
628
+ if (trace.rootGraph.spanId) walk(trace.rootGraph.spanId);
629
+ else result.push(trace.rootGraph);
630
+ return result;
631
+ }
632
+
633
+ // src/live.ts
634
+ import { readdirSync as readdirSync2, readFileSync, statSync as statSync2, watch, existsSync as existsSync2 } from "fs";
635
+ import { join as join2, resolve as resolve2 } from "path";
636
+ var C = {
637
+ reset: "\x1B[0m",
638
+ bold: "\x1B[1m",
639
+ dim: "\x1B[90m",
640
+ under: "\x1B[4m",
641
+ red: "\x1B[31m",
642
+ green: "\x1B[32m",
643
+ yellow: "\x1B[33m",
644
+ blue: "\x1B[34m",
645
+ magenta: "\x1B[35m",
646
+ cyan: "\x1B[36m",
647
+ white: "\x1B[37m"
648
+ };
649
+ function parseArgs(argv) {
650
+ const config = {
651
+ tracesDir: "./traces",
652
+ refreshMs: 3e3
653
+ };
654
+ const args = argv.slice(0);
655
+ if (args[0] === "live") args.shift();
656
+ let i = 0;
657
+ while (i < args.length) {
658
+ const arg = args[i];
659
+ if (arg === "--help" || arg === "-h") {
660
+ printUsage();
661
+ process.exit(0);
662
+ } else if (arg === "--refresh" || arg === "-r") {
663
+ i++;
664
+ const val = parseInt(args[i] ?? "", 10);
665
+ if (!isNaN(val) && val > 0) config.refreshMs = val * 1e3;
666
+ i++;
667
+ } else if (arg === "--traces-dir" || arg === "-t") {
668
+ i++;
669
+ config.tracesDir = args[i] ?? config.tracesDir;
670
+ i++;
671
+ } else if (!arg.startsWith("-")) {
672
+ config.tracesDir = arg;
673
+ i++;
674
+ } else {
675
+ i++;
676
+ }
677
+ }
678
+ config.tracesDir = resolve2(config.tracesDir);
679
+ return config;
680
+ }
681
+ function printUsage() {
682
+ console.log(`
683
+ AgentFlow Live Monitor \u2014 real-time terminal dashboard for agent systems.
684
+
685
+ Usage:
686
+ agentflow live [traces-dir] [options]
687
+
688
+ Arguments:
689
+ traces-dir Path to the traces directory (default: ./traces)
690
+
691
+ Options:
692
+ -r, --refresh <secs> Refresh interval in seconds (default: 3)
693
+ -t, --traces-dir <path> Explicit traces directory path
694
+ -h, --help Show this help message
695
+
696
+ Examples:
697
+ agentflow live
698
+ agentflow live ./my-traces --refresh 5
699
+ agentflow live /var/log/agentflow/traces -r 10
700
+ `.trim());
701
+ }
702
+ function listTraceFiles(tracesDir) {
703
+ try {
704
+ return readdirSync2(tracesDir).filter((f) => f.endsWith(".json")).map((f) => {
705
+ const fp = join2(tracesDir, f);
706
+ const stat = statSync2(fp);
707
+ return { filename: f, path: fp, mtime: stat.mtime.getTime() };
708
+ }).sort((a, b) => b.mtime - a.mtime);
709
+ } catch {
710
+ return [];
711
+ }
712
+ }
713
+ function safeLoadTrace(fp) {
714
+ try {
715
+ return loadGraph(readFileSync(fp, "utf8"));
716
+ } catch {
717
+ return null;
718
+ }
719
+ }
720
+ function analyze(trace) {
721
+ try {
722
+ const stats = getStats(trace);
723
+ const fails = getFailures(trace);
724
+ const hung = getHungNodes(trace);
725
+ return {
726
+ agentId: trace.agentId,
727
+ trigger: trace.trigger,
728
+ traceId: trace.traceId,
729
+ spanId: trace.spanId,
730
+ parentSpanId: trace.parentSpanId,
731
+ nodes: stats.totalNodes,
732
+ success: fails.length === 0 && hung.length === 0,
733
+ failures: fails.length,
734
+ hung: hung.length
735
+ };
736
+ } catch {
737
+ return null;
738
+ }
739
+ }
740
+ function getDistributedDepth(dt, spanId) {
741
+ if (!spanId) return 0;
742
+ const graph = dt.graphs.get(spanId);
743
+ if (!graph || !graph.parentSpanId) return 0;
744
+ return 1 + getDistributedDepth(dt, graph.parentSpanId);
745
+ }
746
+ var prevFileCount = 0;
747
+ var newExecCount = 0;
748
+ var sessionStart = Date.now();
749
+ function render(config) {
750
+ const files = listTraceFiles(config.tracesDir);
751
+ if (files.length > prevFileCount && prevFileCount > 0) {
752
+ newExecCount += files.length - prevFileCount;
753
+ }
754
+ prevFileCount = files.length;
755
+ const allTraces = [];
756
+ const agents = {};
757
+ for (const f of files.slice(0, 200)) {
758
+ const trace = safeLoadTrace(f.path);
759
+ if (!trace) continue;
760
+ allTraces.push(trace);
761
+ const a = analyze(trace);
762
+ if (!a) continue;
763
+ if (!agents[a.agentId]) {
764
+ agents[a.agentId] = { name: a.agentId, total: 0, ok: 0, fail: 0, lastTs: 0 };
765
+ }
766
+ const ag = agents[a.agentId];
767
+ ag.total++;
768
+ a.success ? ag.ok++ : ag.fail++;
769
+ if (f.mtime > ag.lastTs) ag.lastTs = f.mtime;
770
+ }
771
+ const agentList = Object.values(agents).sort((a, b) => b.total - a.total);
772
+ const totExec = agentList.reduce((s, a) => s + a.total, 0);
773
+ const totFail = agentList.reduce((s, a) => s + a.fail, 0);
774
+ const sysRate = totExec > 0 ? ((totExec - totFail) / totExec * 100).toFixed(1) : "100.0";
775
+ const recent = [];
776
+ for (const f of files.slice(0, 15)) {
777
+ const trace = safeLoadTrace(f.path);
778
+ if (!trace) continue;
779
+ const a = analyze(trace);
780
+ if (a) recent.push({ ...a, ts: f.mtime });
781
+ }
782
+ const now = Date.now();
783
+ const buckets = new Array(12).fill(0);
784
+ const failBuckets = new Array(12).fill(0);
785
+ for (const f of files) {
786
+ const age = now - f.mtime;
787
+ if (age > 36e5) continue;
788
+ const idx = 11 - Math.floor(age / 3e5);
789
+ if (idx >= 0 && idx < 12) {
790
+ const trace = safeLoadTrace(f.path);
791
+ if (!trace) continue;
792
+ const a = analyze(trace);
793
+ if (!a) continue;
794
+ buckets[idx]++;
795
+ if (!a.success) failBuckets[idx]++;
796
+ }
797
+ }
798
+ const maxBucket = Math.max(...buckets, 1);
799
+ const sparkChars = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
800
+ const spark = buckets.map((v, i) => {
801
+ const level = Math.round(v / maxBucket * 8);
802
+ return (failBuckets[i] > 0 ? C.red : C.green) + sparkChars[level] + C.reset;
803
+ }).join("");
804
+ const traceGroups = groupByTraceId(allTraces);
805
+ const distributedTraces = [];
806
+ for (const [_traceId, graphs] of traceGroups) {
807
+ if (graphs.length > 1) {
808
+ try {
809
+ distributedTraces.push(stitchTrace(graphs));
810
+ } catch {
811
+ }
812
+ }
813
+ }
814
+ distributedTraces.sort((a, b) => b.startTime - a.startTime);
815
+ const upSec = Math.floor((Date.now() - sessionStart) / 1e3);
816
+ const upMin = Math.floor(upSec / 60);
817
+ const upStr = upMin > 0 ? `${upMin}m ${upSec % 60}s` : `${upSec}s`;
818
+ const time = (/* @__PURE__ */ new Date()).toLocaleTimeString();
819
+ process.stdout.write("\x1B[2J\x1B[H");
820
+ console.log(`${C.bold}${C.cyan}\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${C.reset}`);
821
+ console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.bold}${C.white}AGENTFLOW LIVE${C.reset} ${C.green}\u25CF LIVE${C.reset} ${C.dim}${time}${C.reset} ${C.bold}${C.cyan}\u2551${C.reset}`);
822
+ const metaLine = `Refresh: ${config.refreshMs / 1e3}s \xB7 Up: ${upStr}`;
823
+ const pad1 = Math.max(0, 64 - metaLine.length);
824
+ console.log(`${C.bold}${C.cyan}\u2551${C.reset} ${C.dim}${metaLine}${C.reset}${" ".repeat(pad1)}${C.bold}${C.cyan}\u2551${C.reset}`);
825
+ console.log(`${C.bold}${C.cyan}\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${C.reset}`);
826
+ const sc = totFail === 0 ? C.green : C.yellow;
827
+ console.log("");
828
+ console.log(` ${C.bold}Agents${C.reset} ${sc}${agentList.length}${C.reset} ${C.bold}Executions${C.reset} ${sc}${totExec}${C.reset} ${C.bold}Success${C.reset} ${sc}${sysRate}%${C.reset} ${C.bold}Traces${C.reset} ${sc}${files.length}${C.reset} ${C.bold}New${C.reset} ${C.yellow}+${newExecCount}${C.reset} ${C.bold}Distributed${C.reset} ${C.magenta}${distributedTraces.length}${C.reset}`);
829
+ console.log("");
830
+ console.log(` ${C.bold}Activity (1h)${C.reset} ${spark} ${C.dim}\u2190 now${C.reset}`);
831
+ console.log("");
832
+ console.log(` ${C.bold}${C.under}Agent Runs OK Fail Rate Last Active${C.reset}`);
833
+ for (const ag of agentList) {
834
+ const rate = (ag.ok / ag.total * 100).toFixed(0);
835
+ const lastTime = new Date(ag.lastTs).toLocaleTimeString();
836
+ const isRecent = Date.now() - ag.lastTs < 3e5;
837
+ let status;
838
+ if (ag.fail > 0) status = `${C.red}\u25CF${C.reset}`;
839
+ else if (isRecent) status = `${C.green}\u25CF${C.reset}`;
840
+ else status = `${C.dim}\u25CB${C.reset}`;
841
+ const name = ag.name.padEnd(28);
842
+ const runs = String(ag.total).padStart(5);
843
+ const ok = String(ag.ok).padStart(5);
844
+ const fail = ag.fail > 0 ? `${C.red}${String(ag.fail).padStart(4)}${C.reset}` : String(ag.fail).padStart(4);
845
+ const rateStr = (rate + "%").padStart(5);
846
+ const activeStr = isRecent ? `${C.green}${lastTime}${C.reset}` : `${C.dim}${lastTime}${C.reset}`;
847
+ console.log(` ${status} ${name}${runs}${ok}${fail} ${rateStr} ${activeStr}`);
848
+ }
849
+ if (distributedTraces.length > 0) {
850
+ console.log("");
851
+ console.log(` ${C.bold}${C.under}Distributed Traces (multi-agent workflows)${C.reset}`);
852
+ for (const dt of distributedTraces.slice(0, 5)) {
853
+ const traceTime = new Date(dt.startTime).toLocaleTimeString();
854
+ const statusIcon = dt.status === "completed" ? `${C.green}\u2713${C.reset}` : dt.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
855
+ const dur = dt.endTime ? `${dt.endTime - dt.startTime}ms` : "running";
856
+ const tid = dt.traceId.slice(0, 8);
857
+ console.log(` ${statusIcon} ${C.magenta}trace:${tid}${C.reset} ${C.dim}${traceTime}${C.reset} ${C.dim}${dur}${C.reset} ${C.dim}(${dt.graphs.size} agents)${C.reset}`);
858
+ const tree = getTraceTree(dt);
859
+ for (let i = 0; i < tree.length; i++) {
860
+ const g = tree[i];
861
+ const depth = getDistributedDepth(dt, g.spanId);
862
+ const indent = " " + "\u2502 ".repeat(Math.max(0, depth - 1));
863
+ const isLast = i === tree.length - 1 || getDistributedDepth(dt, tree[i + 1]?.spanId) <= depth;
864
+ const connector = depth === 0 ? " " : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
865
+ const gStatus = g.status === "completed" ? `${C.green}\u2713${C.reset}` : g.status === "failed" ? `${C.red}\u2717${C.reset}` : `${C.yellow}\u23F3${C.reset}`;
866
+ const gDur = g.endTime ? `${g.endTime - g.startTime}ms` : "running";
867
+ console.log(`${indent}${connector}${gStatus} ${C.bold}${g.agentId}${C.reset} ${C.dim}[${g.trigger}] ${gDur}${C.reset}`);
868
+ }
869
+ }
870
+ }
871
+ console.log("");
872
+ console.log(` ${C.bold}${C.under}Recent Executions${C.reset}`);
873
+ for (const ex of recent.slice(0, 8)) {
874
+ const icon = ex.success ? `${C.green}\u2713${C.reset}` : `${C.red}\u2717${C.reset}`;
875
+ const t = new Date(ex.ts).toLocaleTimeString();
876
+ const agent = ex.agentId.padEnd(28);
877
+ const age = Math.floor((Date.now() - ex.ts) / 1e3);
878
+ const ageStr = age < 60 ? age + "s ago" : Math.floor(age / 60) + "m ago";
879
+ const traceTag = ex.traceId ? ` ${C.magenta}\u29EB${C.reset}` : "";
880
+ console.log(` ${icon} ${agent} ${C.dim}${t} ${ageStr.padStart(8)} ${ex.nodes} nodes${C.reset}${traceTag}`);
881
+ }
882
+ if (files.length === 0) {
883
+ console.log(` ${C.dim}No trace files found. Waiting for traces in:${C.reset}`);
884
+ console.log(` ${C.dim}${config.tracesDir}${C.reset}`);
885
+ }
886
+ console.log("");
887
+ console.log(` ${C.dim}Watching: ${config.tracesDir}${C.reset}`);
888
+ console.log(` ${C.dim}Press Ctrl+C to exit${C.reset}`);
889
+ }
890
+ function startLive(argv) {
891
+ const config = parseArgs(argv);
892
+ if (!existsSync2(config.tracesDir)) {
893
+ console.error(`Traces directory does not exist: ${config.tracesDir}`);
894
+ console.error("Create it or specify a different path: agentflow live <traces-dir>");
895
+ process.exit(1);
896
+ }
897
+ render(config);
898
+ let debounce = null;
899
+ try {
900
+ watch(config.tracesDir, () => {
901
+ if (debounce) clearTimeout(debounce);
902
+ debounce = setTimeout(() => render(config), 500);
903
+ });
904
+ } catch {
905
+ }
906
+ setInterval(() => render(config), config.refreshMs);
907
+ process.on("SIGINT", () => {
908
+ console.log("\n" + C.dim + "Monitor stopped." + C.reset);
909
+ process.exit(0);
910
+ });
911
+ }
912
+
913
+ export {
914
+ createGraphBuilder,
915
+ loadGraph,
916
+ graphToJson,
917
+ runTraced,
918
+ getNode,
919
+ getChildren,
920
+ getParent,
921
+ getFailures,
922
+ getHungNodes,
923
+ getCriticalPath,
924
+ findWaitingOn,
925
+ getSubtree,
926
+ getDuration,
927
+ getDepth,
928
+ getStats,
929
+ groupByTraceId,
930
+ stitchTrace,
931
+ getTraceTree,
932
+ startLive
933
+ };