agentflow-dashboard 0.8.2 → 0.8.4
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/dist/{chunk-EG254FLY.js → chunk-YLQ5MVCW.js} +204 -104
- package/dist/cli.cjs +193 -86
- package/dist/cli.js +1 -1
- package/dist/client/assets/index-BQBa4cES.css +1 -0
- package/dist/client/assets/{index-DCSWGDzI.js → index-DA9m90ZC.js} +7 -7
- package/dist/client/index.html +2 -2
- package/dist/index.cjs +193 -86
- package/dist/index.js +1 -1
- package/dist/server.cjs +193 -86
- package/dist/server.js +1 -1
- package/package.json +21 -7
- package/dist/client/assets/index-DHcSpTgM.css +0 -1
package/dist/server.cjs
CHANGED
|
@@ -37,6 +37,11 @@ var fs3 = __toESM(require("fs"), 1);
|
|
|
37
37
|
var import_node_http = require("http");
|
|
38
38
|
var path3 = __toESM(require("path"), 1);
|
|
39
39
|
var import_node_url2 = require("url");
|
|
40
|
+
var import_agentflow_core3 = require("agentflow-core");
|
|
41
|
+
var import_chokidar2 = __toESM(require("chokidar"), 1);
|
|
42
|
+
var import_express = __toESM(require("express"), 1);
|
|
43
|
+
var import_express_rate_limit = __toESM(require("express-rate-limit"), 1);
|
|
44
|
+
var import_ws = require("ws");
|
|
40
45
|
|
|
41
46
|
// src/config.ts
|
|
42
47
|
var import_node_fs = require("fs");
|
|
@@ -109,12 +114,6 @@ function getProcessPreference(config) {
|
|
|
109
114
|
return config.processPreference ?? null;
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
// src/server.ts
|
|
113
|
-
var import_agentflow_core3 = require("agentflow-core");
|
|
114
|
-
var import_chokidar2 = __toESM(require("chokidar"), 1);
|
|
115
|
-
var import_express = __toESM(require("express"), 1);
|
|
116
|
-
var import_ws = require("ws");
|
|
117
|
-
|
|
118
117
|
// src/adapters/agentflow.ts
|
|
119
118
|
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
120
119
|
"workers.json",
|
|
@@ -126,7 +125,14 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
|
126
125
|
"models.json",
|
|
127
126
|
"config.json"
|
|
128
127
|
]);
|
|
129
|
-
var SKIP_SUFFIXES = [
|
|
128
|
+
var SKIP_SUFFIXES = [
|
|
129
|
+
"-state.json",
|
|
130
|
+
"-config.json",
|
|
131
|
+
"-watch-state.json",
|
|
132
|
+
".tmp",
|
|
133
|
+
".bak",
|
|
134
|
+
".backup"
|
|
135
|
+
];
|
|
130
136
|
var AgentFlowAdapter = class {
|
|
131
137
|
name = "agentflow";
|
|
132
138
|
detect(_dirPath) {
|
|
@@ -403,8 +409,14 @@ registerAdapter(new AgentFlowAdapter());
|
|
|
403
409
|
var PURPOSE_KEYWORDS = [
|
|
404
410
|
{ keywords: ["email", "mail", "inbox", "smtp"], group: "Email Processors" },
|
|
405
411
|
{ keywords: ["monitor", "watch", "alert", "surveillance"], group: "Monitors" },
|
|
406
|
-
{
|
|
407
|
-
|
|
412
|
+
{
|
|
413
|
+
keywords: ["digest", "newsletter", "summary", "report", "briefing"],
|
|
414
|
+
group: "Digests & Reports"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
keywords: ["curator", "janitor", "distiller", "surveyor", "worker", "indexer"],
|
|
418
|
+
group: "Workers"
|
|
419
|
+
},
|
|
408
420
|
{ keywords: ["cron", "schedule", "timer", "periodic"], group: "Scheduled Jobs" },
|
|
409
421
|
{ keywords: ["search", "scrape", "crawl", "fetch"], group: "Data Collection" },
|
|
410
422
|
{ keywords: ["embed", "vector", "index"], group: "Embeddings" }
|
|
@@ -436,6 +448,7 @@ function capitalize(s) {
|
|
|
436
448
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
437
449
|
}
|
|
438
450
|
function deduplicateAgents(agents) {
|
|
451
|
+
var _a, _b, _c, _d;
|
|
439
452
|
const tagged = agents.map((a) => ({
|
|
440
453
|
...a,
|
|
441
454
|
...extractSource(a.agentId)
|
|
@@ -452,14 +465,14 @@ function deduplicateAgents(agents) {
|
|
|
452
465
|
const mergedIds = /* @__PURE__ */ new Set();
|
|
453
466
|
const mergedAgents = [];
|
|
454
467
|
for (const [_key, group] of suffixGroups) {
|
|
455
|
-
const suffix = extractSuffix(group[0].localId);
|
|
468
|
+
const suffix = extractSuffix((_a = group[0]) == null ? void 0 : _a.localId);
|
|
456
469
|
if (group.length < 2) continue;
|
|
457
470
|
const prefixes = new Set(group.map((a) => a.localId.split("-")[0]));
|
|
458
471
|
if (prefixes.size < 2) continue;
|
|
459
472
|
const longPrefixes = [...prefixes].filter((p) => p !== suffix && p.length > 2);
|
|
460
473
|
if (longPrefixes.length >= 2) continue;
|
|
461
474
|
const merged = {
|
|
462
|
-
agentId: group[0].source === "agentflow" ? suffix : `${group[0].source}:${suffix}`,
|
|
475
|
+
agentId: ((_b = group[0]) == null ? void 0 : _b.source) === "agentflow" ? suffix : `${(_c = group[0]) == null ? void 0 : _c.source}:${suffix}`,
|
|
463
476
|
displayName: suffix,
|
|
464
477
|
totalExecutions: group.reduce((s, a) => s + a.totalExecutions, 0),
|
|
465
478
|
successfulExecutions: group.reduce((s, a) => s + a.successfulExecutions, 0),
|
|
@@ -470,7 +483,7 @@ function deduplicateAgents(agents) {
|
|
|
470
483
|
triggers: {},
|
|
471
484
|
recentActivity: group.flatMap((a) => a.recentActivity).sort((a, b) => b.timestamp - a.timestamp).slice(0, 50),
|
|
472
485
|
sources: group.map((a) => a.agentId),
|
|
473
|
-
adapterSource: group[0].source
|
|
486
|
+
adapterSource: (_d = group[0]) == null ? void 0 : _d.source
|
|
474
487
|
};
|
|
475
488
|
merged.successRate = merged.totalExecutions > 0 ? merged.successfulExecutions / merged.totalExecutions * 100 : 0;
|
|
476
489
|
const totalExecTime = group.reduce((s, a) => s + a.avgExecutionTime * a.totalExecutions, 0);
|
|
@@ -886,7 +899,9 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
|
|
|
886
899
|
...getSkipFiles(this.userConfig)
|
|
887
900
|
]);
|
|
888
901
|
this.userSkipDirs = new Set(getSkipDirectories(this.userConfig));
|
|
889
|
-
this.allWatchDirs = [
|
|
902
|
+
this.allWatchDirs = [
|
|
903
|
+
...new Set([this.tracesDir, ...this.dataDirs].map((d) => path.resolve(d)))
|
|
904
|
+
];
|
|
890
905
|
this.ensureTracesDir();
|
|
891
906
|
this.loadExistingFiles();
|
|
892
907
|
this.archiveOldTraces();
|
|
@@ -896,7 +911,7 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
|
|
|
896
911
|
/** Move trace files older than maxAgeMs into archive/YYYY-MM/ subdirectories. */
|
|
897
912
|
archiveOldTraces() {
|
|
898
913
|
const cutoff = Date.now() - this.maxAgeMs;
|
|
899
|
-
|
|
914
|
+
const _archived = 0;
|
|
900
915
|
for (const dir of this.allWatchDirs) {
|
|
901
916
|
if (!fs.existsSync(dir)) continue;
|
|
902
917
|
try {
|
|
@@ -913,7 +928,8 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
|
|
|
913
928
|
try {
|
|
914
929
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
915
930
|
for (const entry of entries) {
|
|
916
|
-
if (entry.name.startsWith(".") || entry.name === "archive" || this.userSkipDirs.has(entry.name))
|
|
931
|
+
if (entry.name.startsWith(".") || entry.name === "archive" || this.userSkipDirs.has(entry.name))
|
|
932
|
+
continue;
|
|
917
933
|
const fullPath = path.join(dir, entry.name);
|
|
918
934
|
if (entry.isDirectory()) {
|
|
919
935
|
archived += this.archiveDirectory(fullPath, cutoff, depth + 1);
|
|
@@ -2160,7 +2176,9 @@ var path2 = __toESM(require("path"), 1);
|
|
|
2160
2176
|
var import_node_url = require("url");
|
|
2161
2177
|
var import_meta = {};
|
|
2162
2178
|
var __cliDirname = path2.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
2163
|
-
var VERSION = JSON.parse(
|
|
2179
|
+
var VERSION = JSON.parse(
|
|
2180
|
+
fs2.readFileSync(path2.resolve(__cliDirname, "../package.json"), "utf-8")
|
|
2181
|
+
).version;
|
|
2164
2182
|
function getLanAddress() {
|
|
2165
2183
|
const interfaces = os.networkInterfaces();
|
|
2166
2184
|
for (const name of Object.keys(interfaces)) {
|
|
@@ -2418,6 +2436,17 @@ var DashboardServer = class {
|
|
|
2418
2436
|
userConfig;
|
|
2419
2437
|
configPath;
|
|
2420
2438
|
setupExpress() {
|
|
2439
|
+
this.app.use(
|
|
2440
|
+
"/api/",
|
|
2441
|
+
(0, import_express_rate_limit.default)({
|
|
2442
|
+
windowMs: 60 * 1e3,
|
|
2443
|
+
// 1 minute
|
|
2444
|
+
max: 300,
|
|
2445
|
+
// 300 requests per minute per IP
|
|
2446
|
+
standardHeaders: true,
|
|
2447
|
+
legacyHeaders: false
|
|
2448
|
+
})
|
|
2449
|
+
);
|
|
2421
2450
|
if (this.config.enableCors) {
|
|
2422
2451
|
this.app.use((_req, res, next) => {
|
|
2423
2452
|
res.header("Access-Control-Allow-Origin", "*");
|
|
@@ -2444,10 +2473,6 @@ var DashboardServer = class {
|
|
|
2444
2473
|
if (fs3.existsSync(clientDir)) {
|
|
2445
2474
|
this.app.use(import_express.default.static(clientDir));
|
|
2446
2475
|
}
|
|
2447
|
-
const pkgVersion = JSON.parse(fs3.readFileSync(path3.resolve(__dirname, "../package.json"), "utf-8")).version;
|
|
2448
|
-
this.app.get("/api/version", (_req, res) => {
|
|
2449
|
-
res.json({ version: pkgVersion });
|
|
2450
|
-
});
|
|
2451
2476
|
this.app.get("/api/traces", (req, res) => {
|
|
2452
2477
|
try {
|
|
2453
2478
|
const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 50, 1), 200);
|
|
@@ -2664,6 +2689,7 @@ var DashboardServer = class {
|
|
|
2664
2689
|
}
|
|
2665
2690
|
});
|
|
2666
2691
|
this.app.get("/api/process-model/:agentId", (req, res) => {
|
|
2692
|
+
var _a, _b;
|
|
2667
2693
|
try {
|
|
2668
2694
|
const agentId = req.params.agentId;
|
|
2669
2695
|
const allTraces = this.watcher.getTracesByAgent(agentId);
|
|
@@ -2681,8 +2707,8 @@ var DashboardServer = class {
|
|
|
2681
2707
|
const nodeArr = Object.values(nodes);
|
|
2682
2708
|
const sorted = nodeArr.filter((n) => n.name && typeof n.startTime === "number" && n.startTime > 0).sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
|
|
2683
2709
|
for (let i = 0; i < sorted.length - 1; i++) {
|
|
2684
|
-
const from = sorted[i].name;
|
|
2685
|
-
const to = sorted[i + 1].name;
|
|
2710
|
+
const from = (_a = sorted[i]) == null ? void 0 : _a.name;
|
|
2711
|
+
const to = (_b = sorted[i + 1]) == null ? void 0 : _b.name;
|
|
2686
2712
|
const key = `${from}|||${to}`;
|
|
2687
2713
|
transMap.set(key, (transMap.get(key) ?? 0) + 1);
|
|
2688
2714
|
}
|
|
@@ -2781,7 +2807,11 @@ var DashboardServer = class {
|
|
|
2781
2807
|
try {
|
|
2782
2808
|
const reportPath = path3.join(somaVault, "..", "soma-report.json");
|
|
2783
2809
|
if (!fs3.existsSync(reportPath)) {
|
|
2784
|
-
return res.json({
|
|
2810
|
+
return res.json({
|
|
2811
|
+
available: false,
|
|
2812
|
+
teaser: false,
|
|
2813
|
+
message: "No report file yet. Run soma watch."
|
|
2814
|
+
});
|
|
2785
2815
|
}
|
|
2786
2816
|
const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
|
|
2787
2817
|
res.json(report);
|
|
@@ -2805,7 +2835,9 @@ var DashboardServer = class {
|
|
|
2805
2835
|
available: true,
|
|
2806
2836
|
layers: report.layers ?? { archive: 0, working: 0, emerging: 0, canon: 0 },
|
|
2807
2837
|
governance: report.governance ?? { pending: 0, promoted: 0, rejected: 0 },
|
|
2808
|
-
insights: (report.insights ?? []).filter(
|
|
2838
|
+
insights: (report.insights ?? []).filter(
|
|
2839
|
+
(i) => i.layer === "emerging" && i.proposal_status === "pending"
|
|
2840
|
+
),
|
|
2809
2841
|
canon: (report.insights ?? []).filter((i) => i.layer === "canon"),
|
|
2810
2842
|
generatedAt: report.generatedAt
|
|
2811
2843
|
});
|
|
@@ -2814,21 +2846,23 @@ var DashboardServer = class {
|
|
|
2814
2846
|
res.status(500).json({ available: false, message: "Failed to read governance data" });
|
|
2815
2847
|
}
|
|
2816
2848
|
});
|
|
2817
|
-
const
|
|
2818
|
-
const sanitizeReason = (s) => s.replace(/["`$\\]/g, "").slice(0, 500);
|
|
2849
|
+
const isValidId = (s) => /^[a-zA-Z0-9_\-.:]+$/.test(s);
|
|
2819
2850
|
this.app.post("/api/soma/governance/promote", (req, res) => {
|
|
2820
2851
|
var _a;
|
|
2821
2852
|
const somaVault = this.config.somaVault;
|
|
2822
2853
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2823
2854
|
const { entryId } = req.body ?? {};
|
|
2824
|
-
if (!entryId
|
|
2855
|
+
if (!entryId || !isValidId(String(entryId)))
|
|
2856
|
+
return res.status(400).json({ error: "Invalid entryId" });
|
|
2825
2857
|
try {
|
|
2826
|
-
const
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2858
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
2859
|
+
"npx",
|
|
2860
|
+
["soma", "governance", "promote", String(entryId), "--vault", somaVault],
|
|
2861
|
+
{
|
|
2862
|
+
encoding: "utf-8",
|
|
2863
|
+
timeout: 1e4
|
|
2864
|
+
}
|
|
2865
|
+
);
|
|
2832
2866
|
res.json({ success: true, message: result.trim() });
|
|
2833
2867
|
} catch (error) {
|
|
2834
2868
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2839,15 +2873,27 @@ var DashboardServer = class {
|
|
|
2839
2873
|
const somaVault = this.config.somaVault;
|
|
2840
2874
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2841
2875
|
const { entryId, reason } = req.body ?? {};
|
|
2842
|
-
if (!entryId || !
|
|
2876
|
+
if (!entryId || !isValidId(String(entryId)))
|
|
2877
|
+
return res.status(400).json({ error: "Invalid entryId" });
|
|
2878
|
+
if (!reason || typeof reason !== "string")
|
|
2879
|
+
return res.status(400).json({ error: "reason required" });
|
|
2843
2880
|
try {
|
|
2844
|
-
const
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2881
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
2882
|
+
"npx",
|
|
2883
|
+
[
|
|
2884
|
+
"soma",
|
|
2885
|
+
"governance",
|
|
2886
|
+
"reject",
|
|
2887
|
+
String(entryId),
|
|
2888
|
+
String(reason).slice(0, 500),
|
|
2889
|
+
"--vault",
|
|
2890
|
+
somaVault
|
|
2891
|
+
],
|
|
2892
|
+
{
|
|
2893
|
+
encoding: "utf-8",
|
|
2894
|
+
timeout: 1e4
|
|
2895
|
+
}
|
|
2896
|
+
);
|
|
2851
2897
|
res.json({ success: true, message: result.trim() });
|
|
2852
2898
|
} catch (error) {
|
|
2853
2899
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2857,13 +2903,16 @@ var DashboardServer = class {
|
|
|
2857
2903
|
var _a;
|
|
2858
2904
|
const somaVault = this.config.somaVault;
|
|
2859
2905
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2906
|
+
if (!isValidId(String(req.params.id))) return res.status(400).json({ error: "Invalid id" });
|
|
2860
2907
|
try {
|
|
2861
|
-
const
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2908
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
2909
|
+
"npx",
|
|
2910
|
+
["soma", "governance", "show", String(req.params.id), "--vault", somaVault],
|
|
2911
|
+
{
|
|
2912
|
+
encoding: "utf-8",
|
|
2913
|
+
timeout: 1e4
|
|
2914
|
+
}
|
|
2915
|
+
);
|
|
2867
2916
|
res.json({ available: true, output: result.trim() });
|
|
2868
2917
|
} catch (error) {
|
|
2869
2918
|
res.status(404).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2886,16 +2935,24 @@ var DashboardServer = class {
|
|
|
2886
2935
|
const somaVault = this.config.somaVault;
|
|
2887
2936
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2888
2937
|
const { name, enforcement, scope, conditions } = req.body ?? {};
|
|
2889
|
-
if (!name
|
|
2938
|
+
if (!name || !isValidId(String(name)))
|
|
2939
|
+
return res.status(400).json({ error: "Invalid policy name" });
|
|
2940
|
+
const enf = String(enforcement || "warn");
|
|
2941
|
+
if (!isValidId(enf)) return res.status(400).json({ error: "Invalid enforcement value" });
|
|
2890
2942
|
try {
|
|
2891
|
-
const
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2943
|
+
const args = [
|
|
2944
|
+
"soma",
|
|
2945
|
+
"policy",
|
|
2946
|
+
"create",
|
|
2947
|
+
String(name),
|
|
2948
|
+
"--enforcement",
|
|
2949
|
+
enf,
|
|
2950
|
+
"--vault",
|
|
2951
|
+
somaVault
|
|
2952
|
+
];
|
|
2953
|
+
if (scope) args.push("--scope", String(scope).slice(0, 500));
|
|
2954
|
+
if (conditions) args.push("--conditions", String(conditions).slice(0, 500));
|
|
2955
|
+
const result = (0, import_node_child_process.execFileSync)("npx", args, { encoding: "utf-8", timeout: 1e4 });
|
|
2899
2956
|
res.json({ success: true, message: result.trim() });
|
|
2900
2957
|
} catch (error) {
|
|
2901
2958
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2905,11 +2962,16 @@ var DashboardServer = class {
|
|
|
2905
2962
|
var _a;
|
|
2906
2963
|
const somaVault = this.config.somaVault;
|
|
2907
2964
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2965
|
+
if (!isValidId(String(req.params.name)))
|
|
2966
|
+
return res.status(400).json({ error: "Invalid policy name" });
|
|
2908
2967
|
try {
|
|
2909
|
-
const
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
{
|
|
2968
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
2969
|
+
"npx",
|
|
2970
|
+
["soma", "policy", "delete", String(req.params.name), "--vault", somaVault],
|
|
2971
|
+
{
|
|
2972
|
+
encoding: "utf-8",
|
|
2973
|
+
timeout: 1e4
|
|
2974
|
+
}
|
|
2913
2975
|
);
|
|
2914
2976
|
res.json({ success: true, message: result.trim() });
|
|
2915
2977
|
} catch (error) {
|
|
@@ -2927,16 +2989,28 @@ var DashboardServer = class {
|
|
|
2927
2989
|
...(report.agents ?? []).map((a) => ({ ...a, type: "agent", id: a.name })),
|
|
2928
2990
|
...(report.insights ?? []).map((i, idx) => {
|
|
2929
2991
|
var _a;
|
|
2930
|
-
return {
|
|
2992
|
+
return {
|
|
2993
|
+
...i,
|
|
2994
|
+
type: i.type || "insight",
|
|
2995
|
+
id: ((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || `insight-${idx}`
|
|
2996
|
+
};
|
|
2931
2997
|
}),
|
|
2932
2998
|
...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
|
|
2933
2999
|
];
|
|
2934
|
-
const {
|
|
3000
|
+
const {
|
|
3001
|
+
type,
|
|
3002
|
+
layer,
|
|
3003
|
+
q,
|
|
3004
|
+
limit: limitStr,
|
|
3005
|
+
offset: offsetStr
|
|
3006
|
+
} = req.query;
|
|
2935
3007
|
if (type) entities = entities.filter((e) => e.type === type);
|
|
2936
3008
|
if (layer) entities = entities.filter((e) => e.layer === layer);
|
|
2937
3009
|
if (q) {
|
|
2938
3010
|
const lq = q.toLowerCase();
|
|
2939
|
-
entities = entities.filter(
|
|
3011
|
+
entities = entities.filter(
|
|
3012
|
+
(e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq)
|
|
3013
|
+
);
|
|
2940
3014
|
}
|
|
2941
3015
|
const total = entities.length;
|
|
2942
3016
|
const offset = parseInt(offsetStr || "0", 10);
|
|
@@ -3027,9 +3101,7 @@ var DashboardServer = class {
|
|
|
3027
3101
|
const orphans = uniqueProcesses.filter(
|
|
3028
3102
|
(p) => !allKnownPids.has(p.pid) && p.pid !== process.pid && p.pid !== process.ppid
|
|
3029
3103
|
);
|
|
3030
|
-
const problems = services.flatMap(
|
|
3031
|
-
(s) => s.audit.problems.map((p) => `[${s.name}] ${p}`)
|
|
3032
|
-
);
|
|
3104
|
+
const problems = services.flatMap((s) => s.audit.problems.map((p) => `[${s.name}] ${p}`));
|
|
3033
3105
|
const result = {
|
|
3034
3106
|
// Backward-compatible fields from primary service
|
|
3035
3107
|
pidFile: (primary == null ? void 0 : primary.audit.pidFile) ?? null,
|
|
@@ -3084,19 +3156,24 @@ var DashboardServer = class {
|
|
|
3084
3156
|
}
|
|
3085
3157
|
} catch {
|
|
3086
3158
|
}
|
|
3087
|
-
const watched = [
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3159
|
+
const watched = [
|
|
3160
|
+
...new Set(
|
|
3161
|
+
[this.config.tracesDir, ...this.config.dataDirs || [], ...extraDirs].map(
|
|
3162
|
+
(w) => path3.resolve(w)
|
|
3163
|
+
)
|
|
3164
|
+
)
|
|
3165
|
+
];
|
|
3092
3166
|
const discovered = [];
|
|
3093
3167
|
const svcNames = getSystemdServices(this.userConfig);
|
|
3094
3168
|
if (svcNames.length > 0) {
|
|
3095
3169
|
try {
|
|
3096
|
-
const
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
{
|
|
3170
|
+
const raw = (0, import_node_child_process.execFileSync)(
|
|
3171
|
+
"systemctl",
|
|
3172
|
+
["--user", "show", "--property=ExecStart", "--no-pager", ...svcNames],
|
|
3173
|
+
{
|
|
3174
|
+
encoding: "utf8",
|
|
3175
|
+
timeout: 5e3
|
|
3176
|
+
}
|
|
3100
3177
|
);
|
|
3101
3178
|
for (const line of raw.split("\n")) {
|
|
3102
3179
|
const match = line.match(/path=([^\s;]+)/);
|
|
@@ -3128,10 +3205,19 @@ var DashboardServer = class {
|
|
|
3128
3205
|
this.app.post("/api/directories", import_express.default.json(), (req, res) => {
|
|
3129
3206
|
try {
|
|
3130
3207
|
const { add, remove } = req.body;
|
|
3131
|
-
if (add
|
|
3132
|
-
|
|
3208
|
+
if (add) {
|
|
3209
|
+
const resolved = path3.resolve(add);
|
|
3210
|
+
if (resolved !== add || add.includes("..")) {
|
|
3211
|
+
return res.status(400).json({ error: "Invalid directory path" });
|
|
3212
|
+
}
|
|
3213
|
+
if (!fs3.existsSync(resolved)) {
|
|
3214
|
+
return res.status(400).json({ error: `Directory does not exist: ${add}` });
|
|
3215
|
+
}
|
|
3133
3216
|
}
|
|
3134
|
-
const configPath = path3.join(
|
|
3217
|
+
const configPath = path3.join(
|
|
3218
|
+
process.env.HOME ?? "/home/trader",
|
|
3219
|
+
".agentflow/dashboard-config.json"
|
|
3220
|
+
);
|
|
3135
3221
|
let config = {};
|
|
3136
3222
|
try {
|
|
3137
3223
|
if (fs3.existsSync(configPath)) {
|
|
@@ -3341,13 +3427,31 @@ var DashboardServer = class {
|
|
|
3341
3427
|
isVirtual: false
|
|
3342
3428
|
});
|
|
3343
3429
|
}
|
|
3344
|
-
const
|
|
3345
|
-
const
|
|
3346
|
-
const
|
|
3347
|
-
for (const
|
|
3348
|
-
}
|
|
3349
|
-
nodes.push({
|
|
3350
|
-
|
|
3430
|
+
const _rootSteps = new Set(model.steps);
|
|
3431
|
+
const _childSteps = new Set(model.transitions.map((t) => t.to));
|
|
3432
|
+
const _leafSteps = new Set(model.steps);
|
|
3433
|
+
for (const _t of model.transitions) {
|
|
3434
|
+
}
|
|
3435
|
+
nodes.push({
|
|
3436
|
+
id: "[START]",
|
|
3437
|
+
label: "[START]",
|
|
3438
|
+
count: model.totalGraphs,
|
|
3439
|
+
frequency: 1,
|
|
3440
|
+
avgDuration: 0,
|
|
3441
|
+
failRate: 0,
|
|
3442
|
+
p95Duration: 0,
|
|
3443
|
+
isVirtual: true
|
|
3444
|
+
});
|
|
3445
|
+
nodes.push({
|
|
3446
|
+
id: "[END]",
|
|
3447
|
+
label: "[END]",
|
|
3448
|
+
count: model.totalGraphs,
|
|
3449
|
+
frequency: 1,
|
|
3450
|
+
avgDuration: 0,
|
|
3451
|
+
failRate: 0,
|
|
3452
|
+
p95Duration: 0,
|
|
3453
|
+
isVirtual: true
|
|
3454
|
+
});
|
|
3351
3455
|
const edges = model.transitions.map((t) => ({
|
|
3352
3456
|
source: t.from,
|
|
3353
3457
|
target: t.to,
|
|
@@ -3367,7 +3471,10 @@ var DashboardServer = class {
|
|
|
3367
3471
|
}
|
|
3368
3472
|
}
|
|
3369
3473
|
const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
|
|
3370
|
-
const maxNodeCount = Math.max(
|
|
3474
|
+
const maxNodeCount = Math.max(
|
|
3475
|
+
...nodes.filter((n) => !n.isVirtual).map((n) => n.count),
|
|
3476
|
+
1
|
|
3477
|
+
);
|
|
3371
3478
|
return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
|
|
3372
3479
|
}
|
|
3373
3480
|
/**
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentflow-dashboard",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
4
4
|
"description": "Real-time monitoring dashboard for AgentFlow - Visualize agent execution graphs and performance",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -34,12 +34,14 @@
|
|
|
34
34
|
"test:e2e": "playwright test",
|
|
35
35
|
"test:e2e:server": "tsx tests/e2e/test-server.ts",
|
|
36
36
|
"test:all": "npm run test:unit && npm run test:integration && npm run test:performance && npm run test:e2e",
|
|
37
|
-
"test:ci": "npm run test:unit && npm run test:integration"
|
|
37
|
+
"test:ci": "npm run test:unit && npm run test:integration",
|
|
38
|
+
"prepublishOnly": "npm run build"
|
|
38
39
|
},
|
|
39
40
|
"dependencies": {
|
|
40
41
|
"agentflow-core": "^0.8.0",
|
|
41
42
|
"chokidar": "^3.5.3",
|
|
42
43
|
"express": "^4.18.2",
|
|
44
|
+
"express-rate-limit": "^8.3.1",
|
|
43
45
|
"react": "^19.1.0",
|
|
44
46
|
"react-dom": "^19.1.0",
|
|
45
47
|
"ws": "^8.16.0"
|
|
@@ -50,14 +52,14 @@
|
|
|
50
52
|
"@types/react": "^19.1.0",
|
|
51
53
|
"@types/react-dom": "^19.1.0",
|
|
52
54
|
"@types/ws": "^8.5.10",
|
|
53
|
-
"@vitejs/plugin-react": "^
|
|
54
|
-
"@vitest/coverage-v8": "^
|
|
55
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
56
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
55
57
|
"get-port": "^7.0.0",
|
|
56
58
|
"supertest": "^7.0.0",
|
|
57
59
|
"tsup": "^8.4.0",
|
|
58
60
|
"tsx": "^4.19.0",
|
|
59
|
-
"vite": "^
|
|
60
|
-
"vitest": "^
|
|
61
|
+
"vite": "^8.0.1",
|
|
62
|
+
"vitest": "^4.1.0",
|
|
61
63
|
"ws": "^8.16.0"
|
|
62
64
|
},
|
|
63
65
|
"keywords": [
|
|
@@ -70,5 +72,17 @@
|
|
|
70
72
|
"observability",
|
|
71
73
|
"real-time"
|
|
72
74
|
],
|
|
73
|
-
"license": "
|
|
75
|
+
"license": "Apache-2.0 WITH Commons-Clause-1.0",
|
|
76
|
+
"repository": {
|
|
77
|
+
"type": "git",
|
|
78
|
+
"url": "https://github.com/ClemenceChee/AgentFlow.git"
|
|
79
|
+
},
|
|
80
|
+
"homepage": "https://github.com/ClemenceChee/AgentFlow#readme",
|
|
81
|
+
"bugs": {
|
|
82
|
+
"url": "https://github.com/ClemenceChee/AgentFlow/issues"
|
|
83
|
+
},
|
|
84
|
+
"author": "Clemence Chee",
|
|
85
|
+
"engines": {
|
|
86
|
+
"node": ">=20"
|
|
87
|
+
}
|
|
74
88
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--bg:#0d1117;--bg2:#161b22;--bg3:#1c2129;--bgh:#21262d;--bd:#30363d;--bdm:#21262d;--t1:#e6edf3;--t2:#8b949e;--t3:#6e7681;--ok:#3fb950;--warn:#d29922;--fail:#f85149;--info:#58a6ff;--f:sans-serif;--fm:"SF Mono",Menlo,monospace;--xs:.85rem;--sm:.9rem;--base:1rem;--lg:1.15rem;--xl:1.35rem;--s1:4px;--s2:8px;--s3:12px;--s4:16px;--s5:24px;--r:5px}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--f);font-size:var(--base);color:var(--t1);background:var(--bg);line-height:1.4;-webkit-font-smoothing:antialiased}.dashboard{display:flex;flex-direction:column;height:100vh;overflow:hidden}.workspace{display:flex;flex:1;min-height:0;overflow:hidden;border-top:1px solid var(--bd)}.workspace__main{flex:1;overflow-y:auto;overflow-x:hidden}.workspace__empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--t3);font-size:var(--sm)}.health-banner{display:flex;align-items:center;gap:var(--s4);padding:0 var(--s4);height:42px;background:var(--bg2);border-bottom:1px solid var(--bd);flex-shrink:0}.health-banner__title{font-family:var(--fm);font-size:var(--base);font-weight:700;margin-right:var(--s1)}.hb-version{font-size:11px;color:var(--t3);font-family:var(--fm);margin-right:var(--s3)}.hb-live{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;letter-spacing:.06em;padding:1px 6px;border-radius:3px;margin-right:var(--s3)}.hb-live--on{color:var(--ok);background:#3fb9501a}.hb-live--off{color:var(--fail);background:#f851491a}.hb-live__dot{width:6px;height:6px;border-radius:50%;background:currentColor}.hb-live__dot--pulse{animation:livePulse 2s ease-in-out infinite}@keyframes livePulse{0%,to{opacity:1}50%{opacity:.3}}.health-banner__stats{display:flex;gap:var(--s4);align-items:center}.stat-cell{display:flex;flex-direction:column;align-items:center}.stat-cell__value{font-family:var(--fm);font-size:var(--sm);font-weight:700;line-height:1}.stat-cell__label{font-size:11px;color:var(--t3);text-transform:uppercase;letter-spacing:.06em}.stat-cell__sparkline{display:flex;gap:1px;margin-top:2px}.spark{width:2px;height:6px;border-radius:1px}.spark--ok{background:var(--ok);opacity:.4}.spark--fail{background:var(--fail)}.dot{display:inline-block;width:7px;height:7px;border-radius:50%;flex-shrink:0}.dot--ok{background:var(--ok)}.dot--fail{background:var(--fail)}.dot--warn{background:var(--warn)}.top-section{flex-shrink:0;max-height:33vh;overflow-y:auto;background:var(--bg2);border-bottom:2px solid var(--bd)}.chip-row{display:flex;gap:var(--s1);flex-wrap:wrap;padding:var(--s1) var(--s4);border-bottom:1px solid var(--bd);background:#161b2299;align-items:center}.chip-row__label{font-size:11px;color:var(--t3);text-transform:uppercase;letter-spacing:.05em;font-weight:600;margin-right:var(--s2);white-space:nowrap}.schip{display:inline-flex;align-items:center;gap:3px;font-size:11px;font-family:var(--fm);padding:2px 6px;border-radius:10px;border:1px solid var(--bd);color:var(--t2)}.schip--ok{border-color:#3fb95033}.schip--fail{border-color:#f851494d;color:var(--fail)}.schip--off{opacity:.5}.schip--infra{border-color:#58a6ff33}.schip--worker{padding:1px 6px;border-radius:var(--r)}.schip__name{font-weight:600;color:var(--t1)}.schip__detail{color:var(--t3)}.schip__state{color:var(--t3);font-style:italic}.chip{font-size:12px;font-family:var(--fm);padding:1px 8px;border-radius:10px;border:1px solid var(--bd);color:var(--t2)}.chip--ok{border-color:#3fb9504d}.chip--fail{border-color:#f851494d;color:var(--fail)}.chip--off{opacity:.5}.chip--infra{border-color:#58a6ff4d}.agroup{border-bottom:1px solid var(--bd)}.agroup__head{display:flex;width:100%;align-items:center;gap:var(--s2);padding:var(--s1) var(--s4);background:transparent;border:none;color:var(--t1);cursor:pointer;font-size:var(--xs);text-align:left}.agroup__head:hover{background:var(--bgh)}.agroup__expand{font-size:11px;color:var(--t3);width:12px}.agroup__name{font-weight:600}.agroup__stats{color:var(--t2);font-family:var(--fm)}.agroup__fail{color:var(--fail);font-weight:600}.agroup__svc{font-size:11px;color:var(--t3);font-family:var(--fm)}.agroup__svc--ok{color:var(--ok)}.agroup__count{margin-left:auto;color:var(--t3)}.agroup__body{padding:var(--s1) var(--s4);display:flex;flex-wrap:wrap;gap:var(--s3);align-items:flex-start}.asubgroup{flex:1;min-width:200px}.asubgroup__label{font-size:11px;color:var(--t3);text-transform:uppercase;letter-spacing:.06em;padding:var(--s1) 0;font-weight:600}.asubgroup__cards{display:flex;gap:var(--s1);flex-wrap:wrap}.acard__merged{font-size:12px;color:var(--info);font-style:italic}.agent-row{display:flex;gap:var(--s2);flex-wrap:wrap;padding:var(--s2) var(--s4)}.acard{background:var(--bg3);border:1px solid var(--bd);border-radius:var(--r);padding:var(--s1) var(--s2);cursor:pointer;text-align:left;min-width:120px;flex:1 1 140px;max-width:220px;transition:border-color .1s;font-size:var(--xs)}.acard:hover{border-color:var(--info)}.acard--sel{border-color:var(--info);background:#58a6ff0f}.acard--fail{border-left:3px solid var(--fail)}.acard__r1{display:flex;align-items:center;gap:3px;margin-bottom:1px}.acard__name{font-family:var(--fm);font-size:var(--xs);font-weight:600;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.acard__source{font-size:12px;padding:0 4px;border-radius:2px;background:#58a6ff26;color:var(--info);text-transform:uppercase;letter-spacing:.04em;flex-shrink:0}.acard__pct{font-family:var(--fm);font-size:var(--xs);color:var(--ok)}.acard__pct--warn{color:var(--warn)}.acard__r2{display:flex;gap:var(--s2);font-size:12px;color:var(--t3)}.acard__failn{color:var(--fail);font-weight:700}.acard__spark{display:flex;gap:1px;align-items:flex-end;height:8px;margin-top:2px;overflow:hidden}.sk{width:2px;min-height:2px;border-radius:1px;flex-shrink:0}.sk--ok{background:var(--ok);opacity:.4}.sk--fail{background:var(--fail)}.exec-sidebar{width:270px;overflow-y:auto;border-right:2px solid var(--bd);flex-shrink:0;background:var(--bg2)}.exec-sidebar__head{display:flex;justify-content:space-between;padding:var(--s2) var(--s3);font-size:var(--xs);font-weight:600;color:var(--t3);text-transform:uppercase;letter-spacing:.06em;border-bottom:1px solid var(--bdm);position:sticky;top:0;background:var(--bg2);z-index:1}.exec-sidebar__agent,.exec-sidebar__count{font-family:var(--fm)}.exec-sidebar__fails{color:var(--fail);font-weight:700}.erow{display:flex;width:100%;align-items:center;gap:4px;padding:3px var(--s2);background:transparent;border:none;border-bottom:1px solid var(--bdm);color:var(--t2);cursor:pointer;font-size:12px;text-align:left}.erow:hover{background:var(--bgh)}.erow--sel{background:var(--bg3);border-left:2px solid var(--info)}.erow--fail{color:var(--fail)}.erow__icon{width:12px;text-align:center;font-size:12px}.erow__time{width:72px;font-size:11px;color:var(--t3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.erow__n{font-family:var(--fm);width:22px;text-align:right}.erow__dur{font-family:var(--fm);width:36px;text-align:right}.erow__bar{flex:1;height:3px;background:var(--bg);border-radius:2px;overflow:hidden}.erow__fill{display:block;height:100%;border-radius:2px}.erow__fill--ok{background:var(--ok);opacity:.4}.erow__fill--fail{background:var(--fail);opacity:.6}.agent-profile{padding:var(--s3);height:100%;display:flex;flex-direction:column}.ap-stats{display:flex;gap:var(--s5);padding:var(--s2) 0;border-bottom:1px solid var(--bdm);margin-bottom:var(--s3);flex-wrap:wrap}.ap-stat{text-align:center}.ap-stat__v{display:block;font-family:var(--fm);font-size:var(--lg);font-weight:700}.ap-stat__l{font-size:11px;color:var(--t3);text-transform:uppercase;letter-spacing:.05em}.ap-tabs{display:flex;gap:0;border-bottom:1px solid var(--bdm);margin-bottom:var(--s3)}.ap-tab{background:transparent;border:none;border-bottom:2px solid transparent;padding:var(--s2) var(--s3);font-size:var(--xs);color:var(--t3);cursor:pointer}.ap-tab:hover{color:var(--t1)}.ap-tab--active{color:var(--t1);border-bottom-color:var(--info)}.ap-content{flex:1;overflow-y:auto}.pmap__controls{display:flex;align-items:center;gap:var(--s4);margin-bottom:var(--s2);font-size:var(--xs);color:var(--t2)}.pmap__slider-label{display:flex;align-items:center;gap:var(--s2)}.pmap__slider{width:120px}.pmap__info{color:var(--t3)}.pmap__svg{display:block}.var-row{padding:var(--s2) 0;border-bottom:1px solid var(--bdm)}.var-row--happy{border-left:3px solid var(--ok);padding-left:var(--s2)}.var-row__header{display:flex;align-items:center;gap:var(--s2);font-size:var(--xs);margin-bottom:var(--s1)}.var-row__rank{font-family:var(--fm);color:var(--t3);width:24px}.var-row__badge{background:#3fb95026;color:var(--ok);font-size:11px;padding:1px 6px;border-radius:3px;font-weight:600}.var-row__count{font-family:var(--fm);color:var(--t2)}.var-row__pct-bar{flex:1;height:4px;background:var(--bg3);border-radius:2px;overflow:hidden}.var-row__pct-fill{height:100%;background:var(--info);border-radius:2px;opacity:.5}.var-row__steps{display:flex;align-items:center;gap:2px;flex-wrap:wrap}.var-row__arrow{color:var(--t3);font-size:12px;margin:0 2px}.var-row__step{font-family:var(--fm);font-size:11px;padding:1px 6px;border:1px solid;border-radius:3px;white-space:nowrap}.bn-layout{display:flex;flex-direction:column;height:100%}.bn-chart-area{flex:1;min-height:200px;max-height:50%;overflow:auto;border:1px solid var(--bdm);border-radius:var(--r);margin-bottom:var(--s2);position:relative}.bn-chart-controls{position:sticky;top:0;right:0;display:flex;justify-content:flex-end;gap:2px;padding:var(--s1);z-index:1;background:#0d1117cc}.bn-ranking{flex:1;overflow-y:auto;min-height:100px}.bn-row{display:flex;align-items:center;gap:var(--s2);padding:var(--s1) 0;font-size:var(--xs)}.bn-row__name{font-family:var(--fm);width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bn-row__type{color:var(--t3);width:60px}.bn-row__p95{font-family:var(--fm);width:60px;text-align:right;color:var(--fail)}.bn-row__bar{flex:1;height:6px;background:var(--bg3);border-radius:3px;overflow:hidden}.bn-row__fill{height:100%;background:var(--fail);opacity:.6;border-radius:3px}.exec-detail{display:flex;flex-direction:column;height:100%}.ed-header{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) var(--s3);border-bottom:1px solid var(--bdm);flex-shrink:0;flex-wrap:wrap}.ed-header__agent{font-family:var(--fm);font-weight:700;font-size:var(--sm)}.ed-header__meta{font-size:var(--xs);color:var(--t2)}.ed-header__ts{font-size:var(--xs);color:var(--t3);font-family:var(--fm);margin-left:auto}.ed-tag{font-size:11px;padding:1px 6px;background:var(--bg2);border:1px solid var(--bd);border-radius:3px;color:var(--t3);font-family:var(--fm)}.ed-tabs{display:flex;gap:0;border-bottom:1px solid var(--bdm);flex-shrink:0;overflow-x:auto;flex-wrap:wrap}.ed-tab{background:transparent;border:none;border-bottom:2px solid transparent;padding:var(--s1) var(--s2);font-size:var(--xs);color:var(--t3);cursor:pointer;white-space:nowrap}.ed-tab:hover{color:var(--t1)}.ed-tab--active{color:var(--t1);border-bottom-color:var(--info)}.ed-content{flex:1;overflow-y:auto;padding:var(--s3)}.flame__fail-callout{background:#f8514914;border:1px solid rgba(248,81,73,.25);border-radius:var(--r);padding:var(--s2) var(--s3);margin-bottom:var(--s3);font-size:var(--xs)}.flame__fail-title{color:var(--fail);font-weight:700;font-size:var(--sm);margin-bottom:var(--s1)}.flame__fail-item{display:flex;align-items:baseline;gap:var(--s2);padding:1px 0}.flame__fail-ts{font-family:var(--fm);color:var(--t3)}.flame__fail-err{color:var(--fail);font-family:var(--fm);font-size:11px}.flame__range{font-size:var(--xs);color:var(--t2);font-family:var(--fm);margin-bottom:var(--s2)}.flame__axis{display:flex;justify-content:space-between;padding:0 0 var(--s1) 28px;font-size:11px;color:var(--t3);font-family:var(--fm);border-bottom:1px solid var(--bdm)}.flame__row{display:flex;align-items:center;height:24px;position:relative}.flame__depth{width:28px;font-size:11px;color:var(--t3);text-align:right;padding-right:var(--s1);flex-shrink:0;font-family:var(--fm)}.flame__track{flex:1;height:18px;position:relative;background:var(--bg2);border-radius:2px;overflow:visible}.flame__bar{position:absolute;top:1px;height:16px;border-radius:2px;cursor:pointer;display:flex;align-items:center;overflow:hidden;transition:opacity .1s}.flame__bar--hov{z-index:1;box-shadow:0 0 4px #fff3}.flame__bar-label{font-size:12px;color:#fff;padding:0 3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 1px 2px rgba(0,0,0,.5)}.flame__side-label{position:absolute;left:calc(100% + 4px);top:1px;font-size:12px;color:var(--t1);white-space:nowrap;pointer-events:none;font-family:var(--fm)}.flame__tooltip{position:fixed;background:var(--bg3);border:1px solid var(--bd);border-radius:var(--r);padding:var(--s2);font-size:var(--xs);white-space:nowrap;z-index:9999;pointer-events:none;box-shadow:0 4px 16px #0009;min-width:220px;max-width:400px}.flame__tt-title{font-weight:700;font-size:var(--sm);margin-bottom:2px}.flame__tt-type{font-size:var(--xs);margin-bottom:2px}.flame__tt-dur{font-family:var(--fm);color:var(--t2);margin-bottom:4px}.flame__tt-meta{font-size:11px;color:var(--t3)}.flame__tt-err{font-size:var(--xs);color:var(--fail);margin-top:4px}.aflow{position:relative}.af-step{position:relative;padding:1px 0}.af-step--fail{background:#f851490a}.af-step__line{position:absolute;top:-3px;left:11px;width:1px;height:6px;background:var(--bdm)}.af-step__row{display:flex;align-items:center;gap:var(--s2);font-size:var(--xs);padding:2px var(--s2)}.af-step__icon{width:16px;text-align:center;font-size:var(--sm);flex-shrink:0}.af-step__cat{font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;width:48px;flex-shrink:0}.af-step__name{font-family:var(--fm);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.af-step__dur{font-family:var(--fm);color:var(--t3);width:48px;text-align:right;flex-shrink:0}.af-step__ts{font-family:var(--fm);color:var(--t3);font-size:11px;width:64px;text-align:right;flex-shrink:0}.af-step__ops{display:flex;gap:var(--s1);align-items:center;padding:1px var(--s2) 1px 32px;flex-wrap:wrap}.af-step__op-tag{font-size:12px;padding:0 4px;border-radius:2px;background:var(--bg);border:1px solid var(--bdm);color:var(--t2);font-family:var(--fm)}.af-step__op-tag--model{border-color:#bc8cff4d;color:#bc8cff}.af-step__op-detail{font-size:12px;color:var(--t3);font-family:var(--fm)}.af-step__err{font-size:var(--xs);color:var(--fail);padding:1px var(--s2) 1px 32px;font-family:var(--fm)}.mv-row{display:flex;gap:var(--s2);flex-wrap:wrap;margin-bottom:var(--s3)}.mv-c{background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r);padding:var(--s2);text-align:center;min-width:70px;flex:1}.mv-v{display:block;font-family:var(--fm);font-size:var(--lg);font-weight:700}.mv-l{font-size:11px;color:var(--t3);text-transform:uppercase;letter-spacing:.05em}.c-ok{color:var(--ok)}.c-fail{color:var(--fail)}.mview__section{font-size:var(--xs);font-weight:600;color:var(--t3);text-transform:uppercase;letter-spacing:.05em;margin:var(--s3) 0 var(--s2)}.mt-row{display:flex;align-items:center;gap:var(--s2);padding:2px 0;font-size:var(--xs)}.mt-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}.mt-name{font-family:var(--fm);width:70px}.mt-cnt{font-family:var(--fm);width:20px;text-align:right}.mt-fail{color:var(--fail);font-weight:700}.mt-dur{color:var(--t3);font-family:var(--fm);width:48px;text-align:right}.mt-bar{flex:1;height:5px;background:var(--bg3);border-radius:3px;overflow:hidden}.mt-fill{height:100%;border-radius:3px;opacity:.5}.dtree{font-size:var(--xs)}.dt-node{display:flex;align-items:center;gap:4px;padding:2px 0}.dt-node--fail{background:#f851490a}.dt-node__name{font-family:var(--fm);font-weight:600}.dt-node__type{font-size:11px;color:var(--t3)}.dt-node__dur{font-family:var(--fm);color:var(--t3);margin-left:auto}.dt-node__err{color:var(--fail);font-family:var(--fm);font-size:11px;margin-left:var(--s2)}.summary-content{font-size:var(--sm)}.sc-grid{display:grid;grid-template-columns:1fr 1fr;gap:var(--s2);margin-bottom:var(--s3)}.sc-label{font-size:11px;color:var(--t3);text-transform:uppercase;display:block}.sc-failures{margin:var(--s3) 0;padding:var(--s2) var(--s3);background:#f851490f;border:1px solid rgba(248,81,73,.2);border-radius:var(--r)}.sc-failures__title{color:var(--fail);font-weight:700;margin-bottom:var(--s1)}.sc-failure{display:flex;gap:var(--s2);font-size:var(--xs);padding:1px 0}.sc-failure__type{color:var(--t3)}.sc-failure__err{color:var(--fail);font-family:var(--fm)}.sc-types{margin-top:var(--s3)}.sc-types__title{font-size:var(--xs);color:var(--t3);text-transform:uppercase;margin-bottom:var(--s1)}.sc-types__list{display:flex;gap:var(--s2);flex-wrap:wrap}.sc-type-badge{font-size:11px;font-family:var(--fm);padding:1px 6px;border:1px solid var(--bd);border-radius:3px;color:var(--t2)}.alert-card{display:flex;align-items:flex-start;gap:var(--s2);padding:var(--s2) var(--s4);font-size:var(--xs);border-bottom:1px solid var(--bdm)}.alert-card--critical{background:#f851490f}.alert-card--warn{background:#d299220f}.alert-icon{flex-shrink:0}.alert-content{flex:1;min-width:0}.alert-title{font-weight:600}.alert-description{color:var(--t3)}.alert-actions{display:flex;gap:var(--s2);margin-top:3px}.alert-action{font-size:11px;font-family:var(--fm);padding:1px 6px;background:var(--bg3);border:1px solid var(--bd);border-radius:3px;color:var(--t2);cursor:pointer}.alert-action:hover{color:var(--t1)}.alert-dismiss{padding:2px;background:transparent;border:none;color:var(--t3);cursor:pointer;font-size:var(--base);flex-shrink:0}.summary-bar{display:flex;gap:var(--s5);padding:var(--s2) var(--s4);background:var(--bg2);border-top:1px solid var(--bd);font-size:var(--xs);flex-shrink:0;height:32px;align-items:center;color:var(--t2)}.tv-chat{display:flex;flex-direction:column;gap:var(--s2)}.tv-bubble{background:var(--bg2);border:1px solid var(--bd);border-radius:12px;padding:var(--s2) var(--s3);max-width:85%;font-size:var(--xs)}.tv-bubble--right{align-self:flex-end;background:#58a6ff14;border-color:#58a6ff33}.tv-bubble--user{border-color:#58a6ff40}.tv-bubble--assistant{border-color:#3fb95033}.tv-bubble--tool{border-color:#d2992233;background:#d299220a}.tv-bubble--thinking{border-color:var(--bdm);opacity:.7;border-style:dashed}.tv-bubble--system{border-color:var(--bdm);background:var(--bg3)}.tv-bubble--event{border-color:var(--bdm)}.tv-bubble--error{border-color:#f851494d;background:#f851490a}.tv-bubble__header{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s1);flex-wrap:wrap}.tv-bubble__icon{font-size:var(--sm)}.tv-bubble__role{font-weight:600;font-size:var(--xs)}.tv-bubble__model{color:var(--t3);font-family:var(--fm);font-size:11px}.tv-bubble__tokens{color:var(--t3);font-size:11px;font-family:var(--fm)}.tv-bubble__time{color:var(--t3);margin-left:auto;font-family:var(--fm);font-size:11px}.tv-bubble__tool{margin:var(--s1) 0}.tv-bubble__tool-name{font-family:var(--fm);font-weight:600;color:var(--warn);font-size:var(--xs)}.tv-bubble__content{white-space:pre-wrap;word-break:break-word;line-height:1.5;color:var(--t1)}.tv-bubble__error{color:var(--fail);font-weight:600;margin-top:var(--s1)}.tv-code{background:var(--bg);border:1px solid var(--bdm);border-radius:6px;padding:var(--s2);font-family:var(--fm);font-size:12px;overflow-x:auto;max-height:150px;overflow-y:auto;white-space:pre;margin:var(--s1) 0;color:var(--t2)}.tv-code--error{border-color:#f8514933;color:var(--fail)}.tv-thinking-btn{background:transparent;border:none;color:var(--t3);cursor:pointer;font-size:var(--xs);padding:var(--s1) 0}.tv-bubble--left{align-self:flex-start}.tv-origin{font-size:7px;padding:1px 4px;border-radius:3px;text-transform:uppercase;letter-spacing:.04em;font-weight:700}.tv-origin--user{background:#58a6ff26;color:var(--info)}.tv-origin--agent{background:#3fb9501f;color:var(--ok)}.tv-origin--system{background:#6e768126;color:var(--t3)}.settings-overlay{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000080;z-index:100;display:flex;justify-content:flex-end}.settings-panel{width:420px;max-width:90vw;background:var(--bg2);border-left:1px solid var(--bd);overflow-y:auto;padding:var(--s4)}.sp-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--s4)}.sp-head h3{font-size:var(--base);font-weight:600}.sp-close{background:transparent;border:none;color:var(--t3);cursor:pointer;font-size:var(--xl)}.sp-error{padding:var(--s2);background:#f851491a;border:1px solid rgba(248,81,73,.3);border-radius:var(--r);font-size:var(--xs);color:var(--fail);margin-bottom:var(--s3)}.sp-section{font-size:var(--xs);color:var(--t3);text-transform:uppercase;letter-spacing:.06em;margin:var(--s3) 0 var(--s2);font-weight:600}.sp-dir{display:flex;align-items:center;gap:var(--s2);padding:var(--s1) var(--s2);font-size:var(--sm);border-radius:var(--r)}.sp-dir:hover{background:var(--bgh)}.sp-dir--sug{background:#d299220a}.sp-dir__path{font-family:var(--fm);font-size:var(--xs);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.sp-btn{font-size:var(--xs);padding:2px 8px;background:var(--bg3);border:1px solid var(--bd);border-radius:var(--r);cursor:pointer;color:var(--t2)}.sp-btn--add{color:var(--info)}.sp-btn--add:hover{background:var(--bgh);border-color:var(--info)}.sp-btn--rm{color:var(--fail);font-size:var(--sm);padding:0 6px}.sp-btn--rm:hover{background:#f851491a}.sp-btn--rescan{width:100%;padding:var(--s2);color:var(--info);font-size:var(--sm)}.sp-btn--rescan:hover{background:var(--bgh)}.sp-btn:disabled{opacity:.5;cursor:default}.sp-manual{display:flex;gap:var(--s2);margin-bottom:var(--s2)}.sp-input{flex:1;background:var(--bg3);border:1px solid var(--bd);border-radius:var(--r);padding:var(--s1) var(--s2);color:var(--t1);font-family:var(--fm);font-size:var(--xs)}.sp-input:focus{outline:none;border-color:var(--info)}.zb{width:24px;height:24px;background:var(--bg3);border:1px solid var(--bd);border-radius:var(--r);color:var(--t1);cursor:pointer;font-size:var(--sm);display:inline-flex;align-items:center;justify-content:center}.zb:hover{background:var(--bgh);border-color:var(--t3)}.pmap__zoom{display:flex;gap:2px;margin-left:auto}.status-dot{display:inline-block;width:7px;height:7px;border-radius:50%;flex-shrink:0}.status-dot--ok{background:var(--ok)}.status-dot--critical{background:var(--fail)}.status-dot--warn{background:var(--warn)}.status-dot--inactive{background:var(--t3)}.soma-intel{padding:16px;font-size:13px;line-height:1.5}.soma-intel__header{display:flex;align-items:center;gap:8px;margin-bottom:16px}.soma-intel__title{font-size:16px;font-weight:700}.soma-intel__badge{font-size:10px;background:var(--info);color:#000;padding:2px 6px;border-radius:4px;font-weight:600}.soma-intel__ts{margin-left:auto;font-size:11px;color:var(--t3)}.soma-intel__ts--stale{color:var(--warn)}.soma-intel__agent-card{background:var(--bg2);border:1px solid var(--bd);border-radius:6px;padding:12px;margin-bottom:16px}.soma-intel__agent-name{font-weight:700;font-size:14px;margin-bottom:4px}.soma-intel__agent-stats{display:flex;gap:16px;font-size:12px;color:var(--t2)}.soma-intel__guard-block{margin-top:8px;padding:8px;background:#f851491a;border:1px solid var(--fail);border-radius:4px;color:var(--fail);font-size:12px;font-weight:600}.soma-intel__empty{color:var(--t3);padding:12px}.soma-intel__section{margin-bottom:16px}.soma-intel__section-header{display:flex;align-items:center;gap:8px;margin-bottom:8px;border-bottom:1px solid var(--bd);padding-bottom:4px;flex-wrap:wrap}.soma-intel__section-title{font-size:13px;font-weight:700;color:var(--t1);margin:0}.soma-intel__filters{display:flex;gap:4px;margin-left:auto}.soma-intel__filter{font-size:11px;padding:2px 6px;background:var(--bg2);border:1px solid var(--bd);border-radius:4px;color:var(--t1);cursor:pointer}.soma-intel__filter:focus{outline:1px solid var(--info)}.soma-intel__show-more{display:block;width:100%;padding:6px;margin-top:4px;background:none;border:1px dashed var(--bd);border-radius:4px;color:var(--t2);font-size:11px;cursor:pointer;text-align:center}.soma-intel__show-more:hover{background:var(--bg2);color:var(--t1)}.soma-intel__table{font-size:12px;font-family:var(--mono)}.soma-intel__row{display:grid;grid-template-columns:1fr 60px 50px 55px 60px;padding:4px 0;border-bottom:1px solid var(--bd)}.soma-intel__row--header{font-weight:700;color:var(--t2);border-bottom:2px solid var(--bd)}.soma-intel__row--active{background:#58a6ff14}.soma-intel__col-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.soma-intel__col-num,.soma-intel__col-status{text-align:right}.soma-intel__insight{padding:8px;background:var(--bg2);border-radius:4px;margin-bottom:6px}.soma-intel__insight-type{font-size:10px;text-transform:uppercase;color:var(--info);margin-right:6px;font-weight:600}.soma-intel__insight-conf{font-size:10px;color:var(--t3);margin-left:6px}.soma-intel__insight-claim{font-size:12px;color:var(--t2);margin-top:4px}.soma-intel__policy{padding:8px;background:var(--bg2);border-radius:4px;margin-bottom:6px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}.soma-intel__enforcement{font-size:10px;padding:2px 6px;border-radius:3px;font-weight:600}.soma-intel__enforcement--warn{background:#d2992233;color:var(--warn)}.soma-intel__enforcement--error{background:#f8514933;color:var(--fail)}.soma-intel__enforcement--abort{background:#f851494d;color:var(--fail)}.soma-intel__policy-cond{width:100%;font-size:11px;color:var(--t3);margin-top:2px}.soma-intel--teaser{text-align:center;padding:40px 20px}.soma-intel__teaser-icon{font-size:48px;margin-bottom:12px}.soma-intel__teaser-title{font-size:20px;font-weight:700;margin-bottom:4px}.soma-intel__teaser-subtitle{font-size:14px;color:var(--t2);margin-bottom:24px}.soma-intel__teaser-features{text-align:left;max-width:360px;margin:0 auto 24px;font-size:13px}.soma-intel__teaser-feature{padding:6px 0;color:var(--t1)}.soma-intel__teaser-cta{display:inline-block;padding:10px 24px;background:var(--info);color:#000;border-radius:6px;text-decoration:none;font-weight:700;font-size:14px}.soma-intel__teaser-cta:hover{opacity:.9}.page-tabs{display:flex;gap:0;background:var(--bg2);border-bottom:1px solid var(--bd);padding:0 var(--s3);flex-shrink:0}.page-tabs__tab{padding:8px 16px;font-size:13px;font-weight:600;color:var(--t2);background:transparent;border:none;border-bottom:2px solid transparent;cursor:pointer;transition:all .15s}.page-tabs__tab:hover{color:var(--t1);background:var(--bg3)}.page-tabs__tab--active{color:var(--info);border-bottom-color:var(--info)}.soma-page{flex:1;overflow-y:auto;padding:var(--s4)}.soma-page__tabs{display:flex;gap:4px;padding:0 0 var(--s3) 0;border-bottom:1px solid var(--bd);margin-bottom:var(--s3)}.soma-page__tab{padding:6px 14px;font-size:12px;font-weight:600;color:var(--t2);background:transparent;border:1px solid transparent;border-radius:var(--r);cursor:pointer;transition:all .15s}.soma-page__tab:hover{color:var(--t1);background:var(--bg3)}.soma-page__tab--active{color:var(--t1);background:var(--bg3);border-color:var(--bd)}.soma-page__tab--locked{opacity:.5;cursor:not-allowed}.soma-page__content{min-height:300px}.soma-page__loading,.soma-page__locked{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:300px;color:var(--t3);gap:var(--s3)}.soma-page__locked-icon{font-size:32px;opacity:.5}.soma-page__teaser{text-align:center;padding:48px var(--s4);max-width:600px;margin:0 auto}.soma-page__teaser-icon{font-size:48px;margin-bottom:var(--s3)}.soma-page__teaser h2{font-size:var(--xl);margin-bottom:var(--s2)}.soma-page__teaser p{color:var(--t2);margin-bottom:var(--s4)}.soma-page__teaser-features{display:grid;gap:var(--s3);text-align:left;margin-bottom:var(--s5)}.soma-page__teaser-card{padding:var(--s3);background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r)}.soma-page__teaser-card strong{display:block;color:var(--t1);margin-bottom:4px;font-size:13px}.soma-page__teaser-card p{font-size:12px;color:var(--t2);margin:0}.soma-page__teaser-cta{font-size:12px;color:var(--t3)}.soma-page__teaser-cta code{background:var(--bg3);padding:2px 6px;border-radius:3px;font-family:var(--fm);font-size:11px}.soma-policies{padding:var(--s3)}.soma-policies__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--s3)}.soma-policies__header h3{font-size:var(--base);font-weight:700}.soma-policies__add{font-size:12px;padding:4px 12px;background:var(--info);color:#000;border:none;border-radius:var(--r);cursor:pointer;font-weight:600}.soma-policies__form{display:flex;gap:var(--s2);margin-bottom:var(--s3);flex-wrap:wrap}.soma-policies__form input,.soma-policies__form select{padding:6px 10px;background:var(--bg);color:var(--t1);border:1px solid var(--bd);border-radius:var(--r);font-size:12px;flex:1;min-width:120px}.soma-policies__submit{padding:6px 16px;background:var(--ok);color:#000;border:none;border-radius:var(--r);cursor:pointer;font-weight:600;font-size:12px}.soma-policies__list{display:flex;flex-direction:column;gap:4px}.soma-policies__row{display:flex;align-items:center;gap:var(--s2);padding:8px var(--s3);background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r);font-size:12px}.soma-policies__name{font-weight:600;color:var(--t1);min-width:120px}.soma-policies__badge{font-size:10px;font-weight:700;border:1px solid;border-radius:3px;padding:1px 6px}.soma-policies__scope{color:var(--t2);flex:1}.soma-policies__cond{color:var(--t3);flex:2}.soma-policies__del{background:none;border:none;color:var(--fail);cursor:pointer;font-size:12px;padding:2px 6px}.soma-policies__empty{color:var(--t3);font-size:12px;padding:var(--s4);text-align:center}.soma-knowledge{padding:var(--s3)}.soma-knowledge__filters{display:flex;gap:var(--s2);margin-bottom:var(--s3);align-items:center;flex-wrap:wrap}.soma-knowledge__search{padding:6px 10px;background:var(--bg);color:var(--t1);border:1px solid var(--bd);border-radius:var(--r);font-size:12px;flex:1;min-width:150px}.soma-knowledge__filters select{padding:6px 8px;background:var(--bg);color:var(--t1);border:1px solid var(--bd);border-radius:var(--r);font-size:12px}.soma-knowledge__count{font-size:11px;color:var(--t3)}.soma-knowledge__body{display:flex;gap:var(--s3);min-height:400px}.soma-knowledge__list{flex:1;display:flex;flex-direction:column;gap:2px;overflow-y:auto;max-height:600px}.soma-knowledge__row{display:flex;align-items:center;gap:var(--s2);padding:6px var(--s3);background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r);font-size:12px;cursor:pointer;text-align:left;width:100%;color:var(--t1)}.soma-knowledge__row:hover{background:var(--bg3)}.soma-knowledge__row--sel{border-color:var(--info);background:var(--bg3)}.soma-knowledge__type{font-size:10px;font-weight:600;color:var(--t3);min-width:60px;text-transform:uppercase}.soma-knowledge__name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.soma-knowledge__layer{font-size:10px;font-weight:600;border:1px solid;border-radius:3px;padding:1px 4px}.soma-knowledge__empty{color:var(--t3);font-size:12px;padding:var(--s4);text-align:center}.soma-knowledge__more{padding:6px;background:var(--bg3);border:1px solid var(--bd);border-radius:var(--r);color:var(--info);cursor:pointer;font-size:12px;width:100%}.soma-knowledge__detail{flex:1;background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r);padding:var(--s3);overflow-y:auto;max-height:600px}.soma-knowledge__detail-header{display:flex;align-items:center;gap:var(--s2);margin-bottom:var(--s3);flex-wrap:wrap}.soma-knowledge__close{margin-left:auto;background:none;border:none;color:var(--t3);cursor:pointer;font-size:14px}.soma-knowledge__detail-body{margin-bottom:var(--s3)}.soma-knowledge__detail-body pre{font-family:var(--fm);font-size:12px;color:var(--t2);white-space:pre-wrap;word-break:break-word}.soma-knowledge__tags{display:flex;gap:4px;flex-wrap:wrap;margin-bottom:var(--s2)}.soma-knowledge__tag{font-size:10px;padding:2px 6px;background:var(--bg3);border-radius:3px;color:var(--t2)}.soma-knowledge__related{font-size:12px;color:var(--t2)}.soma-knowledge__link{background:none;border:none;color:var(--info);cursor:pointer;font-size:12px;text-decoration:underline;margin-left:4px}.soma-activity{padding:var(--s3)}.soma-activity__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--s3)}.soma-activity__header h3{font-size:var(--base);font-weight:700}.soma-activity__status{font-size:11px;color:var(--t3)}.soma-activity__list{display:flex;flex-direction:column;gap:2px;max-height:600px;overflow-y:auto}.soma-activity__empty{color:var(--t3);font-size:12px;padding:var(--s5);text-align:center}.soma-activity__event{display:flex;align-items:center;gap:var(--s2);padding:6px var(--s3);background:var(--bg2);border:1px solid var(--bd);border-radius:var(--r);font-size:12px}.soma-activity__icon{font-size:14px;width:20px;text-align:center}.soma-activity__time{font-family:var(--fm);font-size:11px;color:var(--t3);min-width:70px}.soma-activity__action{font-weight:600;color:var(--info);min-width:80px}.soma-activity__desc{color:var(--t2);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|