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/README.md +366 -0
- package/bin/dashboard.js +12 -0
- package/dist/index.cjs +597 -0
- package/dist/index.js +560 -0
- package/dist/public/dashboard.js +342 -0
- package/dist/public/index.html +274 -0
- package/package.json +51 -0
- package/public/dashboard.js +342 -0
- package/public/index.html +274 -0
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
|
+
});
|