agentflow-dashboard 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,597 @@
1
+ #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AgentStats: () => AgentStats,
34
+ DashboardServer: () => DashboardServer,
35
+ TraceWatcher: () => TraceWatcher
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/server.ts
40
+ var import_express = __toESM(require("express"), 1);
41
+ var import_ws = require("ws");
42
+ var import_http = require("http");
43
+ var path2 = __toESM(require("path"), 1);
44
+ var fs2 = __toESM(require("fs"), 1);
45
+
46
+ // src/watcher.ts
47
+ var import_chokidar = __toESM(require("chokidar"), 1);
48
+ var fs = __toESM(require("fs"), 1);
49
+ var path = __toESM(require("path"), 1);
50
+ var import_events = require("events");
51
+ var TraceWatcher = class extends import_events.EventEmitter {
52
+ watcher;
53
+ traces = /* @__PURE__ */ new Map();
54
+ tracesDir;
55
+ constructor(tracesDir) {
56
+ super();
57
+ this.tracesDir = path.resolve(tracesDir);
58
+ this.ensureTracesDir();
59
+ this.loadExistingTraces();
60
+ this.startWatching();
61
+ }
62
+ ensureTracesDir() {
63
+ if (!fs.existsSync(this.tracesDir)) {
64
+ fs.mkdirSync(this.tracesDir, { recursive: true });
65
+ console.log(`Created traces directory: ${this.tracesDir}`);
66
+ }
67
+ }
68
+ loadExistingTraces() {
69
+ try {
70
+ const files = fs.readdirSync(this.tracesDir).filter((file) => file.endsWith(".json"));
71
+ console.log(`Loading ${files.length} existing trace files...`);
72
+ for (const file of files) {
73
+ this.loadTraceFile(path.join(this.tracesDir, file));
74
+ }
75
+ console.log(`Loaded ${this.traces.size} traces`);
76
+ } catch (error) {
77
+ console.error("Error loading existing traces:", error);
78
+ }
79
+ }
80
+ loadTraceFile(filePath) {
81
+ try {
82
+ const content = fs.readFileSync(filePath, "utf8");
83
+ const trace = JSON.parse(content);
84
+ const filename = path.basename(filePath);
85
+ const stats = fs.statSync(filePath);
86
+ trace.filename = filename;
87
+ trace.lastModified = stats.mtime.getTime();
88
+ if (Array.isArray(trace.nodes)) {
89
+ const nodesMap = new Map(trace.nodes);
90
+ trace.nodes = nodesMap;
91
+ }
92
+ this.traces.set(filename, trace);
93
+ return true;
94
+ } catch (error) {
95
+ console.error(`Error loading trace file ${filePath}:`, error);
96
+ return false;
97
+ }
98
+ }
99
+ startWatching() {
100
+ this.watcher = import_chokidar.default.watch(this.tracesDir, {
101
+ ignored: /^\\./,
102
+ persistent: true,
103
+ ignoreInitial: true
104
+ });
105
+ this.watcher.on("add", (filePath) => {
106
+ if (filePath.endsWith(".json")) {
107
+ console.log(`New trace file: ${path.basename(filePath)}`);
108
+ if (this.loadTraceFile(filePath)) {
109
+ const filename = path.basename(filePath);
110
+ const trace = this.traces.get(filename);
111
+ if (trace) {
112
+ this.emit("trace-added", trace);
113
+ }
114
+ }
115
+ }
116
+ });
117
+ this.watcher.on("change", (filePath) => {
118
+ if (filePath.endsWith(".json")) {
119
+ console.log(`Trace file updated: ${path.basename(filePath)}`);
120
+ if (this.loadTraceFile(filePath)) {
121
+ const filename = path.basename(filePath);
122
+ const trace = this.traces.get(filename);
123
+ if (trace) {
124
+ this.emit("trace-updated", trace);
125
+ }
126
+ }
127
+ }
128
+ });
129
+ this.watcher.on("unlink", (filePath) => {
130
+ if (filePath.endsWith(".json")) {
131
+ const filename = path.basename(filePath);
132
+ console.log(`Trace file removed: ${filename}`);
133
+ this.traces.delete(filename);
134
+ this.emit("trace-removed", filename);
135
+ }
136
+ });
137
+ this.watcher.on("error", (error) => {
138
+ console.error("Trace watcher error:", error);
139
+ });
140
+ console.log(`Started watching traces directory: ${this.tracesDir}`);
141
+ }
142
+ getAllTraces() {
143
+ return Array.from(this.traces.values()).sort((a, b) => {
144
+ return (b.lastModified || b.timestamp) - (a.lastModified || a.timestamp);
145
+ });
146
+ }
147
+ getTrace(filename) {
148
+ return this.traces.get(filename);
149
+ }
150
+ getTracesByAgent(agentId) {
151
+ return this.getAllTraces().filter((trace) => trace.agentId === agentId);
152
+ }
153
+ getRecentTraces(limit = 50) {
154
+ return this.getAllTraces().slice(0, limit);
155
+ }
156
+ getTraceCount() {
157
+ return this.traces.size;
158
+ }
159
+ getAgentIds() {
160
+ const agentIds = /* @__PURE__ */ new Set();
161
+ for (const trace of this.traces.values()) {
162
+ agentIds.add(trace.agentId);
163
+ }
164
+ return Array.from(agentIds).sort();
165
+ }
166
+ stop() {
167
+ if (this.watcher) {
168
+ this.watcher.close();
169
+ this.watcher = void 0;
170
+ console.log("Stopped watching traces directory");
171
+ }
172
+ }
173
+ // Utility method to get trace statistics
174
+ getTraceStats() {
175
+ const total = this.traces.size;
176
+ const agentCount = this.getAgentIds().length;
177
+ const recentCount = this.getRecentTraces(24).length;
178
+ const triggers = /* @__PURE__ */ new Map();
179
+ for (const trace of this.traces.values()) {
180
+ const trigger = trace.trigger || "unknown";
181
+ triggers.set(trigger, (triggers.get(trigger) || 0) + 1);
182
+ }
183
+ return {
184
+ total,
185
+ agentCount,
186
+ recentCount,
187
+ triggers: Object.fromEntries(triggers)
188
+ };
189
+ }
190
+ };
191
+
192
+ // ../core/dist/index.js
193
+ function getFailures(graph) {
194
+ const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
195
+ return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
196
+ }
197
+ function getHungNodes(graph) {
198
+ return [...graph.nodes.values()].filter(
199
+ (node) => node.status === "running" && node.endTime === null
200
+ );
201
+ }
202
+ function getDuration(graph) {
203
+ const end = graph.endTime ?? Date.now();
204
+ return end - graph.startTime;
205
+ }
206
+ function getDepth(graph) {
207
+ const root = graph.nodes.get(graph.rootNodeId);
208
+ if (!root) return -1;
209
+ function dfs(node, depth) {
210
+ if (node.children.length === 0) return depth;
211
+ let maxDepth = depth;
212
+ for (const childId of node.children) {
213
+ const child = graph.nodes.get(childId);
214
+ if (!child) continue;
215
+ const childDepth = dfs(child, depth + 1);
216
+ if (childDepth > maxDepth) maxDepth = childDepth;
217
+ }
218
+ return maxDepth;
219
+ }
220
+ return dfs(root, 0);
221
+ }
222
+ function getStats(graph) {
223
+ const byStatus = {
224
+ running: 0,
225
+ completed: 0,
226
+ failed: 0,
227
+ hung: 0,
228
+ timeout: 0
229
+ };
230
+ const byType = {
231
+ agent: 0,
232
+ tool: 0,
233
+ subagent: 0,
234
+ wait: 0,
235
+ decision: 0,
236
+ custom: 0
237
+ };
238
+ let failureCount = 0;
239
+ let hungCount = 0;
240
+ for (const node of graph.nodes.values()) {
241
+ byStatus[node.status]++;
242
+ byType[node.type]++;
243
+ if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
244
+ failureCount++;
245
+ }
246
+ if (node.status === "running" && node.endTime === null) {
247
+ hungCount++;
248
+ }
249
+ }
250
+ return {
251
+ totalNodes: graph.nodes.size,
252
+ byStatus,
253
+ byType,
254
+ depth: getDepth(graph),
255
+ duration: getDuration(graph),
256
+ failureCount,
257
+ hungCount
258
+ };
259
+ }
260
+
261
+ // src/stats.ts
262
+ var AgentStats = class {
263
+ agentMetrics = /* @__PURE__ */ new Map();
264
+ processedTraces = /* @__PURE__ */ new Set();
265
+ processTrace(trace) {
266
+ const traceKey = `${trace.filename || trace.agentId}-${trace.timestamp}`;
267
+ if (this.processedTraces.has(traceKey)) {
268
+ return;
269
+ }
270
+ this.processedTraces.add(traceKey);
271
+ const agentId = trace.agentId;
272
+ let metrics = this.agentMetrics.get(agentId);
273
+ if (!metrics) {
274
+ metrics = {
275
+ agentId,
276
+ totalExecutions: 0,
277
+ successfulExecutions: 0,
278
+ failedExecutions: 0,
279
+ successRate: 0,
280
+ avgExecutionTime: 0,
281
+ lastExecution: 0,
282
+ triggers: {},
283
+ recentActivity: []
284
+ };
285
+ this.agentMetrics.set(agentId, metrics);
286
+ }
287
+ const analysis = this.analyzeExecution(trace);
288
+ metrics.totalExecutions++;
289
+ metrics.lastExecution = Math.max(metrics.lastExecution, trace.timestamp);
290
+ const trigger = trace.trigger || "unknown";
291
+ metrics.triggers[trigger] = (metrics.triggers[trigger] || 0) + 1;
292
+ if (analysis.success) {
293
+ metrics.successfulExecutions++;
294
+ } else {
295
+ metrics.failedExecutions++;
296
+ }
297
+ metrics.successRate = metrics.successfulExecutions / metrics.totalExecutions * 100;
298
+ if (analysis.executionTime > 0) {
299
+ const currentAvg = metrics.avgExecutionTime;
300
+ const count = metrics.totalExecutions;
301
+ metrics.avgExecutionTime = (currentAvg * (count - 1) + analysis.executionTime) / count;
302
+ }
303
+ metrics.recentActivity.push({
304
+ timestamp: trace.timestamp,
305
+ success: analysis.success,
306
+ executionTime: analysis.executionTime,
307
+ trigger
308
+ });
309
+ if (metrics.recentActivity.length > 100) {
310
+ metrics.recentActivity = metrics.recentActivity.slice(-100);
311
+ }
312
+ metrics.recentActivity.sort((a, b) => b.timestamp - a.timestamp);
313
+ }
314
+ analyzeExecution(trace) {
315
+ try {
316
+ const stats = getStats(trace);
317
+ const failures = getFailures(trace);
318
+ const hungNodes = getHungNodes(trace);
319
+ return {
320
+ success: failures.length === 0 && hungNodes.length === 0,
321
+ executionTime: stats.totalTime || 0,
322
+ nodeCount: stats.totalNodes || 0,
323
+ failureCount: failures.length,
324
+ hungCount: hungNodes.length
325
+ };
326
+ } catch (error) {
327
+ console.warn("Error analyzing trace with AgentFlow:", error);
328
+ const nodes = trace.nodes instanceof Map ? Array.from(trace.nodes.values()) : Array.isArray(trace.nodes) ? trace.nodes.map(([, node]) => node) : [];
329
+ const failedNodes = nodes.filter((node) => node.status === "failed").length;
330
+ const success = failedNodes === 0;
331
+ return {
332
+ success,
333
+ executionTime: 0,
334
+ nodeCount: nodes.length,
335
+ failureCount: failedNodes,
336
+ hungCount: 0
337
+ };
338
+ }
339
+ }
340
+ getAgentStats(agentId) {
341
+ return this.agentMetrics.get(agentId);
342
+ }
343
+ getAgentsList() {
344
+ return Array.from(this.agentMetrics.values()).sort((a, b) => b.lastExecution - a.lastExecution);
345
+ }
346
+ getGlobalStats() {
347
+ const agents = Array.from(this.agentMetrics.values());
348
+ const now = Date.now();
349
+ const oneHourAgo = now - 60 * 60 * 1e3;
350
+ const totalExecutions = agents.reduce((sum, agent) => sum + agent.totalExecutions, 0);
351
+ const totalSuccessful = agents.reduce((sum, agent) => sum + agent.successfulExecutions, 0);
352
+ const globalSuccessRate = totalExecutions > 0 ? totalSuccessful / totalExecutions * 100 : 0;
353
+ const activeAgents = agents.filter((agent) => agent.lastExecution > oneHourAgo).length;
354
+ const topAgents = agents.slice().sort((a, b) => b.totalExecutions - a.totalExecutions).slice(0, 10).map((agent) => ({
355
+ agentId: agent.agentId,
356
+ executionCount: agent.totalExecutions,
357
+ successRate: agent.successRate
358
+ }));
359
+ const recentActivity = [];
360
+ for (const agent of agents) {
361
+ for (const activity of agent.recentActivity.slice(0, 20)) {
362
+ recentActivity.push({
363
+ timestamp: activity.timestamp,
364
+ agentId: agent.agentId,
365
+ success: activity.success,
366
+ trigger: activity.trigger
367
+ });
368
+ }
369
+ }
370
+ recentActivity.sort((a, b) => b.timestamp - a.timestamp);
371
+ recentActivity.splice(200);
372
+ return {
373
+ totalAgents: agents.length,
374
+ totalExecutions,
375
+ globalSuccessRate,
376
+ activeAgents,
377
+ topAgents,
378
+ recentActivity
379
+ };
380
+ }
381
+ getPerformanceSummary() {
382
+ const global = this.getGlobalStats();
383
+ const agents = this.getAgentsList();
384
+ return {
385
+ overview: {
386
+ totalAgents: global.totalAgents,
387
+ totalExecutions: global.totalExecutions,
388
+ successRate: Math.round(global.globalSuccessRate * 100) / 100,
389
+ activeAgents: global.activeAgents
390
+ },
391
+ topPerformers: agents.slice(0, 5).map((agent) => ({
392
+ agentId: agent.agentId,
393
+ executions: agent.totalExecutions,
394
+ successRate: Math.round(agent.successRate * 100) / 100,
395
+ avgTime: Math.round(agent.avgExecutionTime * 100) / 100
396
+ })),
397
+ recentTrends: this.analyzeRecentTrends()
398
+ };
399
+ }
400
+ analyzeRecentTrends() {
401
+ const agents = Array.from(this.agentMetrics.values());
402
+ const now = Date.now();
403
+ const oneHourAgo = now - 60 * 60 * 1e3;
404
+ let recentExecutions = 0;
405
+ let recentFailures = 0;
406
+ for (const agent of agents) {
407
+ for (const activity of agent.recentActivity) {
408
+ if (activity.timestamp > oneHourAgo) {
409
+ recentExecutions++;
410
+ if (!activity.success) {
411
+ recentFailures++;
412
+ }
413
+ }
414
+ }
415
+ }
416
+ return {
417
+ hourlyExecutions: recentExecutions,
418
+ hourlyFailures: recentFailures,
419
+ hourlySuccessRate: recentExecutions > 0 ? Math.round((recentExecutions - recentFailures) / recentExecutions * 1e4) / 100 : 0
420
+ };
421
+ }
422
+ // Clear old data (useful for long-running dashboard instances)
423
+ cleanup() {
424
+ const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
425
+ for (const [agentId, metrics] of this.agentMetrics.entries()) {
426
+ metrics.recentActivity = metrics.recentActivity.filter(
427
+ (activity) => activity.timestamp > cutoff
428
+ );
429
+ if (metrics.lastExecution < cutoff) {
430
+ this.agentMetrics.delete(agentId);
431
+ }
432
+ }
433
+ console.log(`Cleaned up old metrics, ${this.agentMetrics.size} agents remaining`);
434
+ }
435
+ };
436
+
437
+ // src/server.ts
438
+ var DashboardServer = class {
439
+ constructor(config) {
440
+ this.config = config;
441
+ this.watcher = new TraceWatcher(config.tracesDir);
442
+ this.stats = new AgentStats();
443
+ this.setupExpress();
444
+ this.setupWebSocket();
445
+ this.setupTraceWatcher();
446
+ }
447
+ app = (0, import_express.default)();
448
+ server = (0, import_http.createServer)(this.app);
449
+ wss = new import_ws.WebSocketServer({ server: this.server });
450
+ watcher;
451
+ stats;
452
+ setupExpress() {
453
+ if (this.config.enableCors) {
454
+ this.app.use((req, res, next) => {
455
+ res.header("Access-Control-Allow-Origin", "*");
456
+ res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
457
+ next();
458
+ });
459
+ }
460
+ const publicDir = path2.join(__dirname, "../public");
461
+ if (fs2.existsSync(publicDir)) {
462
+ this.app.use(import_express.default.static(publicDir));
463
+ }
464
+ this.app.get("/api/traces", (req, res) => {
465
+ try {
466
+ const traces = this.watcher.getAllTraces();
467
+ res.json(traces);
468
+ } catch (error) {
469
+ res.status(500).json({ error: "Failed to load traces" });
470
+ }
471
+ });
472
+ this.app.get("/api/traces/:filename", (req, res) => {
473
+ try {
474
+ const trace = this.watcher.getTrace(req.params.filename);
475
+ if (!trace) {
476
+ return res.status(404).json({ error: "Trace not found" });
477
+ }
478
+ res.json(trace);
479
+ } catch (error) {
480
+ res.status(500).json({ error: "Failed to load trace" });
481
+ }
482
+ });
483
+ this.app.get("/api/agents", (req, res) => {
484
+ try {
485
+ const agents = this.stats.getAgentsList();
486
+ res.json(agents);
487
+ } catch (error) {
488
+ res.status(500).json({ error: "Failed to load agents" });
489
+ }
490
+ });
491
+ this.app.get("/api/stats", (req, res) => {
492
+ try {
493
+ const globalStats = this.stats.getGlobalStats();
494
+ res.json(globalStats);
495
+ } catch (error) {
496
+ res.status(500).json({ error: "Failed to load statistics" });
497
+ }
498
+ });
499
+ this.app.get("/api/stats/:agentId", (req, res) => {
500
+ try {
501
+ const agentStats = this.stats.getAgentStats(req.params.agentId);
502
+ if (!agentStats) {
503
+ return res.status(404).json({ error: "Agent not found" });
504
+ }
505
+ res.json(agentStats);
506
+ } catch (error) {
507
+ res.status(500).json({ error: "Failed to load agent statistics" });
508
+ }
509
+ });
510
+ this.app.get("*", (req, res) => {
511
+ const indexPath = path2.join(__dirname, "../public/index.html");
512
+ if (fs2.existsSync(indexPath)) {
513
+ res.sendFile(indexPath);
514
+ } else {
515
+ res.status(404).send("Dashboard not found - public files may not be built");
516
+ }
517
+ });
518
+ }
519
+ setupWebSocket() {
520
+ this.wss.on("connection", (ws) => {
521
+ console.log("Dashboard client connected");
522
+ ws.send(JSON.stringify({
523
+ type: "init",
524
+ data: {
525
+ traces: this.watcher.getAllTraces(),
526
+ stats: this.stats.getGlobalStats()
527
+ }
528
+ }));
529
+ ws.on("close", () => {
530
+ console.log("Dashboard client disconnected");
531
+ });
532
+ ws.on("error", (error) => {
533
+ console.error("WebSocket error:", error);
534
+ });
535
+ });
536
+ }
537
+ setupTraceWatcher() {
538
+ this.watcher.on("trace-added", (trace) => {
539
+ this.stats.processTrace(trace);
540
+ this.broadcast({
541
+ type: "trace-added",
542
+ data: trace
543
+ });
544
+ });
545
+ this.watcher.on("trace-updated", (trace) => {
546
+ this.stats.processTrace(trace);
547
+ this.broadcast({
548
+ type: "trace-updated",
549
+ data: trace
550
+ });
551
+ });
552
+ this.watcher.on("stats-updated", () => {
553
+ this.broadcast({
554
+ type: "stats-updated",
555
+ data: this.stats.getGlobalStats()
556
+ });
557
+ });
558
+ }
559
+ broadcast(message) {
560
+ this.wss.clients.forEach((client) => {
561
+ if (client.readyState === 1) {
562
+ client.send(JSON.stringify(message));
563
+ }
564
+ });
565
+ }
566
+ async start() {
567
+ return new Promise((resolve2) => {
568
+ const host = this.config.host || "localhost";
569
+ this.server.listen(this.config.port, host, () => {
570
+ console.log(`AgentFlow Dashboard running at http://${host}:${this.config.port}`);
571
+ console.log(`Watching traces in: ${this.config.tracesDir}`);
572
+ resolve2();
573
+ });
574
+ });
575
+ }
576
+ async stop() {
577
+ return new Promise((resolve2) => {
578
+ this.watcher.stop();
579
+ this.server.close(() => {
580
+ console.log("Dashboard server stopped");
581
+ resolve2();
582
+ });
583
+ });
584
+ }
585
+ getStats() {
586
+ return this.stats.getGlobalStats();
587
+ }
588
+ getTraces() {
589
+ return this.watcher.getAllTraces();
590
+ }
591
+ };
592
+ // Annotate the CommonJS export names for ESM import in node:
593
+ 0 && (module.exports = {
594
+ AgentStats,
595
+ DashboardServer,
596
+ TraceWatcher
597
+ });