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/cli.cjs
CHANGED
|
@@ -43,6 +43,11 @@ var fs2 = __toESM(require("fs"), 1);
|
|
|
43
43
|
var import_node_http = require("http");
|
|
44
44
|
var path2 = __toESM(require("path"), 1);
|
|
45
45
|
var import_node_url = require("url");
|
|
46
|
+
var import_agentflow_core3 = require("agentflow-core");
|
|
47
|
+
var import_chokidar2 = __toESM(require("chokidar"), 1);
|
|
48
|
+
var import_express = __toESM(require("express"), 1);
|
|
49
|
+
var import_express_rate_limit = __toESM(require("express-rate-limit"), 1);
|
|
50
|
+
var import_ws = require("ws");
|
|
46
51
|
|
|
47
52
|
// src/config.ts
|
|
48
53
|
var import_node_fs = require("fs");
|
|
@@ -115,12 +120,6 @@ function getProcessPreference(config) {
|
|
|
115
120
|
return config.processPreference ?? null;
|
|
116
121
|
}
|
|
117
122
|
|
|
118
|
-
// src/server.ts
|
|
119
|
-
var import_agentflow_core3 = require("agentflow-core");
|
|
120
|
-
var import_chokidar2 = __toESM(require("chokidar"), 1);
|
|
121
|
-
var import_express = __toESM(require("express"), 1);
|
|
122
|
-
var import_ws = require("ws");
|
|
123
|
-
|
|
124
123
|
// src/adapters/agentflow.ts
|
|
125
124
|
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
126
125
|
"workers.json",
|
|
@@ -132,7 +131,14 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
|
132
131
|
"models.json",
|
|
133
132
|
"config.json"
|
|
134
133
|
]);
|
|
135
|
-
var SKIP_SUFFIXES = [
|
|
134
|
+
var SKIP_SUFFIXES = [
|
|
135
|
+
"-state.json",
|
|
136
|
+
"-config.json",
|
|
137
|
+
"-watch-state.json",
|
|
138
|
+
".tmp",
|
|
139
|
+
".bak",
|
|
140
|
+
".backup"
|
|
141
|
+
];
|
|
136
142
|
var AgentFlowAdapter = class {
|
|
137
143
|
name = "agentflow";
|
|
138
144
|
detect(_dirPath) {
|
|
@@ -409,8 +415,14 @@ registerAdapter(new AgentFlowAdapter());
|
|
|
409
415
|
var PURPOSE_KEYWORDS = [
|
|
410
416
|
{ keywords: ["email", "mail", "inbox", "smtp"], group: "Email Processors" },
|
|
411
417
|
{ keywords: ["monitor", "watch", "alert", "surveillance"], group: "Monitors" },
|
|
412
|
-
{
|
|
413
|
-
|
|
418
|
+
{
|
|
419
|
+
keywords: ["digest", "newsletter", "summary", "report", "briefing"],
|
|
420
|
+
group: "Digests & Reports"
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
keywords: ["curator", "janitor", "distiller", "surveyor", "worker", "indexer"],
|
|
424
|
+
group: "Workers"
|
|
425
|
+
},
|
|
414
426
|
{ keywords: ["cron", "schedule", "timer", "periodic"], group: "Scheduled Jobs" },
|
|
415
427
|
{ keywords: ["search", "scrape", "crawl", "fetch"], group: "Data Collection" },
|
|
416
428
|
{ keywords: ["embed", "vector", "index"], group: "Embeddings" }
|
|
@@ -442,6 +454,7 @@ function capitalize(s) {
|
|
|
442
454
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
443
455
|
}
|
|
444
456
|
function deduplicateAgents(agents) {
|
|
457
|
+
var _a, _b, _c, _d;
|
|
445
458
|
const tagged = agents.map((a) => ({
|
|
446
459
|
...a,
|
|
447
460
|
...extractSource(a.agentId)
|
|
@@ -458,14 +471,14 @@ function deduplicateAgents(agents) {
|
|
|
458
471
|
const mergedIds = /* @__PURE__ */ new Set();
|
|
459
472
|
const mergedAgents = [];
|
|
460
473
|
for (const [_key, group] of suffixGroups) {
|
|
461
|
-
const suffix = extractSuffix(group[0].localId);
|
|
474
|
+
const suffix = extractSuffix((_a = group[0]) == null ? void 0 : _a.localId);
|
|
462
475
|
if (group.length < 2) continue;
|
|
463
476
|
const prefixes = new Set(group.map((a) => a.localId.split("-")[0]));
|
|
464
477
|
if (prefixes.size < 2) continue;
|
|
465
478
|
const longPrefixes = [...prefixes].filter((p) => p !== suffix && p.length > 2);
|
|
466
479
|
if (longPrefixes.length >= 2) continue;
|
|
467
480
|
const merged = {
|
|
468
|
-
agentId: group[0].source === "agentflow" ? suffix : `${group[0].source}:${suffix}`,
|
|
481
|
+
agentId: ((_b = group[0]) == null ? void 0 : _b.source) === "agentflow" ? suffix : `${(_c = group[0]) == null ? void 0 : _c.source}:${suffix}`,
|
|
469
482
|
displayName: suffix,
|
|
470
483
|
totalExecutions: group.reduce((s, a) => s + a.totalExecutions, 0),
|
|
471
484
|
successfulExecutions: group.reduce((s, a) => s + a.successfulExecutions, 0),
|
|
@@ -476,7 +489,7 @@ function deduplicateAgents(agents) {
|
|
|
476
489
|
triggers: {},
|
|
477
490
|
recentActivity: group.flatMap((a) => a.recentActivity).sort((a, b) => b.timestamp - a.timestamp).slice(0, 50),
|
|
478
491
|
sources: group.map((a) => a.agentId),
|
|
479
|
-
adapterSource: group[0].source
|
|
492
|
+
adapterSource: (_d = group[0]) == null ? void 0 : _d.source
|
|
480
493
|
};
|
|
481
494
|
merged.successRate = merged.totalExecutions > 0 ? merged.successfulExecutions / merged.totalExecutions * 100 : 0;
|
|
482
495
|
const totalExecTime = group.reduce((s, a) => s + a.avgExecutionTime * a.totalExecutions, 0);
|
|
@@ -892,7 +905,9 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
|
|
|
892
905
|
...getSkipFiles(this.userConfig)
|
|
893
906
|
]);
|
|
894
907
|
this.userSkipDirs = new Set(getSkipDirectories(this.userConfig));
|
|
895
|
-
this.allWatchDirs = [
|
|
908
|
+
this.allWatchDirs = [
|
|
909
|
+
...new Set([this.tracesDir, ...this.dataDirs].map((d) => path.resolve(d)))
|
|
910
|
+
];
|
|
896
911
|
this.ensureTracesDir();
|
|
897
912
|
this.loadExistingFiles();
|
|
898
913
|
this.archiveOldTraces();
|
|
@@ -902,7 +917,7 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
|
|
|
902
917
|
/** Move trace files older than maxAgeMs into archive/YYYY-MM/ subdirectories. */
|
|
903
918
|
archiveOldTraces() {
|
|
904
919
|
const cutoff = Date.now() - this.maxAgeMs;
|
|
905
|
-
|
|
920
|
+
const _archived = 0;
|
|
906
921
|
for (const dir of this.allWatchDirs) {
|
|
907
922
|
if (!fs.existsSync(dir)) continue;
|
|
908
923
|
try {
|
|
@@ -919,7 +934,8 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
|
|
|
919
934
|
try {
|
|
920
935
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
921
936
|
for (const entry of entries) {
|
|
922
|
-
if (entry.name.startsWith(".") || entry.name === "archive" || this.userSkipDirs.has(entry.name))
|
|
937
|
+
if (entry.name.startsWith(".") || entry.name === "archive" || this.userSkipDirs.has(entry.name))
|
|
938
|
+
continue;
|
|
923
939
|
const fullPath = path.join(dir, entry.name);
|
|
924
940
|
if (entry.isDirectory()) {
|
|
925
941
|
archived += this.archiveDirectory(fullPath, cutoff, depth + 1);
|
|
@@ -2241,6 +2257,17 @@ var DashboardServer = class {
|
|
|
2241
2257
|
userConfig;
|
|
2242
2258
|
configPath;
|
|
2243
2259
|
setupExpress() {
|
|
2260
|
+
this.app.use(
|
|
2261
|
+
"/api/",
|
|
2262
|
+
(0, import_express_rate_limit.default)({
|
|
2263
|
+
windowMs: 60 * 1e3,
|
|
2264
|
+
// 1 minute
|
|
2265
|
+
max: 300,
|
|
2266
|
+
// 300 requests per minute per IP
|
|
2267
|
+
standardHeaders: true,
|
|
2268
|
+
legacyHeaders: false
|
|
2269
|
+
})
|
|
2270
|
+
);
|
|
2244
2271
|
if (this.config.enableCors) {
|
|
2245
2272
|
this.app.use((_req, res, next) => {
|
|
2246
2273
|
res.header("Access-Control-Allow-Origin", "*");
|
|
@@ -2267,10 +2294,6 @@ var DashboardServer = class {
|
|
|
2267
2294
|
if (fs2.existsSync(clientDir)) {
|
|
2268
2295
|
this.app.use(import_express.default.static(clientDir));
|
|
2269
2296
|
}
|
|
2270
|
-
const pkgVersion = JSON.parse(fs2.readFileSync(path2.resolve(__dirname, "../package.json"), "utf-8")).version;
|
|
2271
|
-
this.app.get("/api/version", (_req, res) => {
|
|
2272
|
-
res.json({ version: pkgVersion });
|
|
2273
|
-
});
|
|
2274
2297
|
this.app.get("/api/traces", (req, res) => {
|
|
2275
2298
|
try {
|
|
2276
2299
|
const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 50, 1), 200);
|
|
@@ -2487,6 +2510,7 @@ var DashboardServer = class {
|
|
|
2487
2510
|
}
|
|
2488
2511
|
});
|
|
2489
2512
|
this.app.get("/api/process-model/:agentId", (req, res) => {
|
|
2513
|
+
var _a, _b;
|
|
2490
2514
|
try {
|
|
2491
2515
|
const agentId = req.params.agentId;
|
|
2492
2516
|
const allTraces = this.watcher.getTracesByAgent(agentId);
|
|
@@ -2504,8 +2528,8 @@ var DashboardServer = class {
|
|
|
2504
2528
|
const nodeArr = Object.values(nodes);
|
|
2505
2529
|
const sorted = nodeArr.filter((n) => n.name && typeof n.startTime === "number" && n.startTime > 0).sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
|
|
2506
2530
|
for (let i = 0; i < sorted.length - 1; i++) {
|
|
2507
|
-
const from = sorted[i].name;
|
|
2508
|
-
const to = sorted[i + 1].name;
|
|
2531
|
+
const from = (_a = sorted[i]) == null ? void 0 : _a.name;
|
|
2532
|
+
const to = (_b = sorted[i + 1]) == null ? void 0 : _b.name;
|
|
2509
2533
|
const key = `${from}|||${to}`;
|
|
2510
2534
|
transMap.set(key, (transMap.get(key) ?? 0) + 1);
|
|
2511
2535
|
}
|
|
@@ -2604,7 +2628,11 @@ var DashboardServer = class {
|
|
|
2604
2628
|
try {
|
|
2605
2629
|
const reportPath = path2.join(somaVault, "..", "soma-report.json");
|
|
2606
2630
|
if (!fs2.existsSync(reportPath)) {
|
|
2607
|
-
return res.json({
|
|
2631
|
+
return res.json({
|
|
2632
|
+
available: false,
|
|
2633
|
+
teaser: false,
|
|
2634
|
+
message: "No report file yet. Run soma watch."
|
|
2635
|
+
});
|
|
2608
2636
|
}
|
|
2609
2637
|
const report = JSON.parse(fs2.readFileSync(reportPath, "utf-8"));
|
|
2610
2638
|
res.json(report);
|
|
@@ -2628,7 +2656,9 @@ var DashboardServer = class {
|
|
|
2628
2656
|
available: true,
|
|
2629
2657
|
layers: report.layers ?? { archive: 0, working: 0, emerging: 0, canon: 0 },
|
|
2630
2658
|
governance: report.governance ?? { pending: 0, promoted: 0, rejected: 0 },
|
|
2631
|
-
insights: (report.insights ?? []).filter(
|
|
2659
|
+
insights: (report.insights ?? []).filter(
|
|
2660
|
+
(i) => i.layer === "emerging" && i.proposal_status === "pending"
|
|
2661
|
+
),
|
|
2632
2662
|
canon: (report.insights ?? []).filter((i) => i.layer === "canon"),
|
|
2633
2663
|
generatedAt: report.generatedAt
|
|
2634
2664
|
});
|
|
@@ -2637,21 +2667,23 @@ var DashboardServer = class {
|
|
|
2637
2667
|
res.status(500).json({ available: false, message: "Failed to read governance data" });
|
|
2638
2668
|
}
|
|
2639
2669
|
});
|
|
2640
|
-
const
|
|
2641
|
-
const sanitizeReason = (s) => s.replace(/["`$\\]/g, "").slice(0, 500);
|
|
2670
|
+
const isValidId = (s) => /^[a-zA-Z0-9_\-.:]+$/.test(s);
|
|
2642
2671
|
this.app.post("/api/soma/governance/promote", (req, res) => {
|
|
2643
2672
|
var _a;
|
|
2644
2673
|
const somaVault = this.config.somaVault;
|
|
2645
2674
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2646
2675
|
const { entryId } = req.body ?? {};
|
|
2647
|
-
if (!entryId
|
|
2676
|
+
if (!entryId || !isValidId(String(entryId)))
|
|
2677
|
+
return res.status(400).json({ error: "Invalid entryId" });
|
|
2648
2678
|
try {
|
|
2649
|
-
const
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2679
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
2680
|
+
"npx",
|
|
2681
|
+
["soma", "governance", "promote", String(entryId), "--vault", somaVault],
|
|
2682
|
+
{
|
|
2683
|
+
encoding: "utf-8",
|
|
2684
|
+
timeout: 1e4
|
|
2685
|
+
}
|
|
2686
|
+
);
|
|
2655
2687
|
res.json({ success: true, message: result.trim() });
|
|
2656
2688
|
} catch (error) {
|
|
2657
2689
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2662,15 +2694,27 @@ var DashboardServer = class {
|
|
|
2662
2694
|
const somaVault = this.config.somaVault;
|
|
2663
2695
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2664
2696
|
const { entryId, reason } = req.body ?? {};
|
|
2665
|
-
if (!entryId || !
|
|
2697
|
+
if (!entryId || !isValidId(String(entryId)))
|
|
2698
|
+
return res.status(400).json({ error: "Invalid entryId" });
|
|
2699
|
+
if (!reason || typeof reason !== "string")
|
|
2700
|
+
return res.status(400).json({ error: "reason required" });
|
|
2666
2701
|
try {
|
|
2667
|
-
const
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2702
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
2703
|
+
"npx",
|
|
2704
|
+
[
|
|
2705
|
+
"soma",
|
|
2706
|
+
"governance",
|
|
2707
|
+
"reject",
|
|
2708
|
+
String(entryId),
|
|
2709
|
+
String(reason).slice(0, 500),
|
|
2710
|
+
"--vault",
|
|
2711
|
+
somaVault
|
|
2712
|
+
],
|
|
2713
|
+
{
|
|
2714
|
+
encoding: "utf-8",
|
|
2715
|
+
timeout: 1e4
|
|
2716
|
+
}
|
|
2717
|
+
);
|
|
2674
2718
|
res.json({ success: true, message: result.trim() });
|
|
2675
2719
|
} catch (error) {
|
|
2676
2720
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2680,13 +2724,16 @@ var DashboardServer = class {
|
|
|
2680
2724
|
var _a;
|
|
2681
2725
|
const somaVault = this.config.somaVault;
|
|
2682
2726
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2727
|
+
if (!isValidId(String(req.params.id))) return res.status(400).json({ error: "Invalid id" });
|
|
2683
2728
|
try {
|
|
2684
|
-
const
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2729
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
2730
|
+
"npx",
|
|
2731
|
+
["soma", "governance", "show", String(req.params.id), "--vault", somaVault],
|
|
2732
|
+
{
|
|
2733
|
+
encoding: "utf-8",
|
|
2734
|
+
timeout: 1e4
|
|
2735
|
+
}
|
|
2736
|
+
);
|
|
2690
2737
|
res.json({ available: true, output: result.trim() });
|
|
2691
2738
|
} catch (error) {
|
|
2692
2739
|
res.status(404).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2709,16 +2756,24 @@ var DashboardServer = class {
|
|
|
2709
2756
|
const somaVault = this.config.somaVault;
|
|
2710
2757
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2711
2758
|
const { name, enforcement, scope, conditions } = req.body ?? {};
|
|
2712
|
-
if (!name
|
|
2759
|
+
if (!name || !isValidId(String(name)))
|
|
2760
|
+
return res.status(400).json({ error: "Invalid policy name" });
|
|
2761
|
+
const enf = String(enforcement || "warn");
|
|
2762
|
+
if (!isValidId(enf)) return res.status(400).json({ error: "Invalid enforcement value" });
|
|
2713
2763
|
try {
|
|
2714
|
-
const
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2764
|
+
const args = [
|
|
2765
|
+
"soma",
|
|
2766
|
+
"policy",
|
|
2767
|
+
"create",
|
|
2768
|
+
String(name),
|
|
2769
|
+
"--enforcement",
|
|
2770
|
+
enf,
|
|
2771
|
+
"--vault",
|
|
2772
|
+
somaVault
|
|
2773
|
+
];
|
|
2774
|
+
if (scope) args.push("--scope", String(scope).slice(0, 500));
|
|
2775
|
+
if (conditions) args.push("--conditions", String(conditions).slice(0, 500));
|
|
2776
|
+
const result = (0, import_node_child_process.execFileSync)("npx", args, { encoding: "utf-8", timeout: 1e4 });
|
|
2722
2777
|
res.json({ success: true, message: result.trim() });
|
|
2723
2778
|
} catch (error) {
|
|
2724
2779
|
res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
|
|
@@ -2728,11 +2783,16 @@ var DashboardServer = class {
|
|
|
2728
2783
|
var _a;
|
|
2729
2784
|
const somaVault = this.config.somaVault;
|
|
2730
2785
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2786
|
+
if (!isValidId(String(req.params.name)))
|
|
2787
|
+
return res.status(400).json({ error: "Invalid policy name" });
|
|
2731
2788
|
try {
|
|
2732
|
-
const
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
{
|
|
2789
|
+
const result = (0, import_node_child_process.execFileSync)(
|
|
2790
|
+
"npx",
|
|
2791
|
+
["soma", "policy", "delete", String(req.params.name), "--vault", somaVault],
|
|
2792
|
+
{
|
|
2793
|
+
encoding: "utf-8",
|
|
2794
|
+
timeout: 1e4
|
|
2795
|
+
}
|
|
2736
2796
|
);
|
|
2737
2797
|
res.json({ success: true, message: result.trim() });
|
|
2738
2798
|
} catch (error) {
|
|
@@ -2750,16 +2810,28 @@ var DashboardServer = class {
|
|
|
2750
2810
|
...(report.agents ?? []).map((a) => ({ ...a, type: "agent", id: a.name })),
|
|
2751
2811
|
...(report.insights ?? []).map((i, idx) => {
|
|
2752
2812
|
var _a;
|
|
2753
|
-
return {
|
|
2813
|
+
return {
|
|
2814
|
+
...i,
|
|
2815
|
+
type: i.type || "insight",
|
|
2816
|
+
id: ((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || `insight-${idx}`
|
|
2817
|
+
};
|
|
2754
2818
|
}),
|
|
2755
2819
|
...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
|
|
2756
2820
|
];
|
|
2757
|
-
const {
|
|
2821
|
+
const {
|
|
2822
|
+
type,
|
|
2823
|
+
layer,
|
|
2824
|
+
q,
|
|
2825
|
+
limit: limitStr,
|
|
2826
|
+
offset: offsetStr
|
|
2827
|
+
} = req.query;
|
|
2758
2828
|
if (type) entities = entities.filter((e) => e.type === type);
|
|
2759
2829
|
if (layer) entities = entities.filter((e) => e.layer === layer);
|
|
2760
2830
|
if (q) {
|
|
2761
2831
|
const lq = q.toLowerCase();
|
|
2762
|
-
entities = entities.filter(
|
|
2832
|
+
entities = entities.filter(
|
|
2833
|
+
(e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq)
|
|
2834
|
+
);
|
|
2763
2835
|
}
|
|
2764
2836
|
const total = entities.length;
|
|
2765
2837
|
const offset = parseInt(offsetStr || "0", 10);
|
|
@@ -2850,9 +2922,7 @@ var DashboardServer = class {
|
|
|
2850
2922
|
const orphans = uniqueProcesses.filter(
|
|
2851
2923
|
(p) => !allKnownPids.has(p.pid) && p.pid !== process.pid && p.pid !== process.ppid
|
|
2852
2924
|
);
|
|
2853
|
-
const problems = services.flatMap(
|
|
2854
|
-
(s) => s.audit.problems.map((p) => `[${s.name}] ${p}`)
|
|
2855
|
-
);
|
|
2925
|
+
const problems = services.flatMap((s) => s.audit.problems.map((p) => `[${s.name}] ${p}`));
|
|
2856
2926
|
const result = {
|
|
2857
2927
|
// Backward-compatible fields from primary service
|
|
2858
2928
|
pidFile: (primary == null ? void 0 : primary.audit.pidFile) ?? null,
|
|
@@ -2907,19 +2977,24 @@ var DashboardServer = class {
|
|
|
2907
2977
|
}
|
|
2908
2978
|
} catch {
|
|
2909
2979
|
}
|
|
2910
|
-
const watched = [
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2980
|
+
const watched = [
|
|
2981
|
+
...new Set(
|
|
2982
|
+
[this.config.tracesDir, ...this.config.dataDirs || [], ...extraDirs].map(
|
|
2983
|
+
(w) => path2.resolve(w)
|
|
2984
|
+
)
|
|
2985
|
+
)
|
|
2986
|
+
];
|
|
2915
2987
|
const discovered = [];
|
|
2916
2988
|
const svcNames = getSystemdServices(this.userConfig);
|
|
2917
2989
|
if (svcNames.length > 0) {
|
|
2918
2990
|
try {
|
|
2919
|
-
const
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
{
|
|
2991
|
+
const raw = (0, import_node_child_process.execFileSync)(
|
|
2992
|
+
"systemctl",
|
|
2993
|
+
["--user", "show", "--property=ExecStart", "--no-pager", ...svcNames],
|
|
2994
|
+
{
|
|
2995
|
+
encoding: "utf8",
|
|
2996
|
+
timeout: 5e3
|
|
2997
|
+
}
|
|
2923
2998
|
);
|
|
2924
2999
|
for (const line of raw.split("\n")) {
|
|
2925
3000
|
const match = line.match(/path=([^\s;]+)/);
|
|
@@ -2951,10 +3026,19 @@ var DashboardServer = class {
|
|
|
2951
3026
|
this.app.post("/api/directories", import_express.default.json(), (req, res) => {
|
|
2952
3027
|
try {
|
|
2953
3028
|
const { add, remove } = req.body;
|
|
2954
|
-
if (add
|
|
2955
|
-
|
|
3029
|
+
if (add) {
|
|
3030
|
+
const resolved = path2.resolve(add);
|
|
3031
|
+
if (resolved !== add || add.includes("..")) {
|
|
3032
|
+
return res.status(400).json({ error: "Invalid directory path" });
|
|
3033
|
+
}
|
|
3034
|
+
if (!fs2.existsSync(resolved)) {
|
|
3035
|
+
return res.status(400).json({ error: `Directory does not exist: ${add}` });
|
|
3036
|
+
}
|
|
2956
3037
|
}
|
|
2957
|
-
const configPath = path2.join(
|
|
3038
|
+
const configPath = path2.join(
|
|
3039
|
+
process.env.HOME ?? "/home/trader",
|
|
3040
|
+
".agentflow/dashboard-config.json"
|
|
3041
|
+
);
|
|
2958
3042
|
let config = {};
|
|
2959
3043
|
try {
|
|
2960
3044
|
if (fs2.existsSync(configPath)) {
|
|
@@ -3164,13 +3248,31 @@ var DashboardServer = class {
|
|
|
3164
3248
|
isVirtual: false
|
|
3165
3249
|
});
|
|
3166
3250
|
}
|
|
3167
|
-
const
|
|
3168
|
-
const
|
|
3169
|
-
const
|
|
3170
|
-
for (const
|
|
3171
|
-
}
|
|
3172
|
-
nodes.push({
|
|
3173
|
-
|
|
3251
|
+
const _rootSteps = new Set(model.steps);
|
|
3252
|
+
const _childSteps = new Set(model.transitions.map((t) => t.to));
|
|
3253
|
+
const _leafSteps = new Set(model.steps);
|
|
3254
|
+
for (const _t of model.transitions) {
|
|
3255
|
+
}
|
|
3256
|
+
nodes.push({
|
|
3257
|
+
id: "[START]",
|
|
3258
|
+
label: "[START]",
|
|
3259
|
+
count: model.totalGraphs,
|
|
3260
|
+
frequency: 1,
|
|
3261
|
+
avgDuration: 0,
|
|
3262
|
+
failRate: 0,
|
|
3263
|
+
p95Duration: 0,
|
|
3264
|
+
isVirtual: true
|
|
3265
|
+
});
|
|
3266
|
+
nodes.push({
|
|
3267
|
+
id: "[END]",
|
|
3268
|
+
label: "[END]",
|
|
3269
|
+
count: model.totalGraphs,
|
|
3270
|
+
frequency: 1,
|
|
3271
|
+
avgDuration: 0,
|
|
3272
|
+
failRate: 0,
|
|
3273
|
+
p95Duration: 0,
|
|
3274
|
+
isVirtual: true
|
|
3275
|
+
});
|
|
3174
3276
|
const edges = model.transitions.map((t) => ({
|
|
3175
3277
|
source: t.from,
|
|
3176
3278
|
target: t.to,
|
|
@@ -3190,7 +3292,10 @@ var DashboardServer = class {
|
|
|
3190
3292
|
}
|
|
3191
3293
|
}
|
|
3192
3294
|
const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
|
|
3193
|
-
const maxNodeCount = Math.max(
|
|
3295
|
+
const maxNodeCount = Math.max(
|
|
3296
|
+
...nodes.filter((n) => !n.isVirtual).map((n) => n.count),
|
|
3297
|
+
1
|
|
3298
|
+
);
|
|
3194
3299
|
return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
|
|
3195
3300
|
}
|
|
3196
3301
|
/**
|
|
@@ -3378,7 +3483,9 @@ if (import_meta.url === `file://${process.argv[1]}`) {
|
|
|
3378
3483
|
// src/cli.ts
|
|
3379
3484
|
var import_meta2 = {};
|
|
3380
3485
|
var __cliDirname = path3.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url));
|
|
3381
|
-
var VERSION = JSON.parse(
|
|
3486
|
+
var VERSION = JSON.parse(
|
|
3487
|
+
fs3.readFileSync(path3.resolve(__cliDirname, "../package.json"), "utf-8")
|
|
3488
|
+
).version;
|
|
3382
3489
|
function getLanAddress() {
|
|
3383
3490
|
const interfaces = os.networkInterfaces();
|
|
3384
3491
|
for (const name of Object.keys(interfaces)) {
|
package/dist/cli.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
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}
|