agentflow-dashboard 0.8.3 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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", "*");
@@ -2483,6 +2510,7 @@ var DashboardServer = class {
2483
2510
  }
2484
2511
  });
2485
2512
  this.app.get("/api/process-model/:agentId", (req, res) => {
2513
+ var _a, _b;
2486
2514
  try {
2487
2515
  const agentId = req.params.agentId;
2488
2516
  const allTraces = this.watcher.getTracesByAgent(agentId);
@@ -2500,8 +2528,8 @@ var DashboardServer = class {
2500
2528
  const nodeArr = Object.values(nodes);
2501
2529
  const sorted = nodeArr.filter((n) => n.name && typeof n.startTime === "number" && n.startTime > 0).sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
2502
2530
  for (let i = 0; i < sorted.length - 1; i++) {
2503
- const from = sorted[i].name;
2504
- 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;
2505
2533
  const key = `${from}|||${to}`;
2506
2534
  transMap.set(key, (transMap.get(key) ?? 0) + 1);
2507
2535
  }
@@ -2600,7 +2628,11 @@ var DashboardServer = class {
2600
2628
  try {
2601
2629
  const reportPath = path2.join(somaVault, "..", "soma-report.json");
2602
2630
  if (!fs2.existsSync(reportPath)) {
2603
- 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
+ });
2604
2636
  }
2605
2637
  const report = JSON.parse(fs2.readFileSync(reportPath, "utf-8"));
2606
2638
  res.json(report);
@@ -2624,7 +2656,9 @@ var DashboardServer = class {
2624
2656
  available: true,
2625
2657
  layers: report.layers ?? { archive: 0, working: 0, emerging: 0, canon: 0 },
2626
2658
  governance: report.governance ?? { pending: 0, promoted: 0, rejected: 0 },
2627
- 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
+ ),
2628
2662
  canon: (report.insights ?? []).filter((i) => i.layer === "canon"),
2629
2663
  generatedAt: report.generatedAt
2630
2664
  });
@@ -2633,21 +2667,23 @@ var DashboardServer = class {
2633
2667
  res.status(500).json({ available: false, message: "Failed to read governance data" });
2634
2668
  }
2635
2669
  });
2636
- const sanitizeArg = (s) => s.replace(/[^a-zA-Z0-9_\-.:]/g, "");
2637
- const sanitizeReason = (s) => s.replace(/["`$\\]/g, "").slice(0, 500);
2670
+ const isValidId = (s) => /^[a-zA-Z0-9_\-.:]+$/.test(s);
2638
2671
  this.app.post("/api/soma/governance/promote", (req, res) => {
2639
2672
  var _a;
2640
2673
  const somaVault = this.config.somaVault;
2641
2674
  if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2642
2675
  const { entryId } = req.body ?? {};
2643
- 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" });
2644
2678
  try {
2645
- const { execSync: execSync2 } = require("child_process");
2646
- const safeId = sanitizeArg(String(entryId));
2647
- const result = execSync2(`npx soma governance promote ${safeId} --vault "${somaVault}"`, {
2648
- encoding: "utf-8",
2649
- timeout: 1e4
2650
- });
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
+ );
2651
2687
  res.json({ success: true, message: result.trim() });
2652
2688
  } catch (error) {
2653
2689
  res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
@@ -2658,15 +2694,27 @@ var DashboardServer = class {
2658
2694
  const somaVault = this.config.somaVault;
2659
2695
  if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2660
2696
  const { entryId, reason } = req.body ?? {};
2661
- 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" });
2662
2701
  try {
2663
- const { execSync: execSync2 } = require("child_process");
2664
- const safeId = sanitizeArg(String(entryId));
2665
- const safeReason = sanitizeReason(String(reason));
2666
- const result = execSync2(`npx soma governance reject ${safeId} "${safeReason}" --vault "${somaVault}"`, {
2667
- encoding: "utf-8",
2668
- timeout: 1e4
2669
- });
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
+ );
2670
2718
  res.json({ success: true, message: result.trim() });
2671
2719
  } catch (error) {
2672
2720
  res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
@@ -2676,13 +2724,16 @@ var DashboardServer = class {
2676
2724
  var _a;
2677
2725
  const somaVault = this.config.somaVault;
2678
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" });
2679
2728
  try {
2680
- const { execSync: execSync2 } = require("child_process");
2681
- const safeId = sanitizeArg(String(req.params.id));
2682
- const result = execSync2(`npx soma governance show ${safeId} --vault "${somaVault}"`, {
2683
- encoding: "utf-8",
2684
- timeout: 1e4
2685
- });
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
+ );
2686
2737
  res.json({ available: true, output: result.trim() });
2687
2738
  } catch (error) {
2688
2739
  res.status(404).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
@@ -2705,16 +2756,24 @@ var DashboardServer = class {
2705
2756
  const somaVault = this.config.somaVault;
2706
2757
  if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2707
2758
  const { name, enforcement, scope, conditions } = req.body ?? {};
2708
- 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" });
2709
2763
  try {
2710
- const safeName = sanitizeArg(String(name));
2711
- const safeEnf = sanitizeArg(String(enforcement || "warn"));
2712
- const safeScope = sanitizeReason(String(scope || "all"));
2713
- const safeCond = sanitizeReason(String(conditions || ""));
2714
- const result = (0, import_node_child_process.execSync)(
2715
- `npx soma policy create "${safeName}" --enforcement ${safeEnf} --scope "${safeScope}" --conditions "${safeCond}" --vault "${somaVault}"`,
2716
- { encoding: "utf-8", timeout: 1e4 }
2717
- );
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 });
2718
2777
  res.json({ success: true, message: result.trim() });
2719
2778
  } catch (error) {
2720
2779
  res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
@@ -2724,11 +2783,16 @@ var DashboardServer = class {
2724
2783
  var _a;
2725
2784
  const somaVault = this.config.somaVault;
2726
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" });
2727
2788
  try {
2728
- const safeName = sanitizeArg(String(req.params.name));
2729
- const result = (0, import_node_child_process.execSync)(
2730
- `npx soma policy delete "${safeName}" --vault "${somaVault}"`,
2731
- { 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
+ }
2732
2796
  );
2733
2797
  res.json({ success: true, message: result.trim() });
2734
2798
  } catch (error) {
@@ -2746,16 +2810,28 @@ var DashboardServer = class {
2746
2810
  ...(report.agents ?? []).map((a) => ({ ...a, type: "agent", id: a.name })),
2747
2811
  ...(report.insights ?? []).map((i, idx) => {
2748
2812
  var _a;
2749
- 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
+ };
2750
2818
  }),
2751
2819
  ...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
2752
2820
  ];
2753
- 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;
2754
2828
  if (type) entities = entities.filter((e) => e.type === type);
2755
2829
  if (layer) entities = entities.filter((e) => e.layer === layer);
2756
2830
  if (q) {
2757
2831
  const lq = q.toLowerCase();
2758
- 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
+ );
2759
2835
  }
2760
2836
  const total = entities.length;
2761
2837
  const offset = parseInt(offsetStr || "0", 10);
@@ -2846,9 +2922,7 @@ var DashboardServer = class {
2846
2922
  const orphans = uniqueProcesses.filter(
2847
2923
  (p) => !allKnownPids.has(p.pid) && p.pid !== process.pid && p.pid !== process.ppid
2848
2924
  );
2849
- const problems = services.flatMap(
2850
- (s) => s.audit.problems.map((p) => `[${s.name}] ${p}`)
2851
- );
2925
+ const problems = services.flatMap((s) => s.audit.problems.map((p) => `[${s.name}] ${p}`));
2852
2926
  const result = {
2853
2927
  // Backward-compatible fields from primary service
2854
2928
  pidFile: (primary == null ? void 0 : primary.audit.pidFile) ?? null,
@@ -2903,19 +2977,24 @@ var DashboardServer = class {
2903
2977
  }
2904
2978
  } catch {
2905
2979
  }
2906
- const watched = [...new Set([
2907
- this.config.tracesDir,
2908
- ...this.config.dataDirs || [],
2909
- ...extraDirs
2910
- ].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
+ ];
2911
2987
  const discovered = [];
2912
2988
  const svcNames = getSystemdServices(this.userConfig);
2913
2989
  if (svcNames.length > 0) {
2914
2990
  try {
2915
- const { execSync: execSync2 } = require("child_process");
2916
- const raw = execSync2(
2917
- `systemctl --user show --property=ExecStart --no-pager ${svcNames.join(" ")} 2>/dev/null`,
2918
- { 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
+ }
2919
2998
  );
2920
2999
  for (const line of raw.split("\n")) {
2921
3000
  const match = line.match(/path=([^\s;]+)/);
@@ -2947,10 +3026,19 @@ var DashboardServer = class {
2947
3026
  this.app.post("/api/directories", import_express.default.json(), (req, res) => {
2948
3027
  try {
2949
3028
  const { add, remove } = req.body;
2950
- if (add && !fs2.existsSync(add)) {
2951
- 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
+ }
2952
3037
  }
2953
- 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
+ );
2954
3042
  let config = {};
2955
3043
  try {
2956
3044
  if (fs2.existsSync(configPath)) {
@@ -3160,13 +3248,31 @@ var DashboardServer = class {
3160
3248
  isVirtual: false
3161
3249
  });
3162
3250
  }
3163
- const rootSteps = new Set(model.steps);
3164
- const childSteps = new Set(model.transitions.map((t) => t.to));
3165
- const leafSteps = new Set(model.steps);
3166
- for (const t of model.transitions) {
3167
- }
3168
- nodes.push({ id: "[START]", label: "[START]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
3169
- 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
+ });
3170
3276
  const edges = model.transitions.map((t) => ({
3171
3277
  source: t.from,
3172
3278
  target: t.to,
@@ -3186,7 +3292,10 @@ var DashboardServer = class {
3186
3292
  }
3187
3293
  }
3188
3294
  const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
3189
- 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
+ );
3190
3299
  return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
3191
3300
  }
3192
3301
  /**
@@ -3374,7 +3483,9 @@ if (import_meta.url === `file://${process.argv[1]}`) {
3374
3483
  // src/cli.ts
3375
3484
  var import_meta2 = {};
3376
3485
  var __cliDirname = path3.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url));
3377
- 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;
3378
3489
  function getLanAddress() {
3379
3490
  const interfaces = os.networkInterfaces();
3380
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-E5RJCBK2.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}