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/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 = ["-state.json", "-config.json", "-watch-state.json", ".tmp", ".bak", ".backup"];
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
- { keywords: ["digest", "newsletter", "summary", "report", "briefing"], group: "Digests & Reports" },
413
- { keywords: ["curator", "janitor", "distiller", "surveyor", "worker", "indexer"], group: "Workers" },
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 = [...new Set([this.tracesDir, ...this.dataDirs].map((d) => path.resolve(d)))];
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
- let archived = 0;
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)) continue;
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({ available: false, teaser: false, message: "No report file yet. Run soma watch." });
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((i) => i.layer === "emerging" && i.proposal_status === "pending"),
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 sanitizeArg = (s) => s.replace(/[^a-zA-Z0-9_\-.:]/g, "");
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) return res.status(400).json({ error: "entryId required" });
2676
+ if (!entryId || !isValidId(String(entryId)))
2677
+ return res.status(400).json({ error: "Invalid entryId" });
2648
2678
  try {
2649
- const { execSync: execSync2 } = require("child_process");
2650
- const safeId = sanitizeArg(String(entryId));
2651
- const result = execSync2(`npx soma governance promote ${safeId} --vault "${somaVault}"`, {
2652
- encoding: "utf-8",
2653
- timeout: 1e4
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 || !reason) return res.status(400).json({ error: "entryId and reason required" });
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 { execSync: execSync2 } = require("child_process");
2668
- const safeId = sanitizeArg(String(entryId));
2669
- const safeReason = sanitizeReason(String(reason));
2670
- const result = execSync2(`npx soma governance reject ${safeId} "${safeReason}" --vault "${somaVault}"`, {
2671
- encoding: "utf-8",
2672
- timeout: 1e4
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 { execSync: execSync2 } = require("child_process");
2685
- const safeId = sanitizeArg(String(req.params.id));
2686
- const result = execSync2(`npx soma governance show ${safeId} --vault "${somaVault}"`, {
2687
- encoding: "utf-8",
2688
- timeout: 1e4
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) return res.status(400).json({ error: "name required" });
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 safeName = sanitizeArg(String(name));
2715
- const safeEnf = sanitizeArg(String(enforcement || "warn"));
2716
- const safeScope = sanitizeReason(String(scope || "all"));
2717
- const safeCond = sanitizeReason(String(conditions || ""));
2718
- const result = (0, import_node_child_process.execSync)(
2719
- `npx soma policy create "${safeName}" --enforcement ${safeEnf} --scope "${safeScope}" --conditions "${safeCond}" --vault "${somaVault}"`,
2720
- { encoding: "utf-8", timeout: 1e4 }
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 safeName = sanitizeArg(String(req.params.name));
2733
- const result = (0, import_node_child_process.execSync)(
2734
- `npx soma policy delete "${safeName}" --vault "${somaVault}"`,
2735
- { encoding: "utf-8", timeout: 1e4 }
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 { ...i, type: i.type || "insight", id: ((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || `insight-${idx}` };
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 { type, layer, q, limit: limitStr, offset: offsetStr } = req.query;
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((e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq));
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 = [...new Set([
2911
- this.config.tracesDir,
2912
- ...this.config.dataDirs || [],
2913
- ...extraDirs
2914
- ].map((w) => path2.resolve(w)))];
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 { execSync: execSync2 } = require("child_process");
2920
- const raw = execSync2(
2921
- `systemctl --user show --property=ExecStart --no-pager ${svcNames.join(" ")} 2>/dev/null`,
2922
- { encoding: "utf8", timeout: 5e3 }
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 && !fs2.existsSync(add)) {
2955
- return res.status(400).json({ error: `Directory does not exist: ${add}` });
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(process.env.HOME ?? "/home/trader", ".agentflow/dashboard-config.json");
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 rootSteps = new Set(model.steps);
3168
- const childSteps = new Set(model.transitions.map((t) => t.to));
3169
- const leafSteps = new Set(model.steps);
3170
- for (const t of model.transitions) {
3171
- }
3172
- nodes.push({ id: "[START]", label: "[START]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
3173
- nodes.push({ id: "[END]", label: "[END]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
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(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
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(fs3.readFileSync(path3.resolve(__cliDirname, "../package.json"), "utf-8")).version;
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
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startDashboard
3
- } from "./chunk-EG254FLY.js";
3
+ } from "./chunk-YLQ5MVCW.js";
4
4
  export {
5
5
  startDashboard
6
6
  };
@@ -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}