agentflow-dashboard 0.3.0 → 0.4.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 +116 -297
- package/dist/{chunk-2FTN742J.js → chunk-25MUPUYY.js} +748 -72
- package/dist/cli.cjs +584 -56
- package/dist/cli.js +2 -147
- package/dist/index.cjs +740 -67
- package/dist/index.js +1 -1
- package/dist/public/dashboard.js +401 -25
- package/dist/public/debug.html +43 -0
- package/dist/public/index.html +215 -1
- package/dist/server.cjs +1875 -0
- package/dist/server.js +6 -0
- package/package.json +21 -5
- package/public/dashboard.js +401 -25
- package/public/debug.html +43 -0
- package/public/index.html +215 -1
package/dist/cli.cjs
CHANGED
|
@@ -226,7 +226,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
|
|
|
226
226
|
var import_events = require("events");
|
|
227
227
|
var fs = __toESM(require("fs"), 1);
|
|
228
228
|
var path = __toESM(require("path"), 1);
|
|
229
|
-
var TraceWatcher = class extends import_events.EventEmitter {
|
|
229
|
+
var TraceWatcher = class _TraceWatcher extends import_events.EventEmitter {
|
|
230
230
|
watchers = [];
|
|
231
231
|
traces = /* @__PURE__ */ new Map();
|
|
232
232
|
tracesDir;
|
|
@@ -254,22 +254,65 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
254
254
|
}
|
|
255
255
|
loadExistingFiles() {
|
|
256
256
|
let totalFiles = 0;
|
|
257
|
+
let totalDirectories = 0;
|
|
257
258
|
for (const dir of this.allWatchDirs) {
|
|
258
259
|
if (!fs.existsSync(dir)) continue;
|
|
259
260
|
try {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
this.loadFile(path.join(dir, file));
|
|
264
|
-
}
|
|
261
|
+
totalDirectories++;
|
|
262
|
+
const loadedFiles = this.scanDirectoryRecursive(dir);
|
|
263
|
+
totalFiles += loadedFiles;
|
|
265
264
|
} catch (error) {
|
|
266
265
|
console.error(`Error scanning directory ${dir}:`, error);
|
|
267
266
|
}
|
|
268
267
|
}
|
|
269
|
-
console.log(`Scanned ${
|
|
268
|
+
console.log(`Scanned ${totalDirectories} directories (recursive), loaded ${this.traces.size} items from ${totalFiles} files`);
|
|
269
|
+
}
|
|
270
|
+
/** Recursively scan directory for supported file types */
|
|
271
|
+
scanDirectoryRecursive(dir, depth = 0) {
|
|
272
|
+
if (depth > 10) return 0;
|
|
273
|
+
let fileCount = 0;
|
|
274
|
+
try {
|
|
275
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
if (entry.name.startsWith(".")) continue;
|
|
278
|
+
const fullPath = path.join(dir, entry.name);
|
|
279
|
+
if (entry.isFile()) {
|
|
280
|
+
if (this.isSupportedFile(entry.name)) {
|
|
281
|
+
if (this.loadFile(fullPath)) {
|
|
282
|
+
fileCount++;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
} else if (entry.isDirectory()) {
|
|
286
|
+
fileCount += this.scanDirectoryRecursive(fullPath, depth + 1);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.warn(`Cannot read directory ${dir}:`, error.message);
|
|
291
|
+
}
|
|
292
|
+
return fileCount;
|
|
270
293
|
}
|
|
294
|
+
/** Check if file type is supported */
|
|
295
|
+
isSupportedFile(filename) {
|
|
296
|
+
return filename.endsWith(".json") || filename.endsWith(".jsonl") || filename.endsWith(".log") || filename.endsWith(".trace");
|
|
297
|
+
}
|
|
298
|
+
/** File names that are config/state, not traces — skip them. */
|
|
299
|
+
static SKIP_FILES = /* @__PURE__ */ new Set([
|
|
300
|
+
"workers.json",
|
|
301
|
+
"package.json",
|
|
302
|
+
"package-lock.json",
|
|
303
|
+
"tsconfig.json",
|
|
304
|
+
"biome.json",
|
|
305
|
+
"jobs.json",
|
|
306
|
+
"auth.json",
|
|
307
|
+
"models.json",
|
|
308
|
+
"config.json"
|
|
309
|
+
]);
|
|
310
|
+
static SKIP_SUFFIXES = ["-state.json", "-config.json", "-watch-state.json", ".tmp", ".bak", ".backup"];
|
|
271
311
|
/** Load a .json trace, .jsonl session file, or .log file. */
|
|
272
312
|
loadFile(filePath) {
|
|
313
|
+
const filename = path.basename(filePath);
|
|
314
|
+
if (_TraceWatcher.SKIP_FILES.has(filename)) return false;
|
|
315
|
+
if (_TraceWatcher.SKIP_SUFFIXES.some((s) => filename.endsWith(s))) return false;
|
|
273
316
|
if (filePath.endsWith(".jsonl")) {
|
|
274
317
|
return this.loadSessionFile(filePath);
|
|
275
318
|
}
|
|
@@ -283,12 +326,23 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
283
326
|
const content = fs.readFileSync(filePath, "utf8");
|
|
284
327
|
const filename = path.basename(filePath);
|
|
285
328
|
const stats = fs.statSync(filePath);
|
|
329
|
+
if (filename.startsWith("openclaw-") || filePath.includes("openclaw")) {
|
|
330
|
+
const result = this.loadOpenClawLogFile(content, filename, filePath, stats);
|
|
331
|
+
if (result) return true;
|
|
332
|
+
}
|
|
286
333
|
const traces = this.parseUniversalLog(content, filename, filePath);
|
|
287
334
|
for (let i = 0; i < traces.length; i++) {
|
|
288
335
|
const trace = traces[i];
|
|
336
|
+
if (trace.nodes && !(trace.nodes instanceof Map)) {
|
|
337
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
338
|
+
for (const [key2, value] of Object.entries(trace.nodes)) {
|
|
339
|
+
nodeMap.set(key2, value);
|
|
340
|
+
}
|
|
341
|
+
trace.nodes = nodeMap;
|
|
342
|
+
}
|
|
289
343
|
trace.filename = filename;
|
|
290
344
|
trace.lastModified = stats.mtime.getTime();
|
|
291
|
-
trace.sourceType = "trace";
|
|
345
|
+
trace.sourceType = trace.sourceType || "trace";
|
|
292
346
|
trace.sourceDir = path.dirname(filePath);
|
|
293
347
|
const key = traces.length === 1 ? this.traceKey(filePath) : `${this.traceKey(filePath)}-${i}`;
|
|
294
348
|
this.traces.set(key, trace);
|
|
@@ -328,10 +382,26 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
328
382
|
if (activity.timestamp > session.endTime) {
|
|
329
383
|
session.endTime = activity.timestamp;
|
|
330
384
|
}
|
|
385
|
+
if (activity.level === "error" || activity.level === "fatal") {
|
|
386
|
+
session.status = "failed";
|
|
387
|
+
}
|
|
331
388
|
}
|
|
332
389
|
const traces = Array.from(activities.values()).filter(
|
|
333
390
|
(session) => Object.keys(session.nodes).length > 0
|
|
334
391
|
);
|
|
392
|
+
for (const trace of traces) {
|
|
393
|
+
const sortedNodes = Object.values(trace.nodes).sort((a, b) => a.startTime - b.startTime);
|
|
394
|
+
trace.sessionEvents = sortedNodes.map((node) => ({
|
|
395
|
+
type: node.status === "failed" ? "tool_result" : "system",
|
|
396
|
+
timestamp: node.startTime,
|
|
397
|
+
name: node.name,
|
|
398
|
+
content: node.metadata.count > 1 ? `${node.name} (${node.metadata.count} occurrences, ${node.metadata.errorCount || 0} errors)` : node.name,
|
|
399
|
+
duration: node.endTime - node.startTime,
|
|
400
|
+
toolError: node.status === "failed" ? `${node.metadata.errorCount || 1} error(s)` : void 0,
|
|
401
|
+
id: node.id
|
|
402
|
+
}));
|
|
403
|
+
trace.sourceType = "session";
|
|
404
|
+
}
|
|
335
405
|
if (traces.length === 0) {
|
|
336
406
|
const stats = fs.statSync(filePath);
|
|
337
407
|
traces.push({
|
|
@@ -345,6 +415,7 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
345
415
|
status: "completed",
|
|
346
416
|
startTime: stats.mtime.getTime(),
|
|
347
417
|
endTime: stats.mtime.getTime(),
|
|
418
|
+
children: [],
|
|
348
419
|
metadata: { lineCount: lines.length, path: filePath }
|
|
349
420
|
}
|
|
350
421
|
},
|
|
@@ -411,38 +482,37 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
411
482
|
...kvPairs
|
|
412
483
|
};
|
|
413
484
|
}
|
|
485
|
+
/** Strip ANSI escape codes from a string. */
|
|
486
|
+
stripAnsi(str) {
|
|
487
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
488
|
+
}
|
|
414
489
|
extractTimestamp(line) {
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
const isoMatch = line.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
|
|
490
|
+
const clean = this.stripAnsi(line);
|
|
491
|
+
const isoMatch = clean.match(/(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)/);
|
|
418
492
|
if (isoMatch) return new Date(isoMatch[1]).getTime();
|
|
419
493
|
return null;
|
|
420
494
|
}
|
|
421
495
|
extractLogLevel(line) {
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
return levelMatch ? levelMatch[1].toLowerCase() : null;
|
|
496
|
+
const clean = this.stripAnsi(line);
|
|
497
|
+
const levelMatch = clean.match(/\b(debug|info|warn|warning|error|fatal|trace)\b/i);
|
|
498
|
+
return levelMatch ? levelMatch[1].trim().toLowerCase() : null;
|
|
426
499
|
}
|
|
427
500
|
extractAction(line) {
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
501
|
+
const clean = this.stripAnsi(line);
|
|
502
|
+
const actionMatch = clean.match(/\]\s+(\S+)/);
|
|
503
|
+
if (actionMatch) return actionMatch[1].trim();
|
|
504
|
+
const afterLevel = clean.replace(/^.*?(debug|info|warn|warning|error|fatal|trace)\s*\]?\s*/i, "");
|
|
505
|
+
return afterLevel.split(/\s+/)[0] || "";
|
|
432
506
|
}
|
|
433
507
|
extractKeyValuePairs(line) {
|
|
434
508
|
const pairs = {};
|
|
435
|
-
const
|
|
509
|
+
const clean = this.stripAnsi(line);
|
|
510
|
+
const kvRegex = /(\w+)=('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|\S+)/g;
|
|
436
511
|
let match;
|
|
437
|
-
while ((match =
|
|
512
|
+
while ((match = kvRegex.exec(clean)) !== null) {
|
|
513
|
+
if (match[1] === "Z" || match[1] === "m") continue;
|
|
438
514
|
pairs[match[1]] = this.parseValue(match[2]);
|
|
439
515
|
}
|
|
440
|
-
if (Object.keys(pairs).length === 0) {
|
|
441
|
-
const kvRegex = /(\w+)=([^\s]+)/g;
|
|
442
|
-
while ((match = kvRegex.exec(line)) !== null) {
|
|
443
|
-
pairs[match[1]] = this.parseValue(match[2]);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
516
|
return pairs;
|
|
447
517
|
}
|
|
448
518
|
parseValue(value) {
|
|
@@ -466,30 +536,51 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
466
536
|
if (kvPairs.component) return kvPairs.component;
|
|
467
537
|
if (kvPairs.service) return kvPairs.service;
|
|
468
538
|
if (kvPairs.module) return kvPairs.module;
|
|
469
|
-
return
|
|
539
|
+
if (kvPairs.worker) return kvPairs.worker;
|
|
540
|
+
return action || "unknown";
|
|
470
541
|
}
|
|
471
542
|
detectOperation(action, kvPairs) {
|
|
472
543
|
if (action.includes(".")) return action.split(".").slice(1).join(".");
|
|
473
544
|
if (kvPairs.operation) return kvPairs.operation;
|
|
474
545
|
if (kvPairs.method) return kvPairs.method;
|
|
546
|
+
if (kvPairs.action) return kvPairs.action;
|
|
475
547
|
return action || "activity";
|
|
476
548
|
}
|
|
477
549
|
extractSessionIdentifier(activity) {
|
|
478
|
-
return activity.
|
|
550
|
+
return activity.session_id || activity.run_id || activity.request_id || activity.trace_id || activity.sweep_id || activity.transaction_id || "default";
|
|
479
551
|
}
|
|
480
552
|
detectAgentIdentifier(activity, filename, filePath) {
|
|
481
|
-
if (activity.
|
|
482
|
-
const
|
|
483
|
-
if (
|
|
484
|
-
|
|
553
|
+
if (activity.agent_id) {
|
|
554
|
+
const agentId = activity.agent_id;
|
|
555
|
+
if (agentId.startsWith("vault-")) return agentId;
|
|
556
|
+
if (agentId === "main" && filePath.includes(".alfred/")) return "alfred-main";
|
|
557
|
+
return agentId;
|
|
558
|
+
}
|
|
559
|
+
const pathAgent = this.extractAgentFromPath(filePath);
|
|
560
|
+
if (filePath.includes(".alfred/") && !pathAgent.startsWith("alfred-")) {
|
|
561
|
+
const basename2 = path.basename(filePath, path.extname(filePath));
|
|
562
|
+
if (basename2.match(/^(janitor|curator|distiller|surveyor|alfred)$/)) {
|
|
563
|
+
return basename2 === "alfred" ? "alfred" : `alfred-${basename2}`;
|
|
485
564
|
}
|
|
486
|
-
return activity.component;
|
|
487
565
|
}
|
|
488
|
-
return
|
|
566
|
+
return pathAgent;
|
|
489
567
|
}
|
|
490
568
|
extractAgentFromPath(filePath) {
|
|
491
569
|
const filename = path.basename(filePath, path.extname(filePath));
|
|
492
570
|
const pathParts = filePath.split(path.sep);
|
|
571
|
+
if (filePath.includes(".openclaw/")) {
|
|
572
|
+
const agentsIndex = pathParts.lastIndexOf("agents");
|
|
573
|
+
if (agentsIndex !== -1 && agentsIndex + 1 < pathParts.length) {
|
|
574
|
+
return `openclaw-${pathParts[agentsIndex + 1]}`;
|
|
575
|
+
}
|
|
576
|
+
if (filename.startsWith("openclaw-")) {
|
|
577
|
+
return "openclaw-gateway";
|
|
578
|
+
}
|
|
579
|
+
return "openclaw";
|
|
580
|
+
}
|
|
581
|
+
if (filePath.includes(".alfred/") || filename.includes("alfred")) {
|
|
582
|
+
return "alfred";
|
|
583
|
+
}
|
|
493
584
|
for (const part of pathParts.reverse()) {
|
|
494
585
|
if (part.match(/agent|worker|service|daemon|bot|ai|llm/i)) {
|
|
495
586
|
return part;
|
|
@@ -511,7 +602,18 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
511
602
|
return "event";
|
|
512
603
|
}
|
|
513
604
|
addActivityNode(session, activity) {
|
|
514
|
-
const nodeId = `${activity.component}-${activity.operation}
|
|
605
|
+
const nodeId = `${activity.component}-${activity.operation}`;
|
|
606
|
+
if (session.nodes[nodeId]) {
|
|
607
|
+
const node2 = session.nodes[nodeId];
|
|
608
|
+
node2.endTime = Math.max(node2.endTime, activity.timestamp);
|
|
609
|
+
node2.startTime = Math.min(node2.startTime, activity.timestamp);
|
|
610
|
+
node2.metadata.count = (node2.metadata.count || 1) + 1;
|
|
611
|
+
if (activity.level === "error" || activity.level === "fatal") {
|
|
612
|
+
node2.status = "failed";
|
|
613
|
+
node2.metadata.errorCount = (node2.metadata.errorCount || 0) + 1;
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
515
617
|
const node = {
|
|
516
618
|
id: nodeId,
|
|
517
619
|
type: activity.component,
|
|
@@ -519,7 +621,8 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
519
621
|
status: this.getUniversalNodeStatus(activity),
|
|
520
622
|
startTime: activity.timestamp,
|
|
521
623
|
endTime: activity.timestamp,
|
|
522
|
-
|
|
624
|
+
children: [],
|
|
625
|
+
metadata: { ...activity, count: 1 }
|
|
523
626
|
};
|
|
524
627
|
session.nodes[nodeId] = node;
|
|
525
628
|
if (!session.rootNodeId) {
|
|
@@ -534,22 +637,275 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
534
637
|
if ((_b = activity.operation) == null ? void 0 : _b.match(/complete|finish|end|done/i)) return "completed";
|
|
535
638
|
return "completed";
|
|
536
639
|
}
|
|
640
|
+
/** Parse OpenClaw tslog-format log files with session run results. */
|
|
641
|
+
loadOpenClawLogFile(content, filename, filePath, stats) {
|
|
642
|
+
var _a, _b, _c, _d;
|
|
643
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
644
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
645
|
+
for (const line of lines) {
|
|
646
|
+
try {
|
|
647
|
+
const parsed = JSON.parse(line);
|
|
648
|
+
if (parsed["0"] && typeof parsed["0"] === "string") {
|
|
649
|
+
try {
|
|
650
|
+
const inner = typeof parsed["0"] === "string" && parsed["0"].startsWith("{") ? JSON.parse(parsed["0"]) : null;
|
|
651
|
+
if ((inner == null ? void 0 : inner.payloads) && ((_a = inner == null ? void 0 : inner.meta) == null ? void 0 : _a.agentMeta)) {
|
|
652
|
+
const agentMeta = inner.meta.agentMeta;
|
|
653
|
+
const sessionId = agentMeta.sessionId || "unknown";
|
|
654
|
+
const agentName = this.openClawSessionIdToAgent(sessionId);
|
|
655
|
+
const timestamp = parsed.time ? new Date(parsed.time).getTime() : ((_b = parsed._meta) == null ? void 0 : _b.date) ? new Date(parsed._meta.date).getTime() : stats.mtime.getTime();
|
|
656
|
+
const texts = (inner.payloads || []).map((p) => p.text || "").filter(Boolean);
|
|
657
|
+
if (!sessions.has(sessionId)) {
|
|
658
|
+
sessions.set(sessionId, { entries: [] });
|
|
659
|
+
}
|
|
660
|
+
sessions.get(sessionId).entries.push({
|
|
661
|
+
text: texts.join("\n"),
|
|
662
|
+
timestamp,
|
|
663
|
+
sessionId,
|
|
664
|
+
provider: agentMeta.provider || "",
|
|
665
|
+
model: agentMeta.model || "",
|
|
666
|
+
usage: agentMeta.usage || {},
|
|
667
|
+
durationMs: inner.meta.durationMs || 0,
|
|
668
|
+
agentName
|
|
669
|
+
});
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
} catch {
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (parsed.payloads && ((_c = parsed.meta) == null ? void 0 : _c.agentMeta)) {
|
|
676
|
+
const agentMeta = parsed.meta.agentMeta;
|
|
677
|
+
const sessionId = agentMeta.sessionId || "unknown";
|
|
678
|
+
const agentName = this.openClawSessionIdToAgent(sessionId);
|
|
679
|
+
const timestamp = parsed.time ? new Date(parsed.time).getTime() : ((_d = parsed._meta) == null ? void 0 : _d.date) ? new Date(parsed._meta.date).getTime() : stats.mtime.getTime();
|
|
680
|
+
const texts = (parsed.payloads || []).map((p) => p.text || "").filter(Boolean);
|
|
681
|
+
if (!sessions.has(sessionId)) {
|
|
682
|
+
sessions.set(sessionId, { entries: [] });
|
|
683
|
+
}
|
|
684
|
+
sessions.get(sessionId).entries.push({
|
|
685
|
+
text: texts.join("\n"),
|
|
686
|
+
timestamp,
|
|
687
|
+
sessionId,
|
|
688
|
+
provider: agentMeta.provider || "",
|
|
689
|
+
model: agentMeta.model || "",
|
|
690
|
+
usage: agentMeta.usage || {},
|
|
691
|
+
durationMs: parsed.meta.durationMs || 0,
|
|
692
|
+
agentName
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
} catch {
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (sessions.size === 0) return false;
|
|
699
|
+
let traceIndex = 0;
|
|
700
|
+
for (const [sessionId, session] of sessions) {
|
|
701
|
+
const entries = session.entries;
|
|
702
|
+
if (entries.length === 0) continue;
|
|
703
|
+
const firstEntry = entries[0];
|
|
704
|
+
const lastEntry = entries[entries.length - 1];
|
|
705
|
+
const agentId = firstEntry.agentName;
|
|
706
|
+
let totalInput = 0, totalOutput = 0, totalTokens = 0;
|
|
707
|
+
let totalDuration = 0;
|
|
708
|
+
for (const entry of entries) {
|
|
709
|
+
totalInput += entry.usage.input || 0;
|
|
710
|
+
totalOutput += entry.usage.output || 0;
|
|
711
|
+
totalTokens += entry.usage.total || 0;
|
|
712
|
+
totalDuration += entry.durationMs;
|
|
713
|
+
}
|
|
714
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
715
|
+
const rootId = `openclaw-${sessionId.slice(0, 12)}`;
|
|
716
|
+
for (let j = 0; j < entries.length; j++) {
|
|
717
|
+
const e = entries[j];
|
|
718
|
+
const nodeId = `entry-${j}`;
|
|
719
|
+
nodes.set(nodeId, {
|
|
720
|
+
id: nodeId,
|
|
721
|
+
type: "tool",
|
|
722
|
+
name: `${e.model}: ${e.sessionId}`,
|
|
723
|
+
startTime: e.timestamp - e.durationMs,
|
|
724
|
+
endTime: e.timestamp,
|
|
725
|
+
status: "completed",
|
|
726
|
+
parentId: rootId,
|
|
727
|
+
children: [],
|
|
728
|
+
metadata: {
|
|
729
|
+
provider: e.provider,
|
|
730
|
+
model: e.model,
|
|
731
|
+
durationMs: e.durationMs,
|
|
732
|
+
usage: e.usage,
|
|
733
|
+
preview: e.text.slice(0, 200)
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
nodes.set(rootId, {
|
|
738
|
+
id: rootId,
|
|
739
|
+
type: "agent",
|
|
740
|
+
name: sessionId,
|
|
741
|
+
startTime: firstEntry.timestamp - (firstEntry.durationMs || 0),
|
|
742
|
+
endTime: lastEntry.timestamp,
|
|
743
|
+
status: "completed",
|
|
744
|
+
parentId: void 0,
|
|
745
|
+
children: Array.from(nodes.keys()).filter((k) => k !== rootId),
|
|
746
|
+
metadata: {
|
|
747
|
+
provider: firstEntry.provider,
|
|
748
|
+
model: firstEntry.model,
|
|
749
|
+
sessionId,
|
|
750
|
+
totalTokens,
|
|
751
|
+
inputTokens: totalInput,
|
|
752
|
+
outputTokens: totalOutput,
|
|
753
|
+
durationMs: totalDuration
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
const sessionEvents = entries.map((e, idx) => ({
|
|
757
|
+
type: "assistant",
|
|
758
|
+
timestamp: e.timestamp,
|
|
759
|
+
name: e.model,
|
|
760
|
+
content: e.text,
|
|
761
|
+
model: e.model,
|
|
762
|
+
provider: e.provider,
|
|
763
|
+
tokens: { input: e.usage.input || 0, output: e.usage.output || 0, total: e.usage.total || 0 },
|
|
764
|
+
duration: e.durationMs,
|
|
765
|
+
id: `entry-${idx}`
|
|
766
|
+
}));
|
|
767
|
+
const trace = {
|
|
768
|
+
id: sessionId,
|
|
769
|
+
nodes,
|
|
770
|
+
edges: [],
|
|
771
|
+
events: [],
|
|
772
|
+
startTime: firstEntry.timestamp - (firstEntry.durationMs || 0),
|
|
773
|
+
endTime: lastEntry.timestamp,
|
|
774
|
+
agentId,
|
|
775
|
+
trigger: "cron",
|
|
776
|
+
name: sessionId,
|
|
777
|
+
traceId: sessionId,
|
|
778
|
+
spanId: sessionId,
|
|
779
|
+
filename,
|
|
780
|
+
lastModified: stats.mtime.getTime(),
|
|
781
|
+
sourceType: "session",
|
|
782
|
+
sourceDir: path.dirname(filePath),
|
|
783
|
+
sessionEvents,
|
|
784
|
+
tokenUsage: { input: totalInput, output: totalOutput, total: totalTokens || totalInput + totalOutput, cost: 0 },
|
|
785
|
+
metadata: {
|
|
786
|
+
provider: firstEntry.provider,
|
|
787
|
+
model: firstEntry.model,
|
|
788
|
+
durationMs: totalDuration,
|
|
789
|
+
source: "openclaw-log"
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
const key = sessions.size === 1 ? this.traceKey(filePath) : `${this.traceKey(filePath)}-${traceIndex}`;
|
|
793
|
+
this.traces.set(key, trace);
|
|
794
|
+
traceIndex++;
|
|
795
|
+
}
|
|
796
|
+
return traceIndex > 0;
|
|
797
|
+
}
|
|
798
|
+
/** Map OpenClaw sessionId prefix to agent name. */
|
|
799
|
+
openClawSessionIdToAgent(sessionId) {
|
|
800
|
+
if (sessionId.startsWith("janitor-")) return "vault-janitor";
|
|
801
|
+
if (sessionId.startsWith("curator-")) return "vault-curator";
|
|
802
|
+
if (sessionId.startsWith("distiller-")) return "vault-distiller";
|
|
803
|
+
if (sessionId.startsWith("main-")) return "main";
|
|
804
|
+
const firstSegment = sessionId.split("-")[0];
|
|
805
|
+
if (firstSegment) return firstSegment;
|
|
806
|
+
return "openclaw";
|
|
807
|
+
}
|
|
537
808
|
loadTraceFile(filePath) {
|
|
538
809
|
try {
|
|
539
810
|
const content = fs.readFileSync(filePath, "utf8");
|
|
540
|
-
const graph = (0, import_agentflow_core2.loadGraph)(content);
|
|
541
811
|
const filename = path.basename(filePath);
|
|
812
|
+
if (filename === "sessions.json") {
|
|
813
|
+
return this.loadSessionsIndex(filePath, content);
|
|
814
|
+
}
|
|
815
|
+
const graph = (0, import_agentflow_core2.loadGraph)(content);
|
|
542
816
|
const stats = fs.statSync(filePath);
|
|
543
817
|
graph.filename = filename;
|
|
544
818
|
graph.lastModified = stats.mtime.getTime();
|
|
545
819
|
graph.sourceType = "trace";
|
|
546
820
|
graph.sourceDir = path.dirname(filePath);
|
|
821
|
+
if (graph.nodes instanceof Map) {
|
|
822
|
+
for (const node of graph.nodes.values()) {
|
|
823
|
+
if (!node.children) node.children = [];
|
|
824
|
+
}
|
|
825
|
+
}
|
|
547
826
|
this.traces.set(this.traceKey(filePath), graph);
|
|
548
827
|
return true;
|
|
549
828
|
} catch {
|
|
550
829
|
return false;
|
|
551
830
|
}
|
|
552
831
|
}
|
|
832
|
+
/** Parse sessions.json index to discover agents and their sessions. */
|
|
833
|
+
loadSessionsIndex(filePath, content) {
|
|
834
|
+
try {
|
|
835
|
+
const data = JSON.parse(content);
|
|
836
|
+
if (typeof data !== "object" || data === null) return false;
|
|
837
|
+
const stats = fs.statSync(filePath);
|
|
838
|
+
const pathParts = filePath.split(path.sep);
|
|
839
|
+
const agentsIndex = pathParts.lastIndexOf("agents");
|
|
840
|
+
if (agentsIndex === -1 || agentsIndex + 1 >= pathParts.length) return false;
|
|
841
|
+
const agentName = pathParts[agentsIndex + 1];
|
|
842
|
+
const agentId = filePath.includes(".openclaw/") ? `openclaw-${agentName}` : agentName;
|
|
843
|
+
let loaded = 0;
|
|
844
|
+
for (const [sessionKey, sessionData] of Object.entries(data)) {
|
|
845
|
+
if (!sessionData || typeof sessionData !== "object") continue;
|
|
846
|
+
const session = sessionData;
|
|
847
|
+
const sessionId = session.sessionId;
|
|
848
|
+
if (!sessionId) continue;
|
|
849
|
+
const existingKey = Array.from(this.traces.keys()).find((k) => {
|
|
850
|
+
const t = this.traces.get(k);
|
|
851
|
+
return (t == null ? void 0 : t.id) === sessionId || (t == null ? void 0 : t.traceId) === sessionId;
|
|
852
|
+
});
|
|
853
|
+
if (existingKey) continue;
|
|
854
|
+
const updatedAt = session.updatedAt || stats.mtime.getTime();
|
|
855
|
+
const label = session.label || sessionKey.split(":").pop() || sessionId;
|
|
856
|
+
const chatType = session.chatType || (sessionKey.includes("cron") ? "cron" : "direct");
|
|
857
|
+
const trigger = sessionKey.includes("cron") ? "cron" : "message";
|
|
858
|
+
const rootId = `idx-${sessionId.slice(0, 12)}`;
|
|
859
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
860
|
+
nodes.set(rootId, {
|
|
861
|
+
id: rootId,
|
|
862
|
+
type: "agent",
|
|
863
|
+
name: label,
|
|
864
|
+
startTime: updatedAt,
|
|
865
|
+
endTime: updatedAt,
|
|
866
|
+
status: "completed",
|
|
867
|
+
parentId: void 0,
|
|
868
|
+
children: [],
|
|
869
|
+
metadata: {
|
|
870
|
+
sessionId,
|
|
871
|
+
sessionKey,
|
|
872
|
+
chatType,
|
|
873
|
+
source: "sessions-index"
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
const trace = {
|
|
877
|
+
id: sessionId,
|
|
878
|
+
nodes,
|
|
879
|
+
edges: [],
|
|
880
|
+
events: [],
|
|
881
|
+
startTime: updatedAt,
|
|
882
|
+
agentId,
|
|
883
|
+
trigger,
|
|
884
|
+
name: label,
|
|
885
|
+
traceId: sessionId,
|
|
886
|
+
spanId: sessionId,
|
|
887
|
+
filename: `${agentName}-${sessionId.slice(0, 8)}.index`,
|
|
888
|
+
lastModified: updatedAt,
|
|
889
|
+
sourceType: "session",
|
|
890
|
+
sourceDir: path.dirname(filePath),
|
|
891
|
+
sessionEvents: [],
|
|
892
|
+
tokenUsage: { input: 0, output: 0, total: 0, cost: 0 },
|
|
893
|
+
metadata: {
|
|
894
|
+
sessionKey,
|
|
895
|
+
chatType,
|
|
896
|
+
source: "sessions-index",
|
|
897
|
+
agentName
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
const key = `${this.traceKey(filePath)}-${sessionId.slice(0, 12)}`;
|
|
901
|
+
this.traces.set(key, trace);
|
|
902
|
+
loaded++;
|
|
903
|
+
}
|
|
904
|
+
return loaded > 0;
|
|
905
|
+
} catch {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
553
909
|
/** Parse a JSONL session log into a WatchedTrace (best-effort). */
|
|
554
910
|
loadSessionFile(filePath) {
|
|
555
911
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
@@ -565,6 +921,10 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
565
921
|
}
|
|
566
922
|
}
|
|
567
923
|
if (rawEvents.length === 0) return false;
|
|
924
|
+
const firstEvent = rawEvents[0];
|
|
925
|
+
if ((firstEvent == null ? void 0 : firstEvent.jobId) && (firstEvent == null ? void 0 : firstEvent.action) && !(firstEvent == null ? void 0 : firstEvent.type)) {
|
|
926
|
+
return this.loadCronRunFile(rawEvents, filePath);
|
|
927
|
+
}
|
|
568
928
|
const sessionEvent = rawEvents.find((e) => e.type === "session");
|
|
569
929
|
const sessionId = (sessionEvent == null ? void 0 : sessionEvent.id) || path.basename(filePath, ".jsonl");
|
|
570
930
|
const sessionTimestamp = (sessionEvent == null ? void 0 : sessionEvent.timestamp) || ((_a = rawEvents[0]) == null ? void 0 : _a.timestamp);
|
|
@@ -572,7 +932,25 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
572
932
|
if (!startTime) return false;
|
|
573
933
|
const parentDir = path.basename(path.dirname(filePath));
|
|
574
934
|
const grandParentDir = path.basename(path.dirname(path.dirname(filePath)));
|
|
575
|
-
const
|
|
935
|
+
const greatGrandParentDir = path.basename(path.dirname(path.dirname(path.dirname(filePath))));
|
|
936
|
+
let agentId;
|
|
937
|
+
if (parentDir === "sessions" && greatGrandParentDir === "agents") {
|
|
938
|
+
agentId = grandParentDir;
|
|
939
|
+
} else if (grandParentDir === "agents") {
|
|
940
|
+
agentId = parentDir;
|
|
941
|
+
} else if (parentDir === "runs" && grandParentDir === "cron") {
|
|
942
|
+
agentId = "openclaw-cron";
|
|
943
|
+
} else {
|
|
944
|
+
agentId = parentDir;
|
|
945
|
+
}
|
|
946
|
+
if (filePath.includes(".openclaw/") && !agentId.startsWith("openclaw-")) {
|
|
947
|
+
agentId = `openclaw-${agentId}`;
|
|
948
|
+
}
|
|
949
|
+
if (filePath.includes(".alfred/") || filePath.includes("alfred")) {
|
|
950
|
+
if (!agentId.startsWith("alfred-")) {
|
|
951
|
+
agentId = `alfred-${agentId}`;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
576
954
|
const modelEvent = rawEvents.find((e) => e.type === "model_change");
|
|
577
955
|
const provider = (modelEvent == null ? void 0 : modelEvent.provider) || "";
|
|
578
956
|
const modelId = (modelEvent == null ? void 0 : modelEvent.modelId) || "";
|
|
@@ -885,11 +1263,75 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
885
1263
|
return false;
|
|
886
1264
|
}
|
|
887
1265
|
}
|
|
1266
|
+
/** Parse cron run JSONL files (ts, jobId, action, status format). */
|
|
1267
|
+
loadCronRunFile(rawEvents, filePath) {
|
|
1268
|
+
var _a, _b, _c;
|
|
1269
|
+
try {
|
|
1270
|
+
const filename = path.basename(filePath);
|
|
1271
|
+
const jobId = ((_a = rawEvents[0]) == null ? void 0 : _a.jobId) || path.basename(filePath, ".jsonl");
|
|
1272
|
+
const fileStat = fs.statSync(filePath);
|
|
1273
|
+
const sessionEvents = [];
|
|
1274
|
+
let lastStatus = "completed";
|
|
1275
|
+
for (const evt of rawEvents) {
|
|
1276
|
+
const ts = evt.ts || Date.now();
|
|
1277
|
+
const action = evt.action || "unknown";
|
|
1278
|
+
const status = evt.status || "ok";
|
|
1279
|
+
if (status !== "ok") lastStatus = "failed";
|
|
1280
|
+
sessionEvents.push({
|
|
1281
|
+
type: action === "finished" ? "assistant" : "system",
|
|
1282
|
+
timestamp: ts,
|
|
1283
|
+
name: `${jobId}: ${action}`,
|
|
1284
|
+
content: evt.summary || evt.error || `${action} (${status})`,
|
|
1285
|
+
id: `cron-${ts}`
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
const firstTs = ((_b = rawEvents[0]) == null ? void 0 : _b.ts) || fileStat.mtime.getTime();
|
|
1289
|
+
const lastTs = ((_c = rawEvents[rawEvents.length - 1]) == null ? void 0 : _c.ts) || fileStat.mtime.getTime();
|
|
1290
|
+
const rootId = `cron-${jobId.slice(0, 12)}`;
|
|
1291
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1292
|
+
nodes.set(rootId, {
|
|
1293
|
+
id: rootId,
|
|
1294
|
+
type: "agent",
|
|
1295
|
+
name: jobId,
|
|
1296
|
+
startTime: firstTs,
|
|
1297
|
+
endTime: lastTs,
|
|
1298
|
+
status: lastStatus,
|
|
1299
|
+
parentId: void 0,
|
|
1300
|
+
children: [],
|
|
1301
|
+
metadata: { jobId, runs: rawEvents.length }
|
|
1302
|
+
});
|
|
1303
|
+
const trace = {
|
|
1304
|
+
id: jobId,
|
|
1305
|
+
nodes,
|
|
1306
|
+
edges: [],
|
|
1307
|
+
events: [],
|
|
1308
|
+
startTime: firstTs,
|
|
1309
|
+
agentId: "openclaw-cron",
|
|
1310
|
+
trigger: "cron",
|
|
1311
|
+
name: jobId,
|
|
1312
|
+
traceId: jobId,
|
|
1313
|
+
spanId: jobId,
|
|
1314
|
+
filename,
|
|
1315
|
+
lastModified: fileStat.mtime.getTime(),
|
|
1316
|
+
sourceType: "session",
|
|
1317
|
+
sourceDir: path.dirname(filePath),
|
|
1318
|
+
sessionEvents,
|
|
1319
|
+
tokenUsage: { input: 0, output: 0, total: 0, cost: 0 },
|
|
1320
|
+
metadata: { jobId, source: "cron-run" }
|
|
1321
|
+
};
|
|
1322
|
+
this.traces.set(this.traceKey(filePath), trace);
|
|
1323
|
+
return true;
|
|
1324
|
+
} catch {
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
888
1328
|
/** Unique key for a file across directories. */
|
|
889
1329
|
traceKey(filePath) {
|
|
890
1330
|
for (const dir of this.allWatchDirs) {
|
|
891
1331
|
if (filePath.startsWith(dir)) {
|
|
892
|
-
|
|
1332
|
+
const dirParts = dir.split(path.sep).filter(Boolean);
|
|
1333
|
+
const dirSuffix = dirParts.slice(-2).join("/");
|
|
1334
|
+
return path.relative(dir, filePath).replace(/\\/g, "/") + "@" + dirSuffix;
|
|
893
1335
|
}
|
|
894
1336
|
}
|
|
895
1337
|
return filePath;
|
|
@@ -897,16 +1339,35 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
897
1339
|
startWatching() {
|
|
898
1340
|
for (const dir of this.allWatchDirs) {
|
|
899
1341
|
if (!fs.existsSync(dir)) continue;
|
|
900
|
-
const
|
|
901
|
-
|
|
1342
|
+
const patterns = [
|
|
1343
|
+
path.join(dir, "**/*.json"),
|
|
1344
|
+
path.join(dir, "**/*.jsonl"),
|
|
1345
|
+
path.join(dir, "**/*.log"),
|
|
1346
|
+
path.join(dir, "**/*.trace")
|
|
1347
|
+
];
|
|
1348
|
+
const watcher = import_chokidar.default.watch(patterns, {
|
|
1349
|
+
ignored: [
|
|
1350
|
+
/^\./,
|
|
1351
|
+
// Ignore hidden files
|
|
1352
|
+
/node_modules/,
|
|
1353
|
+
// Ignore node_modules
|
|
1354
|
+
/\.git/,
|
|
1355
|
+
// Ignore git directories
|
|
1356
|
+
/\.vscode/,
|
|
1357
|
+
// Ignore vscode
|
|
1358
|
+
/\.idea/
|
|
1359
|
+
// Ignore idea
|
|
1360
|
+
],
|
|
902
1361
|
persistent: true,
|
|
903
1362
|
ignoreInitial: true,
|
|
904
|
-
|
|
905
|
-
|
|
1363
|
+
followSymlinks: false,
|
|
1364
|
+
depth: 10
|
|
1365
|
+
// Allow deep nesting for OpenClaw agents/*/sessions/
|
|
906
1366
|
});
|
|
907
1367
|
watcher.on("add", (filePath) => {
|
|
908
|
-
if (
|
|
909
|
-
|
|
1368
|
+
if (this.isSupportedFile(path.basename(filePath))) {
|
|
1369
|
+
const relativePath = path.relative(dir, filePath);
|
|
1370
|
+
console.log(`New file: ${relativePath} (in ${path.basename(dir)})`);
|
|
910
1371
|
if (this.loadFile(filePath)) {
|
|
911
1372
|
const key = this.traceKey(filePath);
|
|
912
1373
|
const trace = this.traces.get(key);
|
|
@@ -917,7 +1378,7 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
917
1378
|
}
|
|
918
1379
|
});
|
|
919
1380
|
watcher.on("change", (filePath) => {
|
|
920
|
-
if (
|
|
1381
|
+
if (this.isSupportedFile(path.basename(filePath))) {
|
|
921
1382
|
if (this.loadFile(filePath)) {
|
|
922
1383
|
const key = this.traceKey(filePath);
|
|
923
1384
|
const trace = this.traces.get(key);
|
|
@@ -928,7 +1389,7 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
928
1389
|
}
|
|
929
1390
|
});
|
|
930
1391
|
watcher.on("unlink", (filePath) => {
|
|
931
|
-
if (
|
|
1392
|
+
if (this.isSupportedFile(path.basename(filePath))) {
|
|
932
1393
|
const key = this.traceKey(filePath);
|
|
933
1394
|
this.traces.delete(key);
|
|
934
1395
|
this.emit("trace-removed", key);
|
|
@@ -939,7 +1400,7 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
939
1400
|
});
|
|
940
1401
|
this.watchers.push(watcher);
|
|
941
1402
|
}
|
|
942
|
-
console.log(`Watching ${this.allWatchDirs.length} directories for JSON/JSONL files`);
|
|
1403
|
+
console.log(`Watching ${this.allWatchDirs.length} directories recursively for JSON/JSONL/LOG/TRACE files`);
|
|
943
1404
|
}
|
|
944
1405
|
getAllTraces() {
|
|
945
1406
|
return Array.from(this.traces.values()).sort((a, b) => {
|
|
@@ -1001,6 +1462,18 @@ var TraceWatcher = class extends import_events.EventEmitter {
|
|
|
1001
1462
|
var import_meta = {};
|
|
1002
1463
|
var __filename = (0, import_url.fileURLToPath)(import_meta.url);
|
|
1003
1464
|
var __dirname = path2.dirname(__filename);
|
|
1465
|
+
function serializeTrace(trace) {
|
|
1466
|
+
if (!trace) return trace;
|
|
1467
|
+
const obj = { ...trace };
|
|
1468
|
+
if (obj.nodes instanceof Map) {
|
|
1469
|
+
const nodesObj = {};
|
|
1470
|
+
for (const [key, value] of obj.nodes) {
|
|
1471
|
+
nodesObj[key] = value;
|
|
1472
|
+
}
|
|
1473
|
+
obj.nodes = nodesObj;
|
|
1474
|
+
}
|
|
1475
|
+
return obj;
|
|
1476
|
+
}
|
|
1004
1477
|
var DashboardServer = class {
|
|
1005
1478
|
constructor(config) {
|
|
1006
1479
|
this.config = config;
|
|
@@ -1012,6 +1485,10 @@ var DashboardServer = class {
|
|
|
1012
1485
|
this.setupExpress();
|
|
1013
1486
|
this.setupWebSocket();
|
|
1014
1487
|
this.setupTraceWatcher();
|
|
1488
|
+
for (const trace of this.watcher.getAllTraces()) {
|
|
1489
|
+
this.stats.processTrace(trace);
|
|
1490
|
+
}
|
|
1491
|
+
console.log(`Processed ${this.watcher.getTraceCount()} existing traces for stats`);
|
|
1015
1492
|
}
|
|
1016
1493
|
app = (0, import_express.default)();
|
|
1017
1494
|
server = (0, import_http.createServer)(this.app);
|
|
@@ -1036,7 +1513,7 @@ var DashboardServer = class {
|
|
|
1036
1513
|
}
|
|
1037
1514
|
this.app.get("/api/traces", (req, res) => {
|
|
1038
1515
|
try {
|
|
1039
|
-
const traces = this.watcher.getAllTraces();
|
|
1516
|
+
const traces = this.watcher.getAllTraces().map(serializeTrace);
|
|
1040
1517
|
res.json(traces);
|
|
1041
1518
|
} catch (error) {
|
|
1042
1519
|
res.status(500).json({ error: "Failed to load traces" });
|
|
@@ -1048,7 +1525,7 @@ var DashboardServer = class {
|
|
|
1048
1525
|
if (!trace) {
|
|
1049
1526
|
return res.status(404).json({ error: "Trace not found" });
|
|
1050
1527
|
}
|
|
1051
|
-
res.json(trace);
|
|
1528
|
+
res.json(serializeTrace(trace));
|
|
1052
1529
|
} catch (error) {
|
|
1053
1530
|
res.status(500).json({ error: "Failed to load trace" });
|
|
1054
1531
|
}
|
|
@@ -1110,7 +1587,55 @@ var DashboardServer = class {
|
|
|
1110
1587
|
if (!processConfig) {
|
|
1111
1588
|
return res.json(null);
|
|
1112
1589
|
}
|
|
1113
|
-
const
|
|
1590
|
+
const alfredResult = (0, import_agentflow_core3.auditProcesses)(processConfig);
|
|
1591
|
+
const openclawConfig = {
|
|
1592
|
+
processName: "openclaw",
|
|
1593
|
+
pidFile: void 0,
|
|
1594
|
+
workersFile: void 0,
|
|
1595
|
+
systemdUnit: null
|
|
1596
|
+
};
|
|
1597
|
+
const openclawResult = (0, import_agentflow_core3.auditProcesses)(openclawConfig);
|
|
1598
|
+
const clawmetryConfig = {
|
|
1599
|
+
processName: "clawmetry",
|
|
1600
|
+
pidFile: void 0,
|
|
1601
|
+
workersFile: void 0,
|
|
1602
|
+
systemdUnit: null
|
|
1603
|
+
};
|
|
1604
|
+
const clawmetryResult = (0, import_agentflow_core3.auditProcesses)(clawmetryConfig);
|
|
1605
|
+
const allOsProcesses = [
|
|
1606
|
+
...alfredResult.osProcesses,
|
|
1607
|
+
...openclawResult.osProcesses,
|
|
1608
|
+
...clawmetryResult.osProcesses
|
|
1609
|
+
];
|
|
1610
|
+
const uniqueProcesses = allOsProcesses.filter(
|
|
1611
|
+
(proc, index, arr) => arr.findIndex((p) => p.pid === proc.pid) === index
|
|
1612
|
+
);
|
|
1613
|
+
const result = {
|
|
1614
|
+
...alfredResult,
|
|
1615
|
+
osProcesses: uniqueProcesses,
|
|
1616
|
+
// Recalculate orphans based on all processes
|
|
1617
|
+
orphans: uniqueProcesses.filter((p) => {
|
|
1618
|
+
var _a;
|
|
1619
|
+
const alfredKnownPids = /* @__PURE__ */ new Set();
|
|
1620
|
+
if (((_a = alfredResult.pidFile) == null ? void 0 : _a.pid) && !alfredResult.pidFile.stale) alfredKnownPids.add(alfredResult.pidFile.pid);
|
|
1621
|
+
if (alfredResult.workers) {
|
|
1622
|
+
if (alfredResult.workers.orchestratorPid) alfredKnownPids.add(alfredResult.workers.orchestratorPid);
|
|
1623
|
+
for (const w of alfredResult.workers.workers) {
|
|
1624
|
+
if (w.pid) alfredKnownPids.add(w.pid);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
const isOpenClawProcess = p.cmdline.includes("openclaw") || p.cmdline.includes("clawmetry");
|
|
1628
|
+
return !alfredKnownPids.has(p.pid) && !isOpenClawProcess && p.pid !== process.pid && p.pid !== process.ppid;
|
|
1629
|
+
})
|
|
1630
|
+
};
|
|
1631
|
+
const openclawProblems = [];
|
|
1632
|
+
if (openclawResult.osProcesses.length === 0) {
|
|
1633
|
+
openclawProblems.push("No OpenClaw gateway processes detected");
|
|
1634
|
+
}
|
|
1635
|
+
if (clawmetryResult.osProcesses.length === 0) {
|
|
1636
|
+
openclawProblems.push("No clawmetry processes detected");
|
|
1637
|
+
}
|
|
1638
|
+
result.problems = [...alfredResult.problems || [], ...openclawProblems];
|
|
1114
1639
|
this.processHealthCache = { result, ts: now };
|
|
1115
1640
|
res.json(result);
|
|
1116
1641
|
} catch (error) {
|
|
@@ -1133,7 +1658,7 @@ var DashboardServer = class {
|
|
|
1133
1658
|
JSON.stringify({
|
|
1134
1659
|
type: "init",
|
|
1135
1660
|
data: {
|
|
1136
|
-
traces: this.watcher.getAllTraces(),
|
|
1661
|
+
traces: this.watcher.getAllTraces().map(serializeTrace),
|
|
1137
1662
|
stats: this.stats.getGlobalStats()
|
|
1138
1663
|
}
|
|
1139
1664
|
})
|
|
@@ -1151,14 +1676,14 @@ var DashboardServer = class {
|
|
|
1151
1676
|
this.stats.processTrace(trace);
|
|
1152
1677
|
this.broadcast({
|
|
1153
1678
|
type: "trace-added",
|
|
1154
|
-
data: trace
|
|
1679
|
+
data: serializeTrace(trace)
|
|
1155
1680
|
});
|
|
1156
1681
|
});
|
|
1157
1682
|
this.watcher.on("trace-updated", (trace) => {
|
|
1158
1683
|
this.stats.processTrace(trace);
|
|
1159
1684
|
this.broadcast({
|
|
1160
1685
|
type: "trace-updated",
|
|
1161
|
-
data: trace
|
|
1686
|
+
data: serializeTrace(trace)
|
|
1162
1687
|
});
|
|
1163
1688
|
});
|
|
1164
1689
|
this.watcher.on("stats-updated", () => {
|
|
@@ -1201,9 +1726,12 @@ var DashboardServer = class {
|
|
|
1201
1726
|
return this.watcher.getAllTraces();
|
|
1202
1727
|
}
|
|
1203
1728
|
};
|
|
1729
|
+
if (import_meta.url === `file://${process.argv[1]}`) {
|
|
1730
|
+
startDashboard().catch(console.error);
|
|
1731
|
+
}
|
|
1204
1732
|
|
|
1205
1733
|
// src/cli.ts
|
|
1206
|
-
var VERSION = "0.
|
|
1734
|
+
var VERSION = "0.4.0";
|
|
1207
1735
|
function getLanAddress() {
|
|
1208
1736
|
const interfaces = os.networkInterfaces();
|
|
1209
1737
|
for (const name of Object.keys(interfaces)) {
|