agentflow-dashboard 0.1.3 → 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 +7 -5
- package/dist/chunk-L24LYP6L.js +515 -0
- package/dist/cli.cjs +665 -0
- package/dist/cli.js +120 -0
- package/dist/index.cjs +191 -236
- package/dist/index.js +5 -557
- package/dist/public/dashboard.js +365 -253
- package/dist/public/index.html +115 -0
- package/package.json +5 -4
- package/public/dashboard.js +365 -253
- package/public/index.html +115 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DashboardServer
|
|
3
|
+
} from "./chunk-L24LYP6L.js";
|
|
4
|
+
|
|
5
|
+
// src/cli.ts
|
|
6
|
+
import * as fs from "fs";
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
async function startDashboard() {
|
|
9
|
+
var _a;
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const config = {
|
|
12
|
+
port: 3e3,
|
|
13
|
+
tracesDir: "./traces",
|
|
14
|
+
host: "localhost",
|
|
15
|
+
enableCors: false
|
|
16
|
+
};
|
|
17
|
+
for (let i = 0; i < args.length; i++) {
|
|
18
|
+
switch (args[i]) {
|
|
19
|
+
case "--port":
|
|
20
|
+
case "-p":
|
|
21
|
+
config.port = parseInt(args[++i]) || 3e3;
|
|
22
|
+
break;
|
|
23
|
+
case "--traces":
|
|
24
|
+
case "-t":
|
|
25
|
+
config.tracesDir = args[++i];
|
|
26
|
+
break;
|
|
27
|
+
case "--host":
|
|
28
|
+
case "-h":
|
|
29
|
+
config.host = args[++i];
|
|
30
|
+
break;
|
|
31
|
+
case "--data-dir":
|
|
32
|
+
if (!config.dataDirs) config.dataDirs = [];
|
|
33
|
+
config.dataDirs.push(args[++i]);
|
|
34
|
+
break;
|
|
35
|
+
case "--cors":
|
|
36
|
+
config.enableCors = true;
|
|
37
|
+
break;
|
|
38
|
+
case "--help":
|
|
39
|
+
printHelp();
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const tracesPath = path.resolve(config.tracesDir);
|
|
44
|
+
if (!fs.existsSync(tracesPath)) {
|
|
45
|
+
console.log(`Traces directory doesn't exist: ${tracesPath}`);
|
|
46
|
+
console.log("Creating traces directory...");
|
|
47
|
+
fs.mkdirSync(tracesPath, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
config.tracesDir = tracesPath;
|
|
50
|
+
console.log("\u{1F680} Starting AgentFlow Dashboard...");
|
|
51
|
+
console.log(` Port: ${config.port}`);
|
|
52
|
+
console.log(` Host: ${config.host}`);
|
|
53
|
+
console.log(` Traces: ${config.tracesDir}`);
|
|
54
|
+
console.log(` CORS: ${config.enableCors ? "enabled" : "disabled"}`);
|
|
55
|
+
if ((_a = config.dataDirs) == null ? void 0 : _a.length) {
|
|
56
|
+
console.log(` Data dirs: ${config.dataDirs.join(", ")}`);
|
|
57
|
+
}
|
|
58
|
+
const dashboard = new DashboardServer(config);
|
|
59
|
+
process.on("SIGINT", async () => {
|
|
60
|
+
console.log("\\n\u{1F6D1} Shutting down dashboard...");
|
|
61
|
+
await dashboard.stop();
|
|
62
|
+
process.exit(0);
|
|
63
|
+
});
|
|
64
|
+
process.on("SIGTERM", async () => {
|
|
65
|
+
console.log("\\n\u{1F6D1} Received SIGTERM, shutting down...");
|
|
66
|
+
await dashboard.stop();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
await dashboard.start();
|
|
71
|
+
console.log("\u2705 Dashboard started successfully!");
|
|
72
|
+
console.log(` Open: http://${config.host}:${config.port}`);
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
const stats = dashboard.getStats();
|
|
75
|
+
const traces = dashboard.getTraces();
|
|
76
|
+
console.log(`\\n\u{1F4CA} Dashboard Status:`);
|
|
77
|
+
console.log(` Total Traces: ${traces.length}`);
|
|
78
|
+
console.log(` Total Agents: ${stats.totalAgents}`);
|
|
79
|
+
console.log(` Success Rate: ${stats.globalSuccessRate.toFixed(1)}%`);
|
|
80
|
+
console.log(` Active Agents: ${stats.activeAgents}`);
|
|
81
|
+
}, 1e3);
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.error("\u274C Failed to start dashboard:", error);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function printHelp() {
|
|
88
|
+
console.log(`
|
|
89
|
+
\u{1F50D} AgentFlow Dashboard - Real-time monitoring for AI agent executions
|
|
90
|
+
|
|
91
|
+
Usage:
|
|
92
|
+
agentflow-dashboard [options]
|
|
93
|
+
npx agentflow-dashboard [options]
|
|
94
|
+
|
|
95
|
+
Options:
|
|
96
|
+
-p, --port <number> Server port (default: 3000)
|
|
97
|
+
-t, --traces <path> Traces directory (default: ./traces)
|
|
98
|
+
-h, --host <address> Host address (default: localhost)
|
|
99
|
+
--data-dir <path> Extra directory for process discovery (repeatable)
|
|
100
|
+
--cors Enable CORS headers
|
|
101
|
+
--help Show this help message
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
agentflow-dashboard --port 8080 --traces /var/log/agentflow
|
|
105
|
+
agentflow-dashboard --host 0.0.0.0 --cors
|
|
106
|
+
agentflow-dashboard --traces ./my-agent-traces
|
|
107
|
+
|
|
108
|
+
Features:
|
|
109
|
+
\u2728 Real-time trace monitoring
|
|
110
|
+
\u{1F4CA} Agent performance analytics
|
|
111
|
+
\u{1F3AF} Execution graph visualization
|
|
112
|
+
\u{1F4C8} Success/failure tracking
|
|
113
|
+
\u{1F50D} Multi-agent system overview
|
|
114
|
+
|
|
115
|
+
Visit: https://github.com/ClemenceChee/AgentFlow
|
|
116
|
+
`);
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
startDashboard
|
|
120
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -38,233 +38,20 @@ module.exports = __toCommonJS(index_exports);
|
|
|
38
38
|
|
|
39
39
|
// src/server.ts
|
|
40
40
|
var import_express = __toESM(require("express"), 1);
|
|
41
|
-
var
|
|
41
|
+
var fs2 = __toESM(require("fs"), 1);
|
|
42
42
|
var import_http = require("http");
|
|
43
43
|
var path2 = __toESM(require("path"), 1);
|
|
44
|
-
var fs2 = __toESM(require("fs"), 1);
|
|
45
44
|
var import_url = require("url");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
49
|
-
var fs = __toESM(require("fs"), 1);
|
|
50
|
-
var path = __toESM(require("path"), 1);
|
|
51
|
-
var import_events = require("events");
|
|
52
|
-
var TraceWatcher = class extends import_events.EventEmitter {
|
|
53
|
-
watcher;
|
|
54
|
-
traces = /* @__PURE__ */ new Map();
|
|
55
|
-
tracesDir;
|
|
56
|
-
constructor(tracesDir) {
|
|
57
|
-
super();
|
|
58
|
-
this.tracesDir = path.resolve(tracesDir);
|
|
59
|
-
this.ensureTracesDir();
|
|
60
|
-
this.loadExistingTraces();
|
|
61
|
-
this.startWatching();
|
|
62
|
-
}
|
|
63
|
-
ensureTracesDir() {
|
|
64
|
-
if (!fs.existsSync(this.tracesDir)) {
|
|
65
|
-
fs.mkdirSync(this.tracesDir, { recursive: true });
|
|
66
|
-
console.log(`Created traces directory: ${this.tracesDir}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
loadExistingTraces() {
|
|
70
|
-
try {
|
|
71
|
-
const files = fs.readdirSync(this.tracesDir).filter((file) => file.endsWith(".json"));
|
|
72
|
-
console.log(`Loading ${files.length} existing trace files...`);
|
|
73
|
-
for (const file of files) {
|
|
74
|
-
this.loadTraceFile(path.join(this.tracesDir, file));
|
|
75
|
-
}
|
|
76
|
-
console.log(`Loaded ${this.traces.size} traces`);
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error("Error loading existing traces:", error);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
loadTraceFile(filePath) {
|
|
82
|
-
try {
|
|
83
|
-
const content = fs.readFileSync(filePath, "utf8");
|
|
84
|
-
const trace = JSON.parse(content);
|
|
85
|
-
const filename = path.basename(filePath);
|
|
86
|
-
const stats = fs.statSync(filePath);
|
|
87
|
-
trace.filename = filename;
|
|
88
|
-
trace.lastModified = stats.mtime.getTime();
|
|
89
|
-
if (Array.isArray(trace.nodes)) {
|
|
90
|
-
const nodesMap = new Map(trace.nodes);
|
|
91
|
-
trace.nodes = nodesMap;
|
|
92
|
-
}
|
|
93
|
-
this.traces.set(filename, trace);
|
|
94
|
-
return true;
|
|
95
|
-
} catch (error) {
|
|
96
|
-
console.error(`Error loading trace file ${filePath}:`, error);
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
startWatching() {
|
|
101
|
-
this.watcher = import_chokidar.default.watch(this.tracesDir, {
|
|
102
|
-
ignored: /^\\./,
|
|
103
|
-
persistent: true,
|
|
104
|
-
ignoreInitial: true
|
|
105
|
-
});
|
|
106
|
-
this.watcher.on("add", (filePath) => {
|
|
107
|
-
if (filePath.endsWith(".json")) {
|
|
108
|
-
console.log(`New trace file: ${path.basename(filePath)}`);
|
|
109
|
-
if (this.loadTraceFile(filePath)) {
|
|
110
|
-
const filename = path.basename(filePath);
|
|
111
|
-
const trace = this.traces.get(filename);
|
|
112
|
-
if (trace) {
|
|
113
|
-
this.emit("trace-added", trace);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
});
|
|
118
|
-
this.watcher.on("change", (filePath) => {
|
|
119
|
-
if (filePath.endsWith(".json")) {
|
|
120
|
-
console.log(`Trace file updated: ${path.basename(filePath)}`);
|
|
121
|
-
if (this.loadTraceFile(filePath)) {
|
|
122
|
-
const filename = path.basename(filePath);
|
|
123
|
-
const trace = this.traces.get(filename);
|
|
124
|
-
if (trace) {
|
|
125
|
-
this.emit("trace-updated", trace);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
this.watcher.on("unlink", (filePath) => {
|
|
131
|
-
if (filePath.endsWith(".json")) {
|
|
132
|
-
const filename = path.basename(filePath);
|
|
133
|
-
console.log(`Trace file removed: ${filename}`);
|
|
134
|
-
this.traces.delete(filename);
|
|
135
|
-
this.emit("trace-removed", filename);
|
|
136
|
-
}
|
|
137
|
-
});
|
|
138
|
-
this.watcher.on("error", (error) => {
|
|
139
|
-
console.error("Trace watcher error:", error);
|
|
140
|
-
});
|
|
141
|
-
console.log(`Started watching traces directory: ${this.tracesDir}`);
|
|
142
|
-
}
|
|
143
|
-
getAllTraces() {
|
|
144
|
-
return Array.from(this.traces.values()).sort((a, b) => {
|
|
145
|
-
return (b.lastModified || b.timestamp) - (a.lastModified || a.timestamp);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
getTrace(filename) {
|
|
149
|
-
return this.traces.get(filename);
|
|
150
|
-
}
|
|
151
|
-
getTracesByAgent(agentId) {
|
|
152
|
-
return this.getAllTraces().filter((trace) => trace.agentId === agentId);
|
|
153
|
-
}
|
|
154
|
-
getRecentTraces(limit = 50) {
|
|
155
|
-
return this.getAllTraces().slice(0, limit);
|
|
156
|
-
}
|
|
157
|
-
getTraceCount() {
|
|
158
|
-
return this.traces.size;
|
|
159
|
-
}
|
|
160
|
-
getAgentIds() {
|
|
161
|
-
const agentIds = /* @__PURE__ */ new Set();
|
|
162
|
-
for (const trace of this.traces.values()) {
|
|
163
|
-
agentIds.add(trace.agentId);
|
|
164
|
-
}
|
|
165
|
-
return Array.from(agentIds).sort();
|
|
166
|
-
}
|
|
167
|
-
stop() {
|
|
168
|
-
if (this.watcher) {
|
|
169
|
-
this.watcher.close();
|
|
170
|
-
this.watcher = void 0;
|
|
171
|
-
console.log("Stopped watching traces directory");
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
// Utility method to get trace statistics
|
|
175
|
-
getTraceStats() {
|
|
176
|
-
const total = this.traces.size;
|
|
177
|
-
const agentCount = this.getAgentIds().length;
|
|
178
|
-
const recentCount = this.getRecentTraces(24).length;
|
|
179
|
-
const triggers = /* @__PURE__ */ new Map();
|
|
180
|
-
for (const trace of this.traces.values()) {
|
|
181
|
-
const trigger = trace.trigger || "unknown";
|
|
182
|
-
triggers.set(trigger, (triggers.get(trigger) || 0) + 1);
|
|
183
|
-
}
|
|
184
|
-
return {
|
|
185
|
-
total,
|
|
186
|
-
agentCount,
|
|
187
|
-
recentCount,
|
|
188
|
-
triggers: Object.fromEntries(triggers)
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// ../core/dist/index.js
|
|
194
|
-
function getFailures(graph) {
|
|
195
|
-
const failureStatuses = /* @__PURE__ */ new Set(["failed", "hung", "timeout"]);
|
|
196
|
-
return [...graph.nodes.values()].filter((node) => failureStatuses.has(node.status));
|
|
197
|
-
}
|
|
198
|
-
function getHungNodes(graph) {
|
|
199
|
-
return [...graph.nodes.values()].filter(
|
|
200
|
-
(node) => node.status === "running" && node.endTime === null
|
|
201
|
-
);
|
|
202
|
-
}
|
|
203
|
-
function getDuration(graph) {
|
|
204
|
-
const end = graph.endTime ?? Date.now();
|
|
205
|
-
return end - graph.startTime;
|
|
206
|
-
}
|
|
207
|
-
function getDepth(graph) {
|
|
208
|
-
const root = graph.nodes.get(graph.rootNodeId);
|
|
209
|
-
if (!root) return -1;
|
|
210
|
-
function dfs(node, depth) {
|
|
211
|
-
if (node.children.length === 0) return depth;
|
|
212
|
-
let maxDepth = depth;
|
|
213
|
-
for (const childId of node.children) {
|
|
214
|
-
const child = graph.nodes.get(childId);
|
|
215
|
-
if (!child) continue;
|
|
216
|
-
const childDepth = dfs(child, depth + 1);
|
|
217
|
-
if (childDepth > maxDepth) maxDepth = childDepth;
|
|
218
|
-
}
|
|
219
|
-
return maxDepth;
|
|
220
|
-
}
|
|
221
|
-
return dfs(root, 0);
|
|
222
|
-
}
|
|
223
|
-
function getStats(graph) {
|
|
224
|
-
const byStatus = {
|
|
225
|
-
running: 0,
|
|
226
|
-
completed: 0,
|
|
227
|
-
failed: 0,
|
|
228
|
-
hung: 0,
|
|
229
|
-
timeout: 0
|
|
230
|
-
};
|
|
231
|
-
const byType = {
|
|
232
|
-
agent: 0,
|
|
233
|
-
tool: 0,
|
|
234
|
-
subagent: 0,
|
|
235
|
-
wait: 0,
|
|
236
|
-
decision: 0,
|
|
237
|
-
custom: 0
|
|
238
|
-
};
|
|
239
|
-
let failureCount = 0;
|
|
240
|
-
let hungCount = 0;
|
|
241
|
-
for (const node of graph.nodes.values()) {
|
|
242
|
-
byStatus[node.status]++;
|
|
243
|
-
byType[node.type]++;
|
|
244
|
-
if (node.status === "failed" || node.status === "timeout" || node.status === "hung") {
|
|
245
|
-
failureCount++;
|
|
246
|
-
}
|
|
247
|
-
if (node.status === "running" && node.endTime === null) {
|
|
248
|
-
hungCount++;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
return {
|
|
252
|
-
totalNodes: graph.nodes.size,
|
|
253
|
-
byStatus,
|
|
254
|
-
byType,
|
|
255
|
-
depth: getDepth(graph),
|
|
256
|
-
duration: getDuration(graph),
|
|
257
|
-
failureCount,
|
|
258
|
-
hungCount
|
|
259
|
-
};
|
|
260
|
-
}
|
|
45
|
+
var import_ws = require("ws");
|
|
46
|
+
var import_agentflow_core3 = require("agentflow-core");
|
|
261
47
|
|
|
262
48
|
// src/stats.ts
|
|
49
|
+
var import_agentflow_core = require("agentflow-core");
|
|
263
50
|
var AgentStats = class {
|
|
264
51
|
agentMetrics = /* @__PURE__ */ new Map();
|
|
265
52
|
processedTraces = /* @__PURE__ */ new Set();
|
|
266
53
|
processTrace(trace) {
|
|
267
|
-
const traceKey = `${trace.filename || trace.agentId}-${trace.
|
|
54
|
+
const traceKey = `${trace.filename || trace.agentId}-${trace.startTime}`;
|
|
268
55
|
if (this.processedTraces.has(traceKey)) {
|
|
269
56
|
return;
|
|
270
57
|
}
|
|
@@ -287,7 +74,7 @@ var AgentStats = class {
|
|
|
287
74
|
}
|
|
288
75
|
const analysis = this.analyzeExecution(trace);
|
|
289
76
|
metrics.totalExecutions++;
|
|
290
|
-
metrics.lastExecution = Math.max(metrics.lastExecution, trace.
|
|
77
|
+
metrics.lastExecution = Math.max(metrics.lastExecution, trace.startTime);
|
|
291
78
|
const trigger = trace.trigger || "unknown";
|
|
292
79
|
metrics.triggers[trigger] = (metrics.triggers[trigger] || 0) + 1;
|
|
293
80
|
if (analysis.success) {
|
|
@@ -302,7 +89,7 @@ var AgentStats = class {
|
|
|
302
89
|
metrics.avgExecutionTime = (currentAvg * (count - 1) + analysis.executionTime) / count;
|
|
303
90
|
}
|
|
304
91
|
metrics.recentActivity.push({
|
|
305
|
-
timestamp: trace.
|
|
92
|
+
timestamp: trace.startTime,
|
|
306
93
|
success: analysis.success,
|
|
307
94
|
executionTime: analysis.executionTime,
|
|
308
95
|
trigger
|
|
@@ -314,23 +101,22 @@ var AgentStats = class {
|
|
|
314
101
|
}
|
|
315
102
|
analyzeExecution(trace) {
|
|
316
103
|
try {
|
|
317
|
-
const stats = getStats(trace);
|
|
318
|
-
const failures = getFailures(trace);
|
|
319
|
-
const hungNodes = getHungNodes(trace);
|
|
104
|
+
const stats = (0, import_agentflow_core.getStats)(trace);
|
|
105
|
+
const failures = (0, import_agentflow_core.getFailures)(trace);
|
|
106
|
+
const hungNodes = (0, import_agentflow_core.getHungNodes)(trace);
|
|
320
107
|
return {
|
|
321
108
|
success: failures.length === 0 && hungNodes.length === 0,
|
|
322
|
-
executionTime: stats.
|
|
109
|
+
executionTime: stats.duration || 0,
|
|
323
110
|
nodeCount: stats.totalNodes || 0,
|
|
324
111
|
failureCount: failures.length,
|
|
325
112
|
hungCount: hungNodes.length
|
|
326
113
|
};
|
|
327
114
|
} catch (error) {
|
|
328
115
|
console.warn("Error analyzing trace with AgentFlow:", error);
|
|
329
|
-
const nodes = trace.nodes instanceof Map ? Array.from(trace.nodes.values()) :
|
|
116
|
+
const nodes = trace.nodes instanceof Map ? Array.from(trace.nodes.values()) : [];
|
|
330
117
|
const failedNodes = nodes.filter((node) => node.status === "failed").length;
|
|
331
|
-
const success = failedNodes === 0;
|
|
332
118
|
return {
|
|
333
|
-
success,
|
|
119
|
+
success: failedNodes === 0,
|
|
334
120
|
executionTime: 0,
|
|
335
121
|
nodeCount: nodes.length,
|
|
336
122
|
failureCount: failedNodes,
|
|
@@ -420,7 +206,6 @@ var AgentStats = class {
|
|
|
420
206
|
hourlySuccessRate: recentExecutions > 0 ? Math.round((recentExecutions - recentFailures) / recentExecutions * 1e4) / 100 : 0
|
|
421
207
|
};
|
|
422
208
|
}
|
|
423
|
-
// Clear old data (useful for long-running dashboard instances)
|
|
424
209
|
cleanup() {
|
|
425
210
|
const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1e3;
|
|
426
211
|
for (const [agentId, metrics] of this.agentMetrics.entries()) {
|
|
@@ -435,6 +220,148 @@ var AgentStats = class {
|
|
|
435
220
|
}
|
|
436
221
|
};
|
|
437
222
|
|
|
223
|
+
// src/watcher.ts
|
|
224
|
+
var import_agentflow_core2 = require("agentflow-core");
|
|
225
|
+
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
226
|
+
var import_events = require("events");
|
|
227
|
+
var fs = __toESM(require("fs"), 1);
|
|
228
|
+
var path = __toESM(require("path"), 1);
|
|
229
|
+
var TraceWatcher = class extends import_events.EventEmitter {
|
|
230
|
+
watcher;
|
|
231
|
+
traces = /* @__PURE__ */ new Map();
|
|
232
|
+
tracesDir;
|
|
233
|
+
constructor(tracesDir) {
|
|
234
|
+
super();
|
|
235
|
+
this.tracesDir = path.resolve(tracesDir);
|
|
236
|
+
this.ensureTracesDir();
|
|
237
|
+
this.loadExistingTraces();
|
|
238
|
+
this.startWatching();
|
|
239
|
+
}
|
|
240
|
+
ensureTracesDir() {
|
|
241
|
+
if (!fs.existsSync(this.tracesDir)) {
|
|
242
|
+
fs.mkdirSync(this.tracesDir, { recursive: true });
|
|
243
|
+
console.log(`Created traces directory: ${this.tracesDir}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
loadExistingTraces() {
|
|
247
|
+
try {
|
|
248
|
+
const files = fs.readdirSync(this.tracesDir).filter((file) => file.endsWith(".json"));
|
|
249
|
+
console.log(`Loading ${files.length} existing trace files...`);
|
|
250
|
+
for (const file of files) {
|
|
251
|
+
this.loadTraceFile(path.join(this.tracesDir, file));
|
|
252
|
+
}
|
|
253
|
+
console.log(`Loaded ${this.traces.size} traces`);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error("Error loading existing traces:", error);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
loadTraceFile(filePath) {
|
|
259
|
+
try {
|
|
260
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
261
|
+
const graph = (0, import_agentflow_core2.loadGraph)(content);
|
|
262
|
+
const filename = path.basename(filePath);
|
|
263
|
+
const stats = fs.statSync(filePath);
|
|
264
|
+
graph.filename = filename;
|
|
265
|
+
graph.lastModified = stats.mtime.getTime();
|
|
266
|
+
this.traces.set(filename, graph);
|
|
267
|
+
return true;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
console.error(`Error loading trace file ${filePath}:`, error);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
startWatching() {
|
|
274
|
+
this.watcher = import_chokidar.default.watch(this.tracesDir, {
|
|
275
|
+
ignored: /^\./,
|
|
276
|
+
persistent: true,
|
|
277
|
+
ignoreInitial: true
|
|
278
|
+
});
|
|
279
|
+
this.watcher.on("add", (filePath) => {
|
|
280
|
+
if (filePath.endsWith(".json")) {
|
|
281
|
+
console.log(`New trace file: ${path.basename(filePath)}`);
|
|
282
|
+
if (this.loadTraceFile(filePath)) {
|
|
283
|
+
const filename = path.basename(filePath);
|
|
284
|
+
const trace = this.traces.get(filename);
|
|
285
|
+
if (trace) {
|
|
286
|
+
this.emit("trace-added", trace);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
this.watcher.on("change", (filePath) => {
|
|
292
|
+
if (filePath.endsWith(".json")) {
|
|
293
|
+
console.log(`Trace file updated: ${path.basename(filePath)}`);
|
|
294
|
+
if (this.loadTraceFile(filePath)) {
|
|
295
|
+
const filename = path.basename(filePath);
|
|
296
|
+
const trace = this.traces.get(filename);
|
|
297
|
+
if (trace) {
|
|
298
|
+
this.emit("trace-updated", trace);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
this.watcher.on("unlink", (filePath) => {
|
|
304
|
+
if (filePath.endsWith(".json")) {
|
|
305
|
+
const filename = path.basename(filePath);
|
|
306
|
+
console.log(`Trace file removed: ${filename}`);
|
|
307
|
+
this.traces.delete(filename);
|
|
308
|
+
this.emit("trace-removed", filename);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
this.watcher.on("error", (error) => {
|
|
312
|
+
console.error("Trace watcher error:", error);
|
|
313
|
+
});
|
|
314
|
+
console.log(`Started watching traces directory: ${this.tracesDir}`);
|
|
315
|
+
}
|
|
316
|
+
getAllTraces() {
|
|
317
|
+
return Array.from(this.traces.values()).sort((a, b) => {
|
|
318
|
+
return (b.lastModified || b.startTime) - (a.lastModified || a.startTime);
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
getTrace(filename) {
|
|
322
|
+
return this.traces.get(filename);
|
|
323
|
+
}
|
|
324
|
+
getTracesByAgent(agentId) {
|
|
325
|
+
return this.getAllTraces().filter((trace) => trace.agentId === agentId);
|
|
326
|
+
}
|
|
327
|
+
getRecentTraces(limit = 50) {
|
|
328
|
+
return this.getAllTraces().slice(0, limit);
|
|
329
|
+
}
|
|
330
|
+
getTraceCount() {
|
|
331
|
+
return this.traces.size;
|
|
332
|
+
}
|
|
333
|
+
getAgentIds() {
|
|
334
|
+
const agentIds = /* @__PURE__ */ new Set();
|
|
335
|
+
for (const trace of this.traces.values()) {
|
|
336
|
+
agentIds.add(trace.agentId);
|
|
337
|
+
}
|
|
338
|
+
return Array.from(agentIds).sort();
|
|
339
|
+
}
|
|
340
|
+
stop() {
|
|
341
|
+
if (this.watcher) {
|
|
342
|
+
this.watcher.close();
|
|
343
|
+
this.watcher = void 0;
|
|
344
|
+
console.log("Stopped watching traces directory");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
getTraceStats() {
|
|
348
|
+
const total = this.traces.size;
|
|
349
|
+
const agentCount = this.getAgentIds().length;
|
|
350
|
+
const recentCount = this.getRecentTraces(24).length;
|
|
351
|
+
const triggers = /* @__PURE__ */ new Map();
|
|
352
|
+
for (const trace of this.traces.values()) {
|
|
353
|
+
const trigger = trace.trigger || "unknown";
|
|
354
|
+
triggers.set(trigger, (triggers.get(trigger) || 0) + 1);
|
|
355
|
+
}
|
|
356
|
+
return {
|
|
357
|
+
total,
|
|
358
|
+
agentCount,
|
|
359
|
+
recentCount,
|
|
360
|
+
triggers: Object.fromEntries(triggers)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
438
365
|
// src/server.ts
|
|
439
366
|
var import_meta = {};
|
|
440
367
|
var __filename = (0, import_url.fileURLToPath)(import_meta.url);
|
|
@@ -453,11 +380,15 @@ var DashboardServer = class {
|
|
|
453
380
|
wss = new import_ws.WebSocketServer({ server: this.server });
|
|
454
381
|
watcher;
|
|
455
382
|
stats;
|
|
383
|
+
processHealthCache = { result: null, ts: 0 };
|
|
456
384
|
setupExpress() {
|
|
457
385
|
if (this.config.enableCors) {
|
|
458
386
|
this.app.use((req, res, next) => {
|
|
459
387
|
res.header("Access-Control-Allow-Origin", "*");
|
|
460
|
-
res.header(
|
|
388
|
+
res.header(
|
|
389
|
+
"Access-Control-Allow-Headers",
|
|
390
|
+
"Origin, X-Requested-With, Content-Type, Accept"
|
|
391
|
+
);
|
|
461
392
|
next();
|
|
462
393
|
});
|
|
463
394
|
}
|
|
@@ -511,6 +442,28 @@ var DashboardServer = class {
|
|
|
511
442
|
res.status(500).json({ error: "Failed to load agent statistics" });
|
|
512
443
|
}
|
|
513
444
|
});
|
|
445
|
+
this.app.get("/api/process-health", (req, res) => {
|
|
446
|
+
try {
|
|
447
|
+
const now = Date.now();
|
|
448
|
+
if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
|
|
449
|
+
return res.json(this.processHealthCache.result);
|
|
450
|
+
}
|
|
451
|
+
const discoveryDirs = [
|
|
452
|
+
this.config.tracesDir,
|
|
453
|
+
path2.dirname(this.config.tracesDir),
|
|
454
|
+
...this.config.dataDirs || []
|
|
455
|
+
];
|
|
456
|
+
const processConfig = (0, import_agentflow_core3.discoverProcessConfig)(discoveryDirs);
|
|
457
|
+
if (!processConfig) {
|
|
458
|
+
return res.json(null);
|
|
459
|
+
}
|
|
460
|
+
const result = (0, import_agentflow_core3.auditProcesses)(processConfig);
|
|
461
|
+
this.processHealthCache = { result, ts: now };
|
|
462
|
+
res.json(result);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
res.status(500).json({ error: "Failed to audit processes" });
|
|
465
|
+
}
|
|
466
|
+
});
|
|
514
467
|
this.app.get("*", (req, res) => {
|
|
515
468
|
const indexPath = path2.join(__dirname, "../public/index.html");
|
|
516
469
|
if (fs2.existsSync(indexPath)) {
|
|
@@ -523,13 +476,15 @@ var DashboardServer = class {
|
|
|
523
476
|
setupWebSocket() {
|
|
524
477
|
this.wss.on("connection", (ws) => {
|
|
525
478
|
console.log("Dashboard client connected");
|
|
526
|
-
ws.send(
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
479
|
+
ws.send(
|
|
480
|
+
JSON.stringify({
|
|
481
|
+
type: "init",
|
|
482
|
+
data: {
|
|
483
|
+
traces: this.watcher.getAllTraces(),
|
|
484
|
+
stats: this.stats.getGlobalStats()
|
|
485
|
+
}
|
|
486
|
+
})
|
|
487
|
+
);
|
|
533
488
|
ws.on("close", () => {
|
|
534
489
|
console.log("Dashboard client disconnected");
|
|
535
490
|
});
|