agentflow-dashboard 0.8.3 → 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-E5RJCBK2.js → chunk-YLQ5MVCW.js} +204 -100
- package/dist/cli.cjs +193 -82
- package/dist/cli.js +1 -1
- package/dist/client/assets/index-BQBa4cES.css +1 -0
- package/dist/client/assets/{index-BgEw2MGK.js → index-DA9m90ZC.js} +7 -7
- package/dist/client/index.html +2 -2
- package/dist/index.cjs +193 -82
- package/dist/index.js +1 -1
- package/dist/server.cjs +193 -82
- 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", "*");
|
|
@@ -2660,6 +2689,7 @@ var DashboardServer = class {
|
|
|
2660
2689
|
}
|
|
2661
2690
|
});
|
|
2662
2691
|
this.app.get("/api/process-model/:agentId", (req, res) => {
|
|
2692
|
+
var _a, _b;
|
|
2663
2693
|
try {
|
|
2664
2694
|
const agentId = req.params.agentId;
|
|
2665
2695
|
const allTraces = this.watcher.getTracesByAgent(agentId);
|
|
@@ -2677,8 +2707,8 @@ var DashboardServer = class {
|
|
|
2677
2707
|
const nodeArr = Object.values(nodes);
|
|
2678
2708
|
const sorted = nodeArr.filter((n) => n.name && typeof n.startTime === "number" && n.startTime > 0).sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
|
|
2679
2709
|
for (let i = 0; i < sorted.length - 1; i++) {
|
|
2680
|
-
const from = sorted[i].name;
|
|
2681
|
-
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;
|
|
2682
2712
|
const key = `${from}|||${to}`;
|
|
2683
2713
|
transMap.set(key, (transMap.get(key) ?? 0) + 1);
|
|
2684
2714
|
}
|
|
@@ -2777,7 +2807,11 @@ var DashboardServer = class {
|
|
|
2777
2807
|
try {
|
|
2778
2808
|
const reportPath = path3.join(somaVault, "..", "soma-report.json");
|
|
2779
2809
|
if (!fs3.existsSync(reportPath)) {
|
|
2780
|
-
return res.json({
|
|
2810
|
+
return res.json({
|
|
2811
|
+
available: false,
|
|
2812
|
+
teaser: false,
|
|
2813
|
+
message: "No report file yet. Run soma watch."
|
|
2814
|
+
});
|
|
2781
2815
|
}
|
|
2782
2816
|
const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
|
|
2783
2817
|
res.json(report);
|
|
@@ -2801,7 +2835,9 @@ var DashboardServer = class {
|
|
|
2801
2835
|
available: true,
|
|
2802
2836
|
layers: report.layers ?? { archive: 0, working: 0, emerging: 0, canon: 0 },
|
|
2803
2837
|
governance: report.governance ?? { pending: 0, promoted: 0, rejected: 0 },
|
|
2804
|
-
insights: (report.insights ?? []).filter(
|
|
2838
|
+
insights: (report.insights ?? []).filter(
|
|
2839
|
+
(i) => i.layer === "emerging" && i.proposal_status === "pending"
|
|
2840
|
+
),
|
|
2805
2841
|
canon: (report.insights ?? []).filter((i) => i.layer === "canon"),
|
|
2806
2842
|
generatedAt: report.generatedAt
|
|
2807
2843
|
});
|
|
@@ -2810,21 +2846,23 @@ var DashboardServer = class {
|
|
|
2810
2846
|
res.status(500).json({ available: false, message: "Failed to read governance data" });
|
|
2811
2847
|
}
|
|
2812
2848
|
});
|
|
2813
|
-
const
|
|
2814
|
-
const sanitizeReason = (s) => s.replace(/["`$\\]/g, "").slice(0, 500);
|
|
2849
|
+
const isValidId = (s) => /^[a-zA-Z0-9_\-.:]+$/.test(s);
|
|
2815
2850
|
this.app.post("/api/soma/governance/promote", (req, res) => {
|
|
2816
2851
|
var _a;
|
|
2817
2852
|
const somaVault = this.config.somaVault;
|
|
2818
2853
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2819
2854
|
const { entryId } = req.body ?? {};
|
|
2820
|
-
if (!entryId
|
|
2855
|
+
if (!entryId || !isValidId(String(entryId)))
|
|
2856
|
+
return res.status(400).json({ error: "Invalid entryId" });
|
|
2821
2857
|
try {
|
|
2822
|
-
const
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
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
|
+
);
|
|
2828
2866
|
res.json({ success: true, message: result.trim() });
|
|
2829
2867
|
} catch (error) {
|
|
2830
2868
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2835,15 +2873,27 @@ var DashboardServer = class {
|
|
|
2835
2873
|
const somaVault = this.config.somaVault;
|
|
2836
2874
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2837
2875
|
const { entryId, reason } = req.body ?? {};
|
|
2838
|
-
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" });
|
|
2839
2880
|
try {
|
|
2840
|
-
const
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
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
|
+
);
|
|
2847
2897
|
res.json({ success: true, message: result.trim() });
|
|
2848
2898
|
} catch (error) {
|
|
2849
2899
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2853,13 +2903,16 @@ var DashboardServer = class {
|
|
|
2853
2903
|
var _a;
|
|
2854
2904
|
const somaVault = this.config.somaVault;
|
|
2855
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" });
|
|
2856
2907
|
try {
|
|
2857
|
-
const
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
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
|
+
);
|
|
2863
2916
|
res.json({ available: true, output: result.trim() });
|
|
2864
2917
|
} catch (error) {
|
|
2865
2918
|
res.status(404).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2882,16 +2935,24 @@ var DashboardServer = class {
|
|
|
2882
2935
|
const somaVault = this.config.somaVault;
|
|
2883
2936
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2884
2937
|
const { name, enforcement, scope, conditions } = req.body ?? {};
|
|
2885
|
-
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" });
|
|
2886
2942
|
try {
|
|
2887
|
-
const
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
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 });
|
|
2895
2956
|
res.json({ success: true, message: result.trim() });
|
|
2896
2957
|
} catch (error) {
|
|
2897
2958
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2901,11 +2962,16 @@ var DashboardServer = class {
|
|
|
2901
2962
|
var _a;
|
|
2902
2963
|
const somaVault = this.config.somaVault;
|
|
2903
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" });
|
|
2904
2967
|
try {
|
|
2905
|
-
const
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
{
|
|
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
|
+
}
|
|
2909
2975
|
);
|
|
2910
2976
|
res.json({ success: true, message: result.trim() });
|
|
2911
2977
|
} catch (error) {
|
|
@@ -2923,16 +2989,28 @@ var DashboardServer = class {
|
|
|
2923
2989
|
...(report.agents ?? []).map((a) => ({ ...a, type: "agent", id: a.name })),
|
|
2924
2990
|
...(report.insights ?? []).map((i, idx) => {
|
|
2925
2991
|
var _a;
|
|
2926
|
-
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
|
+
};
|
|
2927
2997
|
}),
|
|
2928
2998
|
...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
|
|
2929
2999
|
];
|
|
2930
|
-
const {
|
|
3000
|
+
const {
|
|
3001
|
+
type,
|
|
3002
|
+
layer,
|
|
3003
|
+
q,
|
|
3004
|
+
limit: limitStr,
|
|
3005
|
+
offset: offsetStr
|
|
3006
|
+
} = req.query;
|
|
2931
3007
|
if (type) entities = entities.filter((e) => e.type === type);
|
|
2932
3008
|
if (layer) entities = entities.filter((e) => e.layer === layer);
|
|
2933
3009
|
if (q) {
|
|
2934
3010
|
const lq = q.toLowerCase();
|
|
2935
|
-
entities = entities.filter(
|
|
3011
|
+
entities = entities.filter(
|
|
3012
|
+
(e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq)
|
|
3013
|
+
);
|
|
2936
3014
|
}
|
|
2937
3015
|
const total = entities.length;
|
|
2938
3016
|
const offset = parseInt(offsetStr || "0", 10);
|
|
@@ -3023,9 +3101,7 @@ var DashboardServer = class {
|
|
|
3023
3101
|
const orphans = uniqueProcesses.filter(
|
|
3024
3102
|
(p) => !allKnownPids.has(p.pid) && p.pid !== process.pid && p.pid !== process.ppid
|
|
3025
3103
|
);
|
|
3026
|
-
const problems = services.flatMap(
|
|
3027
|
-
(s) => s.audit.problems.map((p) => `[${s.name}] ${p}`)
|
|
3028
|
-
);
|
|
3104
|
+
const problems = services.flatMap((s) => s.audit.problems.map((p) => `[${s.name}] ${p}`));
|
|
3029
3105
|
const result = {
|
|
3030
3106
|
// Backward-compatible fields from primary service
|
|
3031
3107
|
pidFile: (primary == null ? void 0 : primary.audit.pidFile) ?? null,
|
|
@@ -3080,19 +3156,24 @@ var DashboardServer = class {
|
|
|
3080
3156
|
}
|
|
3081
3157
|
} catch {
|
|
3082
3158
|
}
|
|
3083
|
-
const watched = [
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3159
|
+
const watched = [
|
|
3160
|
+
...new Set(
|
|
3161
|
+
[this.config.tracesDir, ...this.config.dataDirs || [], ...extraDirs].map(
|
|
3162
|
+
(w) => path3.resolve(w)
|
|
3163
|
+
)
|
|
3164
|
+
)
|
|
3165
|
+
];
|
|
3088
3166
|
const discovered = [];
|
|
3089
3167
|
const svcNames = getSystemdServices(this.userConfig);
|
|
3090
3168
|
if (svcNames.length > 0) {
|
|
3091
3169
|
try {
|
|
3092
|
-
const
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
{
|
|
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
|
+
}
|
|
3096
3177
|
);
|
|
3097
3178
|
for (const line of raw.split("\n")) {
|
|
3098
3179
|
const match = line.match(/path=([^\s;]+)/);
|
|
@@ -3124,10 +3205,19 @@ var DashboardServer = class {
|
|
|
3124
3205
|
this.app.post("/api/directories", import_express.default.json(), (req, res) => {
|
|
3125
3206
|
try {
|
|
3126
3207
|
const { add, remove } = req.body;
|
|
3127
|
-
if (add
|
|
3128
|
-
|
|
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
|
+
}
|
|
3129
3216
|
}
|
|
3130
|
-
const configPath = path3.join(
|
|
3217
|
+
const configPath = path3.join(
|
|
3218
|
+
process.env.HOME ?? "/home/trader",
|
|
3219
|
+
".agentflow/dashboard-config.json"
|
|
3220
|
+
);
|
|
3131
3221
|
let config = {};
|
|
3132
3222
|
try {
|
|
3133
3223
|
if (fs3.existsSync(configPath)) {
|
|
@@ -3337,13 +3427,31 @@ var DashboardServer = class {
|
|
|
3337
3427
|
isVirtual: false
|
|
3338
3428
|
});
|
|
3339
3429
|
}
|
|
3340
|
-
const
|
|
3341
|
-
const
|
|
3342
|
-
const
|
|
3343
|
-
for (const
|
|
3344
|
-
}
|
|
3345
|
-
nodes.push({
|
|
3346
|
-
|
|
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
|
+
});
|
|
3347
3455
|
const edges = model.transitions.map((t) => ({
|
|
3348
3456
|
source: t.from,
|
|
3349
3457
|
target: t.to,
|
|
@@ -3363,7 +3471,10 @@ var DashboardServer = class {
|
|
|
3363
3471
|
}
|
|
3364
3472
|
}
|
|
3365
3473
|
const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
|
|
3366
|
-
const maxNodeCount = Math.max(
|
|
3474
|
+
const maxNodeCount = Math.max(
|
|
3475
|
+
...nodes.filter((n) => !n.isVirtual).map((n) => n.count),
|
|
3476
|
+
1
|
|
3477
|
+
);
|
|
3367
3478
|
return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
|
|
3368
3479
|
}
|
|
3369
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}
|