agentflow-dashboard 0.6.0 → 0.7.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.
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // src/server.ts
2
9
  import * as fs3 from "fs";
3
10
  import { createServer } from "http";
@@ -7,8 +14,8 @@ import {
7
14
  auditProcesses,
8
15
  createExecutionEvent,
9
16
  createKnowledgeStore,
17
+ discoverAllProcessConfigs,
10
18
  discoverProcess,
11
- discoverProcessConfig,
12
19
  findVariants,
13
20
  getBottlenecks,
14
21
  loadGraph as loadGraph2
@@ -16,6 +23,422 @@ import {
16
23
  import express from "express";
17
24
  import { WebSocketServer } from "ws";
18
25
 
26
+ // src/adapters/agentflow.ts
27
+ var SKIP_FILES = /* @__PURE__ */ new Set([
28
+ "workers.json",
29
+ "package.json",
30
+ "package-lock.json",
31
+ "tsconfig.json",
32
+ "biome.json",
33
+ "auth.json",
34
+ "models.json",
35
+ "config.json"
36
+ ]);
37
+ var SKIP_SUFFIXES = ["-state.json", "-config.json", "-watch-state.json", ".tmp", ".bak", ".backup"];
38
+ var AgentFlowAdapter = class {
39
+ name = "agentflow";
40
+ detect(_dirPath) {
41
+ return true;
42
+ }
43
+ canHandle(filePath) {
44
+ const filename = filePath.split("/").pop() ?? "";
45
+ if (SKIP_FILES.has(filename)) return false;
46
+ if (SKIP_SUFFIXES.some((s) => filename.endsWith(s))) return false;
47
+ return filename.endsWith(".json") || filename.endsWith(".jsonl") || filename.endsWith(".log") || filename.endsWith(".trace");
48
+ }
49
+ parse(_filePath) {
50
+ return [];
51
+ }
52
+ };
53
+
54
+ // src/adapters/openclaw.ts
55
+ import { existsSync, readFileSync } from "fs";
56
+ import { basename, dirname, join } from "path";
57
+ var jobCache = /* @__PURE__ */ new Map();
58
+ function loadJobs(openclawDir) {
59
+ const cached = jobCache.get(openclawDir);
60
+ if (cached) return cached;
61
+ const jobsPath = join(openclawDir, "cron", "jobs.json");
62
+ const map = /* @__PURE__ */ new Map();
63
+ try {
64
+ if (existsSync(jobsPath)) {
65
+ const data = JSON.parse(readFileSync(jobsPath, "utf-8"));
66
+ const jobs = Array.isArray(data) ? data : data.jobs ?? [];
67
+ for (const job of jobs) {
68
+ if (job.id) map.set(job.id, job);
69
+ }
70
+ }
71
+ } catch {
72
+ }
73
+ jobCache.set(openclawDir, map);
74
+ return map;
75
+ }
76
+ function findOpenClawRoot(filePath) {
77
+ let dir = dirname(filePath);
78
+ for (let i = 0; i < 5; i++) {
79
+ if (existsSync(join(dir, "cron", "jobs.json")) || basename(dir) === ".openclaw") {
80
+ return dir;
81
+ }
82
+ dir = dirname(dir);
83
+ }
84
+ return null;
85
+ }
86
+ var OpenClawAdapter = class {
87
+ name = "openclaw";
88
+ detect(dirPath) {
89
+ return existsSync(join(dirPath, "cron", "jobs.json")) || dirPath.includes(".openclaw") || existsSync(join(dirPath, "cron", "runs"));
90
+ }
91
+ canHandle(filePath) {
92
+ if (!filePath.endsWith(".jsonl")) return false;
93
+ return filePath.includes("/cron/runs/") || filePath.includes("\\cron\\runs\\");
94
+ }
95
+ parse(filePath) {
96
+ const traces = [];
97
+ try {
98
+ const content = readFileSync(filePath, "utf-8");
99
+ const root = findOpenClawRoot(filePath);
100
+ const jobs = root ? loadJobs(root) : /* @__PURE__ */ new Map();
101
+ for (const line of content.split("\n")) {
102
+ if (!line.trim()) continue;
103
+ let entry;
104
+ try {
105
+ entry = JSON.parse(line);
106
+ } catch {
107
+ continue;
108
+ }
109
+ if (entry.action !== "finished") continue;
110
+ const jobId = entry.jobId ?? basename(filePath, ".jsonl");
111
+ const job = jobs.get(jobId);
112
+ const jobName = (job == null ? void 0 : job.name) ?? jobId;
113
+ const startTime = entry.runAtMs ?? entry.ts;
114
+ const duration = entry.durationMs ?? 0;
115
+ const trace = {
116
+ id: entry.sessionId ?? `${jobId}-${entry.ts}`,
117
+ agentId: `openclaw:${jobId}`,
118
+ name: jobName,
119
+ status: entry.status === "ok" ? "completed" : entry.status === "error" ? "failed" : "unknown",
120
+ startTime,
121
+ endTime: startTime + duration,
122
+ trigger: "cron",
123
+ source: "openclaw",
124
+ nodes: {
125
+ root: {
126
+ id: "root",
127
+ type: "cron-job",
128
+ name: jobName,
129
+ status: entry.status === "ok" ? "completed" : entry.status === "error" ? "failed" : "unknown",
130
+ startTime,
131
+ endTime: startTime + duration,
132
+ parentId: null,
133
+ children: [],
134
+ metadata: {
135
+ jobId,
136
+ summary: entry.summary,
137
+ error: entry.error,
138
+ delivered: entry.delivered,
139
+ deliveryStatus: entry.deliveryStatus
140
+ }
141
+ }
142
+ },
143
+ metadata: {
144
+ model: entry.model,
145
+ provider: entry.provider,
146
+ usage: entry.usage,
147
+ sessionId: entry.sessionId,
148
+ sessionKey: entry.sessionKey,
149
+ nextRunAtMs: entry.nextRunAtMs
150
+ },
151
+ filePath
152
+ };
153
+ traces.push(trace);
154
+ }
155
+ } catch {
156
+ }
157
+ return traces;
158
+ }
159
+ };
160
+
161
+ // src/adapters/otel.ts
162
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync } from "fs";
163
+ import { join as join2 } from "path";
164
+ var SPAN_TYPE_MAP = {
165
+ "gen_ai.chat": "llm",
166
+ "gen_ai.completion": "llm",
167
+ "gen_ai.embeddings": "embedding",
168
+ "gen_ai.content.prompt": "llm",
169
+ "gen_ai.content.completion": "llm"
170
+ };
171
+ function mapSpanType(spanName, attributes) {
172
+ for (const [prefix, type] of Object.entries(SPAN_TYPE_MAP)) {
173
+ if (spanName.startsWith(prefix)) return type;
174
+ }
175
+ if (attributes["tool.name"] || attributes["code.function"]) return "tool";
176
+ if (attributes["gen_ai.system"] || attributes["llm.vendor"]) return "llm";
177
+ if (attributes["db.system"]) return "database";
178
+ if (attributes["http.method"] || attributes["http.request.method"]) return "http";
179
+ return "span";
180
+ }
181
+ function extractAttributes(attrs) {
182
+ const result = {};
183
+ if (!Array.isArray(attrs)) return result;
184
+ for (const attr of attrs) {
185
+ const a = attr;
186
+ if (!a.key || !a.value) continue;
187
+ result[a.key] = a.value.stringValue ?? a.value.intValue ?? a.value.doubleValue ?? a.value.boolValue;
188
+ }
189
+ return result;
190
+ }
191
+ function parseOtlpPayload(payload) {
192
+ var _a, _b, _c;
193
+ const traceMap = /* @__PURE__ */ new Map();
194
+ for (const rs of payload.resourceSpans ?? []) {
195
+ const resourceAttrs = extractAttributes(((_a = rs.resource) == null ? void 0 : _a.attributes) ?? []);
196
+ for (const ss of rs.scopeSpans ?? []) {
197
+ for (const span of ss.spans ?? []) {
198
+ if (!span.traceId) continue;
199
+ let entry = traceMap.get(span.traceId);
200
+ if (!entry) {
201
+ entry = { spans: [], resource: resourceAttrs };
202
+ traceMap.set(span.traceId, entry);
203
+ }
204
+ entry.spans.push(span);
205
+ }
206
+ }
207
+ }
208
+ const traces = [];
209
+ for (const [traceId, { spans, resource }] of traceMap) {
210
+ const nodes = {};
211
+ const childMap = /* @__PURE__ */ new Map();
212
+ let traceStart = Number.MAX_SAFE_INTEGER;
213
+ let traceEnd = 0;
214
+ let hasFailed = false;
215
+ for (const span of spans) {
216
+ const attrs = extractAttributes(span.attributes ?? []);
217
+ const startNs = span.startTimeUnixNano ? Number(BigInt(span.startTimeUnixNano) / 1000000n) : 0;
218
+ const endNs = span.endTimeUnixNano ? Number(BigInt(span.endTimeUnixNano) / 1000000n) : null;
219
+ const failed = ((_b = span.status) == null ? void 0 : _b.code) === 2;
220
+ if (failed) hasFailed = true;
221
+ if (startNs < traceStart) traceStart = startNs;
222
+ if (endNs && endNs > traceEnd) traceEnd = endNs;
223
+ nodes[span.spanId] = {
224
+ id: span.spanId,
225
+ type: mapSpanType(span.name, attrs),
226
+ name: span.name,
227
+ status: failed ? "failed" : endNs ? "completed" : "running",
228
+ startTime: startNs,
229
+ endTime: endNs,
230
+ parentId: span.parentSpanId ?? null,
231
+ children: [],
232
+ metadata: {
233
+ ...attrs,
234
+ model: attrs["gen_ai.request.model"] ?? attrs["llm.request.model"],
235
+ inputTokens: attrs["gen_ai.usage.input_tokens"] ?? attrs["llm.usage.input_tokens"],
236
+ outputTokens: attrs["gen_ai.usage.output_tokens"] ?? attrs["llm.usage.output_tokens"]
237
+ }
238
+ };
239
+ if (span.parentSpanId) {
240
+ const siblings = childMap.get(span.parentSpanId) ?? [];
241
+ siblings.push(span.spanId);
242
+ childMap.set(span.parentSpanId, siblings);
243
+ }
244
+ }
245
+ for (const [parentId, children] of childMap) {
246
+ if (nodes[parentId]) {
247
+ nodes[parentId].children = children;
248
+ }
249
+ }
250
+ const serviceName = resource["service.name"] ?? "unknown-service";
251
+ traces.push({
252
+ id: traceId,
253
+ agentId: `otel:${serviceName}`,
254
+ name: ((_c = spans.find((s) => !s.parentSpanId)) == null ? void 0 : _c.name) ?? traceId,
255
+ status: hasFailed ? "failed" : "completed",
256
+ startTime: traceStart === Number.MAX_SAFE_INTEGER ? 0 : traceStart,
257
+ endTime: traceEnd,
258
+ trigger: "otel",
259
+ source: "otel",
260
+ nodes,
261
+ metadata: { ...resource }
262
+ });
263
+ }
264
+ return traces;
265
+ }
266
+ var OTelAdapter = class {
267
+ name = "otel";
268
+ detect(dirPath) {
269
+ try {
270
+ if (existsSync2(join2(dirPath, "otel-traces"))) return true;
271
+ const files = readdirSync(dirPath);
272
+ return files.some((f) => f.endsWith(".otlp.json"));
273
+ } catch {
274
+ return false;
275
+ }
276
+ }
277
+ canHandle(filePath) {
278
+ return filePath.endsWith(".otlp.json");
279
+ }
280
+ parse(filePath) {
281
+ try {
282
+ const content = readFileSync2(filePath, "utf-8");
283
+ const payload = JSON.parse(content);
284
+ const traces = parseOtlpPayload(payload);
285
+ for (const t of traces) t.filePath = filePath;
286
+ return traces;
287
+ } catch {
288
+ return [];
289
+ }
290
+ }
291
+ };
292
+
293
+ // src/adapters/registry.ts
294
+ var adapters = [];
295
+ function registerAdapter(adapter) {
296
+ adapters.push(adapter);
297
+ }
298
+ function findAdapter(filePath) {
299
+ for (const adapter of adapters) {
300
+ if (adapter.canHandle(filePath)) return adapter;
301
+ }
302
+ return null;
303
+ }
304
+
305
+ // src/adapters/index.ts
306
+ registerAdapter(new OpenClawAdapter());
307
+ registerAdapter(new OTelAdapter());
308
+ registerAdapter(new AgentFlowAdapter());
309
+
310
+ // src/agent-clustering.ts
311
+ var PURPOSE_KEYWORDS = [
312
+ { keywords: ["email", "mail", "inbox", "smtp"], group: "Email Processors" },
313
+ { keywords: ["monitor", "watch", "alert", "surveillance"], group: "Monitors" },
314
+ { keywords: ["digest", "newsletter", "summary", "report", "briefing"], group: "Digests & Reports" },
315
+ { keywords: ["curator", "janitor", "distiller", "surveyor", "worker", "indexer"], group: "Workers" },
316
+ { keywords: ["cron", "schedule", "timer", "periodic"], group: "Scheduled Jobs" },
317
+ { keywords: ["search", "scrape", "crawl", "fetch"], group: "Data Collection" },
318
+ { keywords: ["embed", "vector", "index"], group: "Embeddings" }
319
+ ];
320
+ function extractSource(agentId) {
321
+ const colonIdx = agentId.indexOf(":");
322
+ if (colonIdx > 0 && colonIdx < 20) {
323
+ const prefix = agentId.slice(0, colonIdx);
324
+ if (["openclaw", "otel", "langchain", "crewai", "mastra"].includes(prefix)) {
325
+ return { source: prefix, localId: agentId.slice(colonIdx + 1) };
326
+ }
327
+ }
328
+ return { source: "agentflow", localId: agentId };
329
+ }
330
+ function extractSuffix(localId) {
331
+ const dashIdx = localId.indexOf("-");
332
+ if (dashIdx > 0 && dashIdx < localId.length - 1) {
333
+ const suffix = localId.slice(dashIdx + 1);
334
+ if (suffix.length >= 4) return suffix;
335
+ }
336
+ return null;
337
+ }
338
+ function findPurpose(name) {
339
+ const lower = name.toLowerCase();
340
+ for (const { keywords, group } of PURPOSE_KEYWORDS) {
341
+ if (keywords.some((kw) => lower.includes(kw))) return group;
342
+ }
343
+ return "General";
344
+ }
345
+ function capitalize(s) {
346
+ return s.charAt(0).toUpperCase() + s.slice(1);
347
+ }
348
+ function deduplicateAgents(agents) {
349
+ const tagged = agents.map((a) => ({
350
+ ...a,
351
+ ...extractSource(a.agentId)
352
+ }));
353
+ const suffixGroups = /* @__PURE__ */ new Map();
354
+ for (const a of tagged) {
355
+ const suffix = extractSuffix(a.localId);
356
+ if (!suffix) continue;
357
+ const group = suffixGroups.get(suffix) ?? [];
358
+ group.push(a);
359
+ suffixGroups.set(suffix, group);
360
+ }
361
+ const mergedIds = /* @__PURE__ */ new Set();
362
+ const mergedAgents = [];
363
+ for (const [suffix, group] of suffixGroups) {
364
+ if (group.length < 2) continue;
365
+ const prefixes = new Set(group.map((a) => a.localId.split("-")[0]));
366
+ if (prefixes.size < 2) continue;
367
+ const merged = {
368
+ agentId: group[0].source === "agentflow" ? suffix : `${group[0].source}:${suffix}`,
369
+ displayName: suffix,
370
+ totalExecutions: group.reduce((s, a) => s + a.totalExecutions, 0),
371
+ successfulExecutions: group.reduce((s, a) => s + a.successfulExecutions, 0),
372
+ failedExecutions: group.reduce((s, a) => s + a.failedExecutions, 0),
373
+ successRate: 0,
374
+ avgExecutionTime: 0,
375
+ lastExecution: Math.max(...group.map((a) => a.lastExecution)),
376
+ triggers: {},
377
+ recentActivity: group.flatMap((a) => a.recentActivity).sort((a, b) => b.timestamp - a.timestamp).slice(0, 50),
378
+ sources: group.map((a) => a.agentId),
379
+ adapterSource: group[0].source
380
+ };
381
+ merged.successRate = merged.totalExecutions > 0 ? merged.successfulExecutions / merged.totalExecutions * 100 : 0;
382
+ const totalExecTime = group.reduce((s, a) => s + a.avgExecutionTime * a.totalExecutions, 0);
383
+ merged.avgExecutionTime = merged.totalExecutions > 0 ? totalExecTime / merged.totalExecutions : 0;
384
+ for (const a of group) {
385
+ for (const [k, v] of Object.entries(a.triggers)) {
386
+ merged.triggers[k] = (merged.triggers[k] ?? 0) + v;
387
+ }
388
+ }
389
+ mergedAgents.push(merged);
390
+ for (const a of group) mergedIds.add(a.agentId);
391
+ }
392
+ const result = [];
393
+ for (const a of agents) {
394
+ if (mergedIds.has(a.agentId)) continue;
395
+ const { source, localId } = extractSource(a.agentId);
396
+ result.push({
397
+ ...a,
398
+ displayName: a.displayName ?? localId,
399
+ adapterSource: source
400
+ });
401
+ }
402
+ return [...result, ...mergedAgents];
403
+ }
404
+ function groupAgents(agents) {
405
+ const sourceMap = /* @__PURE__ */ new Map();
406
+ for (const a of agents) {
407
+ const source = a.adapterSource ?? extractSource(a.agentId).source;
408
+ const list = sourceMap.get(source) ?? [];
409
+ list.push(a);
410
+ sourceMap.set(source, list);
411
+ }
412
+ const SOURCE_DISPLAY = {
413
+ agentflow: "AgentFlow",
414
+ openclaw: "OpenClaw",
415
+ otel: "OpenTelemetry",
416
+ langchain: "LangChain",
417
+ crewai: "CrewAI"
418
+ };
419
+ const groups = [];
420
+ for (const [source, sourceAgents] of sourceMap) {
421
+ const subMap = /* @__PURE__ */ new Map();
422
+ for (const a of sourceAgents) {
423
+ const purpose = findPurpose(a.displayName ?? a.agentId);
424
+ const list = subMap.get(purpose) ?? [];
425
+ list.push(a.agentId);
426
+ subMap.set(purpose, list);
427
+ }
428
+ const subGroups = [...subMap.entries()].map(([name, agentIds]) => ({ name, agentIds })).sort((a, b) => b.agentIds.length - a.agentIds.length);
429
+ groups.push({
430
+ name: source,
431
+ displayName: SOURCE_DISPLAY[source] ?? capitalize(source),
432
+ totalExecutions: sourceAgents.reduce((s, a) => s + a.totalExecutions, 0),
433
+ failedExecutions: sourceAgents.reduce((s, a) => s + a.failedExecutions, 0),
434
+ agents: sourceAgents.sort((a, b) => b.totalExecutions - a.totalExecutions),
435
+ subGroups
436
+ });
437
+ }
438
+ groups.sort((a, b) => b.totalExecutions - a.totalExecutions);
439
+ return { groups };
440
+ }
441
+
19
442
  // src/stats.ts
20
443
  import { getFailures, getHungNodes, getStats } from "agentflow-core";
21
444
  var AgentStats = class {
@@ -340,7 +763,7 @@ function openClawSessionIdToAgent(sessionId) {
340
763
  if (sessionId.startsWith("janitor-")) return "vault-janitor";
341
764
  if (sessionId.startsWith("curator-")) return "vault-curator";
342
765
  if (sessionId.startsWith("distiller-")) return "vault-distiller";
343
- if (sessionId.startsWith("main-")) return "main";
766
+ if (sessionId.startsWith("main-")) return "alfred-main";
344
767
  const firstSegment = sessionId.split("-")[0];
345
768
  if (firstSegment) return firstSegment;
346
769
  return "openclaw";
@@ -425,10 +848,14 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
425
848
  "package-lock.json",
426
849
  "tsconfig.json",
427
850
  "biome.json",
428
- "jobs.json",
429
851
  "auth.json",
430
852
  "models.json",
431
- "config.json"
853
+ "config.json",
854
+ "runs.json",
855
+ "sessions.json",
856
+ "containers.json",
857
+ "update-check.json",
858
+ "exec-approvals.json"
432
859
  ]);
433
860
  static SKIP_SUFFIXES = [
434
861
  "-state.json",
@@ -438,11 +865,15 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
438
865
  ".bak",
439
866
  ".backup"
440
867
  ];
441
- /** Load a .json trace, .jsonl session file, or .log file. */
868
+ /** Load a file using the adapter registry, falling back to built-in parsing. */
442
869
  loadFile(filePath) {
443
870
  const filename = path.basename(filePath);
444
871
  if (_TraceWatcher.SKIP_FILES.has(filename)) return false;
445
872
  if (_TraceWatcher.SKIP_SUFFIXES.some((s) => filename.endsWith(s))) return false;
873
+ const adapter = findAdapter(filePath);
874
+ if (adapter && adapter.name !== "agentflow") {
875
+ return this.loadViaAdapter(filePath, adapter.name);
876
+ }
446
877
  if (filePath.endsWith(".jsonl")) {
447
878
  return this.loadSessionFile(filePath);
448
879
  }
@@ -451,6 +882,57 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
451
882
  }
452
883
  return this.loadTraceFile(filePath);
453
884
  }
885
+ /** Load a file using a specific adapter and store normalized traces. */
886
+ loadViaAdapter(filePath, adapterName) {
887
+ try {
888
+ const adapter = findAdapter(filePath);
889
+ if (!adapter) return false;
890
+ const normalized = adapter.parse(filePath);
891
+ if (normalized.length === 0) return false;
892
+ for (const trace of normalized) {
893
+ const nodes = /* @__PURE__ */ new Map();
894
+ for (const [id, node] of Object.entries(trace.nodes)) {
895
+ nodes.set(id, {
896
+ id: node.id,
897
+ type: node.type,
898
+ name: node.name,
899
+ status: node.status,
900
+ startTime: node.startTime,
901
+ endTime: node.endTime,
902
+ parentId: node.parentId,
903
+ children: node.children,
904
+ metadata: node.metadata,
905
+ state: {}
906
+ });
907
+ }
908
+ const watched = {
909
+ id: trace.id,
910
+ rootNodeId: Object.keys(trace.nodes)[0] ?? "",
911
+ agentId: trace.agentId,
912
+ name: trace.name,
913
+ trigger: trace.trigger,
914
+ startTime: trace.startTime,
915
+ endTime: trace.endTime,
916
+ status: trace.status,
917
+ nodes,
918
+ edges: [],
919
+ events: [],
920
+ metadata: { ...trace.metadata, adapterSource: adapterName },
921
+ sessionEvents: trace.sessionEvents ?? [],
922
+ sourceType: "session",
923
+ filename: path.basename(filePath),
924
+ lastModified: Date.now(),
925
+ sourceDir: path.dirname(filePath)
926
+ };
927
+ const key = `${adapterName}:${trace.id}`;
928
+ this.traces.set(key, watched);
929
+ }
930
+ return true;
931
+ } catch (error) {
932
+ console.error(`Adapter ${adapterName} failed for ${filePath}:`, error);
933
+ return false;
934
+ }
935
+ }
454
936
  loadLogFile(filePath) {
455
937
  try {
456
938
  const content = fs.readFileSync(filePath, "utf8");
@@ -563,21 +1045,46 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
563
1045
  }
564
1046
  return traces;
565
1047
  }
1048
+ /**
1049
+ * Normalise agent identifiers so that the same worker is never shown
1050
+ * under two different names (e.g. "vault-curator" vs "openclaw-vault-curator").
1051
+ *
1052
+ * Canonical names: alfred-main, vault-curator, vault-janitor,
1053
+ * vault-distiller, vault-surveyor
1054
+ */
1055
+ static AGENT_ALIASES = {
1056
+ "openclaw-main": "alfred-main",
1057
+ "openclaw-vault-curator": "vault-curator",
1058
+ "openclaw-vault-janitor": "vault-janitor",
1059
+ "openclaw-vault-distiller": "vault-distiller",
1060
+ "openclaw-vault-surveyor": "vault-surveyor",
1061
+ "alfred-curator": "vault-curator",
1062
+ "alfred-janitor": "vault-janitor",
1063
+ "alfred-distiller": "vault-distiller",
1064
+ "alfred-surveyor": "vault-surveyor",
1065
+ curator: "vault-curator",
1066
+ janitor: "vault-janitor",
1067
+ distiller: "vault-distiller",
1068
+ surveyor: "vault-surveyor"
1069
+ };
1070
+ normaliseAgentId(raw) {
1071
+ return _TraceWatcher.AGENT_ALIASES[raw] ?? raw;
1072
+ }
566
1073
  detectAgentIdentifier(activity, _filename, filePath) {
567
1074
  if (activity.agent_id) {
568
1075
  const agentId = activity.agent_id;
569
- if (agentId.startsWith("vault-")) return agentId;
570
- if (agentId === "main" && filePath.includes(".alfred/")) return "alfred-main";
571
- return agentId;
1076
+ if (agentId === "main" && filePath.includes(".alfred/")) return this.normaliseAgentId("alfred-main");
1077
+ return this.normaliseAgentId(agentId);
572
1078
  }
573
1079
  const pathAgent = this.extractAgentFromPath(filePath);
574
1080
  if (filePath.includes(".alfred/") && !pathAgent.startsWith("alfred-")) {
575
- const basename2 = path.basename(filePath, path.extname(filePath));
576
- if (basename2.match(/^(janitor|curator|distiller|surveyor|alfred)$/)) {
577
- return basename2 === "alfred" ? "alfred" : `alfred-${basename2}`;
1081
+ const basename3 = path.basename(filePath, path.extname(filePath));
1082
+ if (basename3.match(/^(janitor|curator|distiller|surveyor|alfred)$/)) {
1083
+ const raw = basename3 === "alfred" ? "alfred" : `alfred-${basename3}`;
1084
+ return this.normaliseAgentId(raw);
578
1085
  }
579
1086
  }
580
- return pathAgent;
1087
+ return this.normaliseAgentId(pathAgent);
581
1088
  }
582
1089
  extractAgentFromPath(filePath) {
583
1090
  const filename = path.basename(filePath, path.extname(filePath));
@@ -1408,8 +1915,23 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
1408
1915
  getTrace(filename) {
1409
1916
  const exact = this.traces.get(filename);
1410
1917
  if (exact) return exact;
1918
+ if (filename.includes("::")) {
1919
+ const [fname, startTimeStr] = filename.split("::");
1920
+ const startTime = Number(startTimeStr);
1921
+ if (fname && !Number.isNaN(startTime)) {
1922
+ for (const trace of this.traces.values()) {
1923
+ if (trace.filename === fname && trace.startTime === startTime) {
1924
+ return trace;
1925
+ }
1926
+ }
1927
+ }
1928
+ }
1929
+ for (const prefix of ["openclaw:", "otel:", ""]) {
1930
+ const prefixed = this.traces.get(prefix + filename);
1931
+ if (prefixed) return prefixed;
1932
+ }
1411
1933
  for (const [key, trace] of this.traces) {
1412
- if (trace.filename === filename || key.endsWith(filename)) {
1934
+ if (trace.filename === filename || trace.id === filename || key.endsWith(filename)) {
1413
1935
  return trace;
1414
1936
  }
1415
1937
  }
@@ -1620,6 +2142,31 @@ function serializeTrace(trace) {
1620
2142
  var DashboardServer = class {
1621
2143
  constructor(config) {
1622
2144
  this.config = config;
2145
+ const home = process.env.HOME ?? "/home/trader";
2146
+ const configPath = path3.join(home, ".agentflow/dashboard-config.json");
2147
+ if (!config.dataDirs) config.dataDirs = [];
2148
+ try {
2149
+ if (fs3.existsSync(configPath)) {
2150
+ const saved = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
2151
+ const extraDirs = saved.extraDirs ?? [];
2152
+ for (const d of extraDirs) {
2153
+ if (!config.dataDirs.includes(d)) config.dataDirs.push(d);
2154
+ }
2155
+ }
2156
+ } catch {
2157
+ }
2158
+ const autoDiscoverPaths = [
2159
+ path3.join(home, ".openclaw/cron/runs"),
2160
+ path3.join(home, ".openclaw/workspace/traces"),
2161
+ path3.join(home, ".openclaw/subagents"),
2162
+ path3.join(home, ".openclaw/agents/main/sessions"),
2163
+ path3.join(home, ".agentflow/traces")
2164
+ ];
2165
+ for (const p of autoDiscoverPaths) {
2166
+ if (fs3.existsSync(p) && !config.dataDirs.includes(p)) {
2167
+ config.dataDirs.push(p);
2168
+ }
2169
+ }
1623
2170
  this.watcher = new TraceWatcher({
1624
2171
  tracesDir: config.tracesDir,
1625
2172
  dataDirs: config.dataDirs
@@ -1668,9 +2215,13 @@ var DashboardServer = class {
1668
2215
  next();
1669
2216
  });
1670
2217
  }
2218
+ const clientDir = path3.join(__dirname, "../dist/client");
2219
+ if (fs3.existsSync(clientDir)) {
2220
+ this.app.use(express.static(clientDir));
2221
+ }
1671
2222
  const publicDir = path3.join(__dirname, "../public");
1672
2223
  if (fs3.existsSync(publicDir)) {
1673
- this.app.use(express.static(publicDir));
2224
+ this.app.use("/v1", express.static(publicDir));
1674
2225
  }
1675
2226
  this.app.get("/api/traces", (_req, res) => {
1676
2227
  try {
@@ -1706,10 +2257,28 @@ var DashboardServer = class {
1706
2257
  res.status(500).json({ error: "Failed to load trace events" });
1707
2258
  }
1708
2259
  });
1709
- this.app.get("/api/agents", (_req, res) => {
2260
+ this.app.get("/api/agents", (req, res) => {
1710
2261
  try {
1711
- const agents = this.stats.getAgentsList();
1712
- res.json(agents);
2262
+ const raw = this.stats.getAgentsList();
2263
+ for (const agent of raw) {
2264
+ if (!agent.displayName) {
2265
+ const traces = this.watcher.getTracesByAgent(agent.agentId);
2266
+ if (traces.length > 0) {
2267
+ const latest = traces[traces.length - 1];
2268
+ const name = latest == null ? void 0 : latest.name;
2269
+ if (name && name !== "default" && name !== agent.agentId && !name.startsWith("pipeline:") && name.length < 40) {
2270
+ agent.displayName = name;
2271
+ }
2272
+ }
2273
+ if (!agent.displayName) agent.displayName = agent.agentId;
2274
+ }
2275
+ }
2276
+ if (req.query.flat === "true") {
2277
+ return res.json(raw);
2278
+ }
2279
+ const deduped = deduplicateAgents(raw);
2280
+ const grouped = groupAgents(deduped);
2281
+ res.json(grouped);
1713
2282
  } catch (_error) {
1714
2283
  res.status(500).json({ error: "Failed to load agents" });
1715
2284
  }
@@ -1859,6 +2428,84 @@ var DashboardServer = class {
1859
2428
  res.status(500).json({ error: "Failed to load agent profile" });
1860
2429
  }
1861
2430
  });
2431
+ this.app.get("/api/process-model/:agentId", (req, res) => {
2432
+ try {
2433
+ const agentId = req.params.agentId;
2434
+ const allTraces = this.watcher.getTracesByAgent(agentId);
2435
+ if (allTraces.length === 0) {
2436
+ return res.status(404).json({ error: "No traces for agent" });
2437
+ }
2438
+ const transMap = /* @__PURE__ */ new Map();
2439
+ const nodeTypeMap = /* @__PURE__ */ new Map();
2440
+ const variantMap = /* @__PURE__ */ new Map();
2441
+ const durationMap = /* @__PURE__ */ new Map();
2442
+ for (const trace of allTraces) {
2443
+ const serialized = serializeTrace(trace);
2444
+ const nodes = serialized.nodes;
2445
+ if (!nodes || typeof nodes !== "object") continue;
2446
+ const nodeArr = Object.values(nodes);
2447
+ const sorted = nodeArr.filter((n) => n.name && typeof n.startTime === "number" && n.startTime > 0).sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
2448
+ for (let i = 0; i < sorted.length - 1; i++) {
2449
+ const from = sorted[i].name;
2450
+ const to = sorted[i + 1].name;
2451
+ const key = `${from}|||${to}`;
2452
+ transMap.set(key, (transMap.get(key) ?? 0) + 1);
2453
+ }
2454
+ for (const n of sorted) {
2455
+ if (n.name && n.type) nodeTypeMap.set(n.name, n.type);
2456
+ }
2457
+ const sig = sorted.map((n) => n.name).join("\u2192");
2458
+ if (sig) variantMap.set(sig, (variantMap.get(sig) ?? 0) + 1);
2459
+ for (const n of sorted) {
2460
+ if (n.name && n.endTime && n.startTime) {
2461
+ const dur = n.endTime - n.startTime;
2462
+ if (dur > 0) {
2463
+ const arr = durationMap.get(n.name) ?? [];
2464
+ arr.push(dur);
2465
+ durationMap.set(n.name, arr);
2466
+ }
2467
+ }
2468
+ }
2469
+ }
2470
+ const model = {
2471
+ transitions: [...transMap.entries()].map(([key, count]) => {
2472
+ const [from, to] = key.split("|||");
2473
+ return { from, to, count };
2474
+ }),
2475
+ nodeTypes: Object.fromEntries(nodeTypeMap)
2476
+ };
2477
+ const totalTraces = allTraces.length;
2478
+ const variants = [...variantMap.entries()].sort((a, b) => b[1] - a[1]).slice(0, 20).map(([sig, count]) => ({
2479
+ pathSignature: sig,
2480
+ count,
2481
+ percentage: totalTraces > 0 ? count / totalTraces * 100 : 0
2482
+ }));
2483
+ const bottlenecks = [...durationMap.entries()].map(([name, durations]) => {
2484
+ const sorted = durations.sort((a, b) => a - b);
2485
+ const p95 = sorted[Math.floor(sorted.length * 0.95)] ?? 0;
2486
+ return { nodeName: name, nodeType: nodeTypeMap.get(name) ?? "unknown", p95 };
2487
+ }).sort((a, b) => b.p95 - a.p95).slice(0, 15);
2488
+ const graphs = this.getGraphTraces(agentId);
2489
+ if (graphs.length > 0) {
2490
+ try {
2491
+ const coreBottlenecks = getBottlenecks(graphs).map((b) => ({
2492
+ nodeName: b.nodeName,
2493
+ nodeType: b.nodeType,
2494
+ p95: b.durations.sort((a, b2) => a - b2)[Math.floor(b.durations.length * 0.95)] ?? 0
2495
+ }));
2496
+ if (coreBottlenecks.length > bottlenecks.length) {
2497
+ bottlenecks.length = 0;
2498
+ bottlenecks.push(...coreBottlenecks);
2499
+ }
2500
+ } catch {
2501
+ }
2502
+ }
2503
+ res.json({ model, variants, bottlenecks });
2504
+ } catch (error) {
2505
+ console.error("Process model error:", error);
2506
+ res.status(500).json({ error: "Failed to compute process model" });
2507
+ }
2508
+ });
1862
2509
  this.app.get("/api/stats/:agentId", (req, res) => {
1863
2510
  try {
1864
2511
  const agentStats = this.stats.getAgentStats(req.params.agentId);
@@ -1871,6 +2518,7 @@ var DashboardServer = class {
1871
2518
  }
1872
2519
  });
1873
2520
  this.app.get("/api/process-health", (_req, res) => {
2521
+ var _a, _b;
1874
2522
  try {
1875
2523
  const now = Date.now();
1876
2524
  if (this.processHealthCache.result && now - this.processHealthCache.ts < 1e4) {
@@ -1881,67 +2529,203 @@ var DashboardServer = class {
1881
2529
  path3.dirname(this.config.tracesDir),
1882
2530
  ...this.config.dataDirs || []
1883
2531
  ];
1884
- const processConfig = discoverProcessConfig(discoveryDirs);
1885
- if (!processConfig) {
2532
+ const configs = discoverAllProcessConfigs(discoveryDirs);
2533
+ if (configs.length === 0) {
1886
2534
  return res.json(null);
1887
2535
  }
1888
- const alfredResult = auditProcesses(processConfig);
1889
- const openclawConfig = {
1890
- processName: "openclaw",
1891
- pidFile: void 0,
1892
- workersFile: void 0,
1893
- systemdUnit: null
1894
- };
1895
- const openclawResult = auditProcesses(openclawConfig);
1896
- const clawmetryConfig = {
1897
- processName: "clawmetry",
1898
- pidFile: void 0,
1899
- workersFile: void 0,
1900
- systemdUnit: null
1901
- };
1902
- const clawmetryResult = auditProcesses(clawmetryConfig);
1903
- const allOsProcesses = [
1904
- ...alfredResult.osProcesses,
1905
- ...openclawResult.osProcesses,
1906
- ...clawmetryResult.osProcesses
1907
- ];
2536
+ const services = [];
2537
+ const allKnownPids = /* @__PURE__ */ new Set();
2538
+ for (const config of configs) {
2539
+ const audit = auditProcesses(config);
2540
+ services.push({ name: config.processName, audit });
2541
+ if (((_a = audit.pidFile) == null ? void 0 : _a.pid) && !audit.pidFile.stale) allKnownPids.add(audit.pidFile.pid);
2542
+ if ((_b = audit.systemd) == null ? void 0 : _b.mainPid) allKnownPids.add(audit.systemd.mainPid);
2543
+ if (audit.workers) {
2544
+ if (audit.workers.orchestratorPid) allKnownPids.add(audit.workers.orchestratorPid);
2545
+ for (const w of audit.workers.workers) {
2546
+ if (w.pid) allKnownPids.add(w.pid);
2547
+ }
2548
+ }
2549
+ for (const p of audit.osProcesses) allKnownPids.add(p.pid);
2550
+ }
2551
+ const primary = services.find((s) => s.audit.pidFile) ?? services[0];
2552
+ const allOsProcesses = services.flatMap((s) => s.audit.osProcesses);
1908
2553
  const uniqueProcesses = allOsProcesses.filter(
1909
2554
  (proc, index, arr) => arr.findIndex((p) => p.pid === proc.pid) === index
1910
2555
  );
2556
+ const orphans = uniqueProcesses.filter(
2557
+ (p) => !allKnownPids.has(p.pid) && p.pid !== process.pid && p.pid !== process.ppid
2558
+ );
2559
+ const problems = services.flatMap(
2560
+ (s) => s.audit.problems.map((p) => `[${s.name}] ${p}`)
2561
+ );
1911
2562
  const result = {
1912
- ...alfredResult,
2563
+ // Backward-compatible fields from primary service
2564
+ pidFile: (primary == null ? void 0 : primary.audit.pidFile) ?? null,
2565
+ systemd: (primary == null ? void 0 : primary.audit.systemd) ?? null,
2566
+ workers: (primary == null ? void 0 : primary.audit.workers) ?? null,
1913
2567
  osProcesses: uniqueProcesses,
1914
- // Recalculate orphans based on all processes
1915
- orphans: uniqueProcesses.filter((p) => {
1916
- var _a;
1917
- const alfredKnownPids = /* @__PURE__ */ new Set();
1918
- if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale)
1919
- alfredKnownPids.add(alfredResult.pidFile.pid);
1920
- if (alfredResult.workers) {
1921
- if (alfredResult.workers.orchestratorPid)
1922
- alfredKnownPids.add(alfredResult.workers.orchestratorPid);
1923
- for (const w of alfredResult.workers.workers) {
1924
- if (w.pid) alfredKnownPids.add(w.pid);
2568
+ orphans,
2569
+ problems,
2570
+ // All discovered services with their individual audit results + metrics
2571
+ services: services.map((s) => {
2572
+ var _a2, _b2;
2573
+ const mainPid = ((_a2 = s.audit.pidFile) == null ? void 0 : _a2.pid) ?? ((_b2 = s.audit.systemd) == null ? void 0 : _b2.mainPid);
2574
+ const osProc = mainPid ? uniqueProcesses.find((p) => p.pid === mainPid) : void 0;
2575
+ return {
2576
+ name: s.name,
2577
+ pidFile: s.audit.pidFile,
2578
+ systemd: s.audit.systemd,
2579
+ workers: s.audit.workers,
2580
+ problems: s.audit.problems,
2581
+ metrics: osProc ? { cpu: osProc.cpu, mem: osProc.mem, elapsed: osProc.elapsed } : void 0
2582
+ };
2583
+ }),
2584
+ // Topology edges: parent-child relationships from process ppid
2585
+ topology: uniqueProcesses.map((p) => {
2586
+ try {
2587
+ const statusContent = fs3.readFileSync(`/proc/${p.pid}/status`, "utf8");
2588
+ const ppidMatch = statusContent.match(/^PPid:\s+(\d+)/m);
2589
+ const ppid = ppidMatch ? parseInt(ppidMatch[1] ?? "0", 10) : 0;
2590
+ if (ppid > 1 && allKnownPids.has(ppid)) {
2591
+ return { source: ppid, target: p.pid };
1925
2592
  }
2593
+ } catch {
1926
2594
  }
1927
- const isOpenClawProcess = p.cmdline.includes("openclaw") || p.cmdline.includes("clawmetry");
1928
- return !alfredKnownPids.has(p.pid) && !isOpenClawProcess && p.pid !== process.pid && p.pid !== process.ppid;
1929
- })
2595
+ return null;
2596
+ }).filter(Boolean)
1930
2597
  };
1931
- const openclawProblems = [];
1932
- if (openclawResult.osProcesses.length === 0) {
1933
- openclawProblems.push("No OpenClaw gateway processes detected");
1934
- }
1935
- if (clawmetryResult.osProcesses.length === 0) {
1936
- openclawProblems.push("No clawmetry processes detected");
1937
- }
1938
- result.problems = [...alfredResult.problems || [], ...openclawProblems];
1939
2598
  this.processHealthCache = { result, ts: now };
1940
2599
  res.json(result);
1941
2600
  } catch (_error) {
1942
2601
  res.status(500).json({ error: "Failed to audit processes" });
1943
2602
  }
1944
2603
  });
2604
+ this.app.get("/api/directories", (_req, res) => {
2605
+ try {
2606
+ const home = process.env.HOME ?? "/home/trader";
2607
+ const configPath = path3.join(home, ".agentflow/dashboard-config.json");
2608
+ let extraDirs = [];
2609
+ try {
2610
+ if (fs3.existsSync(configPath)) {
2611
+ const cfg = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
2612
+ extraDirs = cfg.extraDirs ?? [];
2613
+ }
2614
+ } catch {
2615
+ }
2616
+ const watched = [
2617
+ this.config.tracesDir,
2618
+ ...this.config.dataDirs || [],
2619
+ ...extraDirs
2620
+ ];
2621
+ const discovered = [];
2622
+ try {
2623
+ const { execSync } = __require("child_process");
2624
+ const raw = execSync(
2625
+ "systemctl --user show --property=ExecStart --no-pager alfred.service openclaw-gateway.service 2>/dev/null",
2626
+ { encoding: "utf8", timeout: 5e3 }
2627
+ );
2628
+ for (const line of raw.split("\n")) {
2629
+ const match = line.match(/path=([^\s;]+)/);
2630
+ if (match == null ? void 0 : match[1]) {
2631
+ const dir = path3.dirname(match[1]);
2632
+ if (fs3.existsSync(dir)) discovered.push(dir);
2633
+ }
2634
+ }
2635
+ } catch {
2636
+ }
2637
+ const commonPaths = [
2638
+ path3.join(home, ".alfred/traces"),
2639
+ path3.join(home, ".alfred/data"),
2640
+ path3.join(home, ".openclaw/workspace/traces"),
2641
+ path3.join(home, ".openclaw/subagents"),
2642
+ path3.join(home, ".openclaw/cron/runs"),
2643
+ path3.join(home, ".openclaw/cron"),
2644
+ path3.join(home, ".openclaw/agents/main/sessions"),
2645
+ path3.join(home, ".agentflow/traces")
2646
+ ];
2647
+ for (const p of commonPaths) {
2648
+ if (fs3.existsSync(p) && !discovered.includes(p)) {
2649
+ discovered.push(p);
2650
+ }
2651
+ }
2652
+ const watchedSet = new Set(watched.map((w) => path3.resolve(w)));
2653
+ const suggested = discovered.filter((d) => !watchedSet.has(path3.resolve(d)));
2654
+ res.json({ watched, discovered, suggested });
2655
+ } catch (error) {
2656
+ console.error("Directory discovery error:", error);
2657
+ res.status(500).json({ error: "Failed to discover directories" });
2658
+ }
2659
+ });
2660
+ this.app.post("/api/directories", express.json(), (req, res) => {
2661
+ try {
2662
+ const { add, remove } = req.body;
2663
+ if (add && !fs3.existsSync(add)) {
2664
+ return res.status(400).json({ error: `Directory does not exist: ${add}` });
2665
+ }
2666
+ const configPath = path3.join(process.env.HOME ?? "/home/trader", ".agentflow/dashboard-config.json");
2667
+ let config = {};
2668
+ try {
2669
+ if (fs3.existsSync(configPath)) {
2670
+ config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
2671
+ }
2672
+ } catch {
2673
+ }
2674
+ if (!config.extraDirs) config.extraDirs = [];
2675
+ if (add && !config.extraDirs.includes(add)) {
2676
+ config.extraDirs.push(add);
2677
+ }
2678
+ if (remove) {
2679
+ config.extraDirs = config.extraDirs.filter((d) => d !== remove);
2680
+ }
2681
+ fs3.mkdirSync(path3.dirname(configPath), { recursive: true });
2682
+ fs3.writeFileSync(configPath, JSON.stringify(config, null, 2));
2683
+ res.json({ ok: true, extraDirs: config.extraDirs });
2684
+ } catch (error) {
2685
+ console.error("Directory config error:", error);
2686
+ res.status(500).json({ error: "Failed to update directory config" });
2687
+ }
2688
+ });
2689
+ this.app.post("/v1/traces", express.json({ limit: "10mb" }), (req, res) => {
2690
+ try {
2691
+ const traces = parseOtlpPayload(req.body);
2692
+ let ingested = 0;
2693
+ for (const trace of traces) {
2694
+ const nodes = /* @__PURE__ */ new Map();
2695
+ for (const [id, node] of Object.entries(trace.nodes)) {
2696
+ nodes.set(id, { ...node, state: {} });
2697
+ }
2698
+ const watched = {
2699
+ id: trace.id,
2700
+ rootNodeId: Object.keys(trace.nodes)[0] ?? "",
2701
+ agentId: trace.agentId,
2702
+ name: trace.name,
2703
+ trigger: trace.trigger,
2704
+ startTime: trace.startTime,
2705
+ endTime: trace.endTime,
2706
+ status: trace.status,
2707
+ nodes,
2708
+ edges: [],
2709
+ events: [],
2710
+ metadata: { ...trace.metadata, adapterSource: "otel" },
2711
+ sessionEvents: [],
2712
+ sourceType: "session",
2713
+ filename: `otel-${trace.id}`,
2714
+ lastModified: Date.now(),
2715
+ sourceDir: "http-collector"
2716
+ };
2717
+ this.watcher.traces.set(`otel:${trace.id}`, watched);
2718
+ ingested++;
2719
+ }
2720
+ if (ingested > 0) {
2721
+ this.broadcast({ type: "traces-updated", count: ingested });
2722
+ }
2723
+ res.json({ ok: true, tracesIngested: ingested });
2724
+ } catch (error) {
2725
+ console.error("OTLP collector error:", error);
2726
+ res.status(400).json({ error: "Failed to parse OTLP payload" });
2727
+ }
2728
+ });
1945
2729
  this.app.get("/health", (_req, res) => {
1946
2730
  res.json({
1947
2731
  status: "ok",
@@ -1953,10 +2737,18 @@ var DashboardServer = class {
1953
2737
  this.app.get("/ready", (_req, res) => {
1954
2738
  res.json({ status: "ready" });
1955
2739
  });
2740
+ this.app.get("/v1/*", (_req, res) => {
2741
+ const legacyIndex = path3.join(__dirname, "../public/index.html");
2742
+ if (fs3.existsSync(legacyIndex)) {
2743
+ res.sendFile(legacyIndex);
2744
+ } else {
2745
+ res.status(404).send("Legacy dashboard not found");
2746
+ }
2747
+ });
1956
2748
  this.app.get("*", (_req, res) => {
1957
- const indexPath = path3.join(__dirname, "../public/index.html");
1958
- if (fs3.existsSync(indexPath)) {
1959
- res.sendFile(indexPath);
2749
+ const clientIndex = path3.join(__dirname, "../dist/client/index.html");
2750
+ if (fs3.existsSync(clientIndex)) {
2751
+ res.sendFile(clientIndex);
1960
2752
  } else {
1961
2753
  res.status(404).send("Dashboard not found - public files may not be built");
1962
2754
  }
@@ -2203,21 +2995,21 @@ var DashboardServer = class {
2203
2995
  });
2204
2996
  }
2205
2997
  async start() {
2206
- return new Promise((resolve3) => {
2998
+ return new Promise((resolve4) => {
2207
2999
  const host = this.config.host || "localhost";
2208
3000
  this.server.listen(this.config.port, host, () => {
2209
3001
  console.log(`AgentFlow Dashboard running at http://${host}:${this.config.port}`);
2210
3002
  console.log(`Watching traces in: ${this.config.tracesDir}`);
2211
- resolve3();
3003
+ resolve4();
2212
3004
  });
2213
3005
  });
2214
3006
  }
2215
3007
  async stop() {
2216
- return new Promise((resolve3) => {
3008
+ return new Promise((resolve4) => {
2217
3009
  this.watcher.stop();
2218
3010
  this.server.close(() => {
2219
3011
  console.log("Dashboard server stopped");
2220
- resolve3();
3012
+ resolve4();
2221
3013
  });
2222
3014
  });
2223
3015
  }