agentflow-dashboard 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.
package/bin/dashboard.js CHANGED
@@ -1,12 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // Import and start the CLI
4
- import('../dist/cli.js').then(({ startDashboard }) => {
4
+ import('../dist/cli.js')
5
+ .then(({ startDashboard }) => {
5
6
  startDashboard().catch((error) => {
6
- console.error('Failed to start AgentFlow Dashboard:', error);
7
- process.exit(1);
7
+ console.error('Failed to start AgentFlow Dashboard:', error);
8
+ process.exit(1);
8
9
  });
9
- }).catch((error) => {
10
+ })
11
+ .catch((error) => {
10
12
  console.error('Failed to load AgentFlow Dashboard:', error);
11
13
  process.exit(1);
12
- });
14
+ });
@@ -0,0 +1,515 @@
1
+ // src/stats.ts
2
+ import { getFailures, getHungNodes, getStats } from "agentflow-core";
3
+ var AgentStats = class {
4
+ agentMetrics = /* @__PURE__ */ new Map();
5
+ processedTraces = /* @__PURE__ */ new Set();
6
+ processTrace(trace) {
7
+ const traceKey = `${trace.filename || trace.agentId}-${trace.startTime}`;
8
+ if (this.processedTraces.has(traceKey)) {
9
+ return;
10
+ }
11
+ this.processedTraces.add(traceKey);
12
+ const agentId = trace.agentId;
13
+ let metrics = this.agentMetrics.get(agentId);
14
+ if (!metrics) {
15
+ metrics = {
16
+ agentId,
17
+ totalExecutions: 0,
18
+ successfulExecutions: 0,
19
+ failedExecutions: 0,
20
+ successRate: 0,
21
+ avgExecutionTime: 0,
22
+ lastExecution: 0,
23
+ triggers: {},
24
+ recentActivity: []
25
+ };
26
+ this.agentMetrics.set(agentId, metrics);
27
+ }
28
+ const analysis = this.analyzeExecution(trace);
29
+ metrics.totalExecutions++;
30
+ metrics.lastExecution = Math.max(metrics.lastExecution, trace.startTime);
31
+ const trigger = trace.trigger || "unknown";
32
+ metrics.triggers[trigger] = (metrics.triggers[trigger] || 0) + 1;
33
+ if (analysis.success) {
34
+ metrics.successfulExecutions++;
35
+ } else {
36
+ metrics.failedExecutions++;
37
+ }
38
+ metrics.successRate = metrics.successfulExecutions / metrics.totalExecutions * 100;
39
+ if (analysis.executionTime > 0) {
40
+ const currentAvg = metrics.avgExecutionTime;
41
+ const count = metrics.totalExecutions;
42
+ metrics.avgExecutionTime = (currentAvg * (count - 1) + analysis.executionTime) / count;
43
+ }
44
+ metrics.recentActivity.push({
45
+ timestamp: trace.startTime,
46
+ success: analysis.success,
47
+ executionTime: analysis.executionTime,
48
+ trigger
49
+ });
50
+ if (metrics.recentActivity.length > 100) {
51
+ metrics.recentActivity = metrics.recentActivity.slice(-100);
52
+ }
53
+ metrics.recentActivity.sort((a, b) => b.timestamp - a.timestamp);
54
+ }
55
+ analyzeExecution(trace) {
56
+ try {
57
+ const stats = getStats(trace);
58
+ const failures = getFailures(trace);
59
+ const hungNodes = getHungNodes(trace);
60
+ return {
61
+ success: failures.length === 0 && hungNodes.length === 0,
62
+ executionTime: stats.duration || 0,
63
+ nodeCount: stats.totalNodes || 0,
64
+ failureCount: failures.length,
65
+ hungCount: hungNodes.length
66
+ };
67
+ } catch (error) {
68
+ console.warn("Error analyzing trace with AgentFlow:", error);
69
+ const nodes = trace.nodes instanceof Map ? Array.from(trace.nodes.values()) : [];
70
+ const failedNodes = nodes.filter((node) => node.status === "failed").length;
71
+ return {
72
+ success: failedNodes === 0,
73
+ executionTime: 0,
74
+ nodeCount: nodes.length,
75
+ failureCount: failedNodes,
76
+ hungCount: 0
77
+ };
78
+ }
79
+ }
80
+ getAgentStats(agentId) {
81
+ return this.agentMetrics.get(agentId);
82
+ }
83
+ getAgentsList() {
84
+ return Array.from(this.agentMetrics.values()).sort((a, b) => b.lastExecution - a.lastExecution);
85
+ }
86
+ getGlobalStats() {
87
+ const agents = Array.from(this.agentMetrics.values());
88
+ const now = Date.now();
89
+ const oneHourAgo = now - 60 * 60 * 1e3;
90
+ const totalExecutions = agents.reduce((sum, agent) => sum + agent.totalExecutions, 0);
91
+ const totalSuccessful = agents.reduce((sum, agent) => sum + agent.successfulExecutions, 0);
92
+ const globalSuccessRate = totalExecutions > 0 ? totalSuccessful / totalExecutions * 100 : 0;
93
+ const activeAgents = agents.filter((agent) => agent.lastExecution > oneHourAgo).length;
94
+ const topAgents = agents.slice().sort((a, b) => b.totalExecutions - a.totalExecutions).slice(0, 10).map((agent) => ({
95
+ agentId: agent.agentId,
96
+ executionCount: agent.totalExecutions,
97
+ successRate: agent.successRate
98
+ }));
99
+ const recentActivity = [];
100
+ for (const agent of agents) {
101
+ for (const activity of agent.recentActivity.slice(0, 20)) {
102
+ recentActivity.push({
103
+ timestamp: activity.timestamp,
104
+ agentId: agent.agentId,
105
+ success: activity.success,
106
+ trigger: activity.trigger
107
+ });
108
+ }
109
+ }
110
+ recentActivity.sort((a, b) => b.timestamp - a.timestamp);
111
+ recentActivity.splice(200);
112
+ return {
113
+ totalAgents: agents.length,
114
+ totalExecutions,
115
+ globalSuccessRate,
116
+ activeAgents,
117
+ topAgents,
118
+ recentActivity
119
+ };
120
+ }
121
+ getPerformanceSummary() {
122
+ const global = this.getGlobalStats();
123
+ const agents = this.getAgentsList();
124
+ return {
125
+ overview: {
126
+ totalAgents: global.totalAgents,
127
+ totalExecutions: global.totalExecutions,
128
+ successRate: Math.round(global.globalSuccessRate * 100) / 100,
129
+ activeAgents: global.activeAgents
130
+ },
131
+ topPerformers: agents.slice(0, 5).map((agent) => ({
132
+ agentId: agent.agentId,
133
+ executions: agent.totalExecutions,
134
+ successRate: Math.round(agent.successRate * 100) / 100,
135
+ avgTime: Math.round(agent.avgExecutionTime * 100) / 100
136
+ })),
137
+ recentTrends: this.analyzeRecentTrends()
138
+ };
139
+ }
140
+ analyzeRecentTrends() {
141
+ const agents = Array.from(this.agentMetrics.values());
142
+ const now = Date.now();
143
+ const oneHourAgo = now - 60 * 60 * 1e3;
144
+ let recentExecutions = 0;
145
+ let recentFailures = 0;
146
+ for (const agent of agents) {
147
+ for (const activity of agent.recentActivity) {
148
+ if (activity.timestamp > oneHourAgo) {
149
+ recentExecutions++;
150
+ if (!activity.success) {
151
+ recentFailures++;
152
+ }
153
+ }
154
+ }
155
+ }
156
+ return {
157
+ hourlyExecutions: recentExecutions,
158
+ hourlyFailures: recentFailures,
159
+ hourlySuccessRate: recentExecutions > 0 ? Math.round((recentExecutions - recentFailures) / recentExecutions * 1e4) / 100 : 0
160
+ };
161
+ }
162
+ cleanup() {
163
+ const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
164
+ for (const [agentId, metrics] of this.agentMetrics.entries()) {
165
+ metrics.recentActivity = metrics.recentActivity.filter(
166
+ (activity) => activity.timestamp > cutoff
167
+ );
168
+ if (metrics.lastExecution < cutoff) {
169
+ this.agentMetrics.delete(agentId);
170
+ }
171
+ }
172
+ console.log(`Cleaned up old metrics, ${this.agentMetrics.size} agents remaining`);
173
+ }
174
+ };
175
+
176
+ // src/watcher.ts
177
+ import { loadGraph } from "agentflow-core";
178
+ import chokidar from "chokidar";
179
+ import { EventEmitter } from "events";
180
+ import * as fs from "fs";
181
+ import * as path from "path";
182
+ var TraceWatcher = class extends EventEmitter {
183
+ watcher;
184
+ traces = /* @__PURE__ */ new Map();
185
+ tracesDir;
186
+ constructor(tracesDir) {
187
+ super();
188
+ this.tracesDir = path.resolve(tracesDir);
189
+ this.ensureTracesDir();
190
+ this.loadExistingTraces();
191
+ this.startWatching();
192
+ }
193
+ ensureTracesDir() {
194
+ if (!fs.existsSync(this.tracesDir)) {
195
+ fs.mkdirSync(this.tracesDir, { recursive: true });
196
+ console.log(`Created traces directory: ${this.tracesDir}`);
197
+ }
198
+ }
199
+ loadExistingTraces() {
200
+ try {
201
+ const files = fs.readdirSync(this.tracesDir).filter((file) => file.endsWith(".json"));
202
+ console.log(`Loading ${files.length} existing trace files...`);
203
+ for (const file of files) {
204
+ this.loadTraceFile(path.join(this.tracesDir, file));
205
+ }
206
+ console.log(`Loaded ${this.traces.size} traces`);
207
+ } catch (error) {
208
+ console.error("Error loading existing traces:", error);
209
+ }
210
+ }
211
+ loadTraceFile(filePath) {
212
+ try {
213
+ const content = fs.readFileSync(filePath, "utf8");
214
+ const graph = loadGraph(content);
215
+ const filename = path.basename(filePath);
216
+ const stats = fs.statSync(filePath);
217
+ graph.filename = filename;
218
+ graph.lastModified = stats.mtime.getTime();
219
+ this.traces.set(filename, graph);
220
+ return true;
221
+ } catch (error) {
222
+ console.error(`Error loading trace file ${filePath}:`, error);
223
+ return false;
224
+ }
225
+ }
226
+ startWatching() {
227
+ this.watcher = chokidar.watch(this.tracesDir, {
228
+ ignored: /^\./,
229
+ persistent: true,
230
+ ignoreInitial: true
231
+ });
232
+ this.watcher.on("add", (filePath) => {
233
+ if (filePath.endsWith(".json")) {
234
+ console.log(`New trace file: ${path.basename(filePath)}`);
235
+ if (this.loadTraceFile(filePath)) {
236
+ const filename = path.basename(filePath);
237
+ const trace = this.traces.get(filename);
238
+ if (trace) {
239
+ this.emit("trace-added", trace);
240
+ }
241
+ }
242
+ }
243
+ });
244
+ this.watcher.on("change", (filePath) => {
245
+ if (filePath.endsWith(".json")) {
246
+ console.log(`Trace file updated: ${path.basename(filePath)}`);
247
+ if (this.loadTraceFile(filePath)) {
248
+ const filename = path.basename(filePath);
249
+ const trace = this.traces.get(filename);
250
+ if (trace) {
251
+ this.emit("trace-updated", trace);
252
+ }
253
+ }
254
+ }
255
+ });
256
+ this.watcher.on("unlink", (filePath) => {
257
+ if (filePath.endsWith(".json")) {
258
+ const filename = path.basename(filePath);
259
+ console.log(`Trace file removed: ${filename}`);
260
+ this.traces.delete(filename);
261
+ this.emit("trace-removed", filename);
262
+ }
263
+ });
264
+ this.watcher.on("error", (error) => {
265
+ console.error("Trace watcher error:", error);
266
+ });
267
+ console.log(`Started watching traces directory: ${this.tracesDir}`);
268
+ }
269
+ getAllTraces() {
270
+ return Array.from(this.traces.values()).sort((a, b) => {
271
+ return (b.lastModified || b.startTime) - (a.lastModified || a.startTime);
272
+ });
273
+ }
274
+ getTrace(filename) {
275
+ return this.traces.get(filename);
276
+ }
277
+ getTracesByAgent(agentId) {
278
+ return this.getAllTraces().filter((trace) => trace.agentId === agentId);
279
+ }
280
+ getRecentTraces(limit = 50) {
281
+ return this.getAllTraces().slice(0, limit);
282
+ }
283
+ getTraceCount() {
284
+ return this.traces.size;
285
+ }
286
+ getAgentIds() {
287
+ const agentIds = /* @__PURE__ */ new Set();
288
+ for (const trace of this.traces.values()) {
289
+ agentIds.add(trace.agentId);
290
+ }
291
+ return Array.from(agentIds).sort();
292
+ }
293
+ stop() {
294
+ if (this.watcher) {
295
+ this.watcher.close();
296
+ this.watcher = void 0;
297
+ console.log("Stopped watching traces directory");
298
+ }
299
+ }
300
+ getTraceStats() {
301
+ const total = this.traces.size;
302
+ const agentCount = this.getAgentIds().length;
303
+ const recentCount = this.getRecentTraces(24).length;
304
+ const triggers = /* @__PURE__ */ new Map();
305
+ for (const trace of this.traces.values()) {
306
+ const trigger = trace.trigger || "unknown";
307
+ triggers.set(trigger, (triggers.get(trigger) || 0) + 1);
308
+ }
309
+ return {
310
+ total,
311
+ agentCount,
312
+ recentCount,
313
+ triggers: Object.fromEntries(triggers)
314
+ };
315
+ }
316
+ };
317
+
318
+ // src/server.ts
319
+ import express from "express";
320
+ import * as fs2 from "fs";
321
+ import { createServer } from "http";
322
+ import * as path2 from "path";
323
+ import { fileURLToPath } from "url";
324
+ import { WebSocketServer } from "ws";
325
+ import { discoverProcessConfig, auditProcesses } from "agentflow-core";
326
+ var __filename = fileURLToPath(import.meta.url);
327
+ var __dirname = path2.dirname(__filename);
328
+ var DashboardServer = class {
329
+ constructor(config) {
330
+ this.config = config;
331
+ this.watcher = new TraceWatcher(config.tracesDir);
332
+ this.stats = new AgentStats();
333
+ this.setupExpress();
334
+ this.setupWebSocket();
335
+ this.setupTraceWatcher();
336
+ }
337
+ app = express();
338
+ server = createServer(this.app);
339
+ wss = new WebSocketServer({ server: this.server });
340
+ watcher;
341
+ stats;
342
+ processHealthCache = { result: null, ts: 0 };
343
+ setupExpress() {
344
+ if (this.config.enableCors) {
345
+ this.app.use((req, res, next) => {
346
+ res.header("Access-Control-Allow-Origin", "*");
347
+ res.header(
348
+ "Access-Control-Allow-Headers",
349
+ "Origin, X-Requested-With, Content-Type, Accept"
350
+ );
351
+ next();
352
+ });
353
+ }
354
+ const publicDir = path2.join(__dirname, "../public");
355
+ if (fs2.existsSync(publicDir)) {
356
+ this.app.use(express.static(publicDir));
357
+ }
358
+ this.app.get("/api/traces", (req, res) => {
359
+ try {
360
+ const traces = this.watcher.getAllTraces();
361
+ res.json(traces);
362
+ } catch (error) {
363
+ res.status(500).json({ error: "Failed to load traces" });
364
+ }
365
+ });
366
+ this.app.get("/api/traces/:filename", (req, res) => {
367
+ try {
368
+ const trace = this.watcher.getTrace(req.params.filename);
369
+ if (!trace) {
370
+ return res.status(404).json({ error: "Trace not found" });
371
+ }
372
+ res.json(trace);
373
+ } catch (error) {
374
+ res.status(500).json({ error: "Failed to load trace" });
375
+ }
376
+ });
377
+ this.app.get("/api/agents", (req, res) => {
378
+ try {
379
+ const agents = this.stats.getAgentsList();
380
+ res.json(agents);
381
+ } catch (error) {
382
+ res.status(500).json({ error: "Failed to load agents" });
383
+ }
384
+ });
385
+ this.app.get("/api/stats", (req, res) => {
386
+ try {
387
+ const globalStats = this.stats.getGlobalStats();
388
+ res.json(globalStats);
389
+ } catch (error) {
390
+ res.status(500).json({ error: "Failed to load statistics" });
391
+ }
392
+ });
393
+ this.app.get("/api/stats/:agentId", (req, res) => {
394
+ try {
395
+ const agentStats = this.stats.getAgentStats(req.params.agentId);
396
+ if (!agentStats) {
397
+ return res.status(404).json({ error: "Agent not found" });
398
+ }
399
+ res.json(agentStats);
400
+ } catch (error) {
401
+ res.status(500).json({ error: "Failed to load agent statistics" });
402
+ }
403
+ });
404
+ this.app.get("/api/process-health", (req, res) => {
405
+ try {
406
+ const now = Date.now();
407
+ if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
408
+ return res.json(this.processHealthCache.result);
409
+ }
410
+ const discoveryDirs = [
411
+ this.config.tracesDir,
412
+ path2.dirname(this.config.tracesDir),
413
+ ...this.config.dataDirs || []
414
+ ];
415
+ const processConfig = discoverProcessConfig(discoveryDirs);
416
+ if (!processConfig) {
417
+ return res.json(null);
418
+ }
419
+ const result = auditProcesses(processConfig);
420
+ this.processHealthCache = { result, ts: now };
421
+ res.json(result);
422
+ } catch (error) {
423
+ res.status(500).json({ error: "Failed to audit processes" });
424
+ }
425
+ });
426
+ this.app.get("*", (req, res) => {
427
+ const indexPath = path2.join(__dirname, "../public/index.html");
428
+ if (fs2.existsSync(indexPath)) {
429
+ res.sendFile(indexPath);
430
+ } else {
431
+ res.status(404).send("Dashboard not found - public files may not be built");
432
+ }
433
+ });
434
+ }
435
+ setupWebSocket() {
436
+ this.wss.on("connection", (ws) => {
437
+ console.log("Dashboard client connected");
438
+ ws.send(
439
+ JSON.stringify({
440
+ type: "init",
441
+ data: {
442
+ traces: this.watcher.getAllTraces(),
443
+ stats: this.stats.getGlobalStats()
444
+ }
445
+ })
446
+ );
447
+ ws.on("close", () => {
448
+ console.log("Dashboard client disconnected");
449
+ });
450
+ ws.on("error", (error) => {
451
+ console.error("WebSocket error:", error);
452
+ });
453
+ });
454
+ }
455
+ setupTraceWatcher() {
456
+ this.watcher.on("trace-added", (trace) => {
457
+ this.stats.processTrace(trace);
458
+ this.broadcast({
459
+ type: "trace-added",
460
+ data: trace
461
+ });
462
+ });
463
+ this.watcher.on("trace-updated", (trace) => {
464
+ this.stats.processTrace(trace);
465
+ this.broadcast({
466
+ type: "trace-updated",
467
+ data: trace
468
+ });
469
+ });
470
+ this.watcher.on("stats-updated", () => {
471
+ this.broadcast({
472
+ type: "stats-updated",
473
+ data: this.stats.getGlobalStats()
474
+ });
475
+ });
476
+ }
477
+ broadcast(message) {
478
+ this.wss.clients.forEach((client) => {
479
+ if (client.readyState === 1) {
480
+ client.send(JSON.stringify(message));
481
+ }
482
+ });
483
+ }
484
+ async start() {
485
+ return new Promise((resolve2) => {
486
+ const host = this.config.host || "localhost";
487
+ this.server.listen(this.config.port, host, () => {
488
+ console.log(`AgentFlow Dashboard running at http://${host}:${this.config.port}`);
489
+ console.log(`Watching traces in: ${this.config.tracesDir}`);
490
+ resolve2();
491
+ });
492
+ });
493
+ }
494
+ async stop() {
495
+ return new Promise((resolve2) => {
496
+ this.watcher.stop();
497
+ this.server.close(() => {
498
+ console.log("Dashboard server stopped");
499
+ resolve2();
500
+ });
501
+ });
502
+ }
503
+ getStats() {
504
+ return this.stats.getGlobalStats();
505
+ }
506
+ getTraces() {
507
+ return this.watcher.getAllTraces();
508
+ }
509
+ };
510
+
511
+ export {
512
+ AgentStats,
513
+ TraceWatcher,
514
+ DashboardServer
515
+ };