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.
@@ -1,16 +1,23 @@
1
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
- }) : x)(function(x) {
4
- if (typeof require !== "undefined") return require.apply(this, arguments);
5
- throw Error('Dynamic require of "' + x + '" is not supported');
6
- });
7
-
8
1
  // src/server.ts
9
- import { execSync } from "child_process";
2
+ import { execFileSync, execSync } from "child_process";
10
3
  import * as fs3 from "fs";
11
4
  import { createServer } from "http";
12
5
  import * as path3 from "path";
13
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
+ import {
8
+ auditProcesses,
9
+ createExecutionEvent,
10
+ createKnowledgeStore,
11
+ discoverAllProcessConfigs,
12
+ discoverProcess,
13
+ findVariants,
14
+ getBottlenecks,
15
+ loadGraph as loadGraph2
16
+ } from "agentflow-core";
17
+ import chokidar2 from "chokidar";
18
+ import express from "express";
19
+ import rateLimit from "express-rate-limit";
20
+ import { WebSocketServer } from "ws";
14
21
 
15
22
  // src/config.ts
16
23
  import { existsSync, readFileSync } from "fs";
@@ -83,21 +90,6 @@ function getProcessPreference(config) {
83
90
  return config.processPreference ?? null;
84
91
  }
85
92
 
86
- // src/server.ts
87
- import {
88
- auditProcesses,
89
- createExecutionEvent,
90
- createKnowledgeStore,
91
- discoverAllProcessConfigs,
92
- discoverProcess,
93
- findVariants,
94
- getBottlenecks,
95
- loadGraph as loadGraph2
96
- } from "agentflow-core";
97
- import chokidar2 from "chokidar";
98
- import express from "express";
99
- import { WebSocketServer } from "ws";
100
-
101
93
  // src/adapters/agentflow.ts
102
94
  var SKIP_FILES = /* @__PURE__ */ new Set([
103
95
  "workers.json",
@@ -109,7 +101,14 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
109
101
  "models.json",
110
102
  "config.json"
111
103
  ]);
112
- var SKIP_SUFFIXES = ["-state.json", "-config.json", "-watch-state.json", ".tmp", ".bak", ".backup"];
104
+ var SKIP_SUFFIXES = [
105
+ "-state.json",
106
+ "-config.json",
107
+ "-watch-state.json",
108
+ ".tmp",
109
+ ".bak",
110
+ ".backup"
111
+ ];
113
112
  var AgentFlowAdapter = class {
114
113
  name = "agentflow";
115
114
  detect(_dirPath) {
@@ -234,7 +233,7 @@ var OpenClawAdapter = class {
234
233
  };
235
234
 
236
235
  // src/adapters/otel.ts
237
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync } from "fs";
236
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
238
237
  import { join as join3 } from "path";
239
238
  var SPAN_TYPE_MAP = {
240
239
  "gen_ai.chat": "llm",
@@ -386,8 +385,14 @@ registerAdapter(new AgentFlowAdapter());
386
385
  var PURPOSE_KEYWORDS = [
387
386
  { keywords: ["email", "mail", "inbox", "smtp"], group: "Email Processors" },
388
387
  { keywords: ["monitor", "watch", "alert", "surveillance"], group: "Monitors" },
389
- { keywords: ["digest", "newsletter", "summary", "report", "briefing"], group: "Digests & Reports" },
390
- { keywords: ["curator", "janitor", "distiller", "surveyor", "worker", "indexer"], group: "Workers" },
388
+ {
389
+ keywords: ["digest", "newsletter", "summary", "report", "briefing"],
390
+ group: "Digests & Reports"
391
+ },
392
+ {
393
+ keywords: ["curator", "janitor", "distiller", "surveyor", "worker", "indexer"],
394
+ group: "Workers"
395
+ },
391
396
  { keywords: ["cron", "schedule", "timer", "periodic"], group: "Scheduled Jobs" },
392
397
  { keywords: ["search", "scrape", "crawl", "fetch"], group: "Data Collection" },
393
398
  { keywords: ["embed", "vector", "index"], group: "Embeddings" }
@@ -419,6 +424,7 @@ function capitalize(s) {
419
424
  return s.charAt(0).toUpperCase() + s.slice(1);
420
425
  }
421
426
  function deduplicateAgents(agents) {
427
+ var _a, _b, _c, _d;
422
428
  const tagged = agents.map((a) => ({
423
429
  ...a,
424
430
  ...extractSource(a.agentId)
@@ -435,14 +441,14 @@ function deduplicateAgents(agents) {
435
441
  const mergedIds = /* @__PURE__ */ new Set();
436
442
  const mergedAgents = [];
437
443
  for (const [_key, group] of suffixGroups) {
438
- const suffix = extractSuffix(group[0].localId);
444
+ const suffix = extractSuffix((_a = group[0]) == null ? void 0 : _a.localId);
439
445
  if (group.length < 2) continue;
440
446
  const prefixes = new Set(group.map((a) => a.localId.split("-")[0]));
441
447
  if (prefixes.size < 2) continue;
442
448
  const longPrefixes = [...prefixes].filter((p) => p !== suffix && p.length > 2);
443
449
  if (longPrefixes.length >= 2) continue;
444
450
  const merged = {
445
- agentId: group[0].source === "agentflow" ? suffix : `${group[0].source}:${suffix}`,
451
+ agentId: ((_b = group[0]) == null ? void 0 : _b.source) === "agentflow" ? suffix : `${(_c = group[0]) == null ? void 0 : _c.source}:${suffix}`,
446
452
  displayName: suffix,
447
453
  totalExecutions: group.reduce((s, a) => s + a.totalExecutions, 0),
448
454
  successfulExecutions: group.reduce((s, a) => s + a.successfulExecutions, 0),
@@ -453,7 +459,7 @@ function deduplicateAgents(agents) {
453
459
  triggers: {},
454
460
  recentActivity: group.flatMap((a) => a.recentActivity).sort((a, b) => b.timestamp - a.timestamp).slice(0, 50),
455
461
  sources: group.map((a) => a.agentId),
456
- adapterSource: group[0].source
462
+ adapterSource: (_d = group[0]) == null ? void 0 : _d.source
457
463
  };
458
464
  merged.successRate = merged.totalExecutions > 0 ? merged.successfulExecutions / merged.totalExecutions * 100 : 0;
459
465
  const totalExecTime = group.reduce((s, a) => s + a.avgExecutionTime * a.totalExecutions, 0);
@@ -869,7 +875,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
869
875
  ...getSkipFiles(this.userConfig)
870
876
  ]);
871
877
  this.userSkipDirs = new Set(getSkipDirectories(this.userConfig));
872
- this.allWatchDirs = [...new Set([this.tracesDir, ...this.dataDirs].map((d) => path.resolve(d)))];
878
+ this.allWatchDirs = [
879
+ ...new Set([this.tracesDir, ...this.dataDirs].map((d) => path.resolve(d)))
880
+ ];
873
881
  this.ensureTracesDir();
874
882
  this.loadExistingFiles();
875
883
  this.archiveOldTraces();
@@ -879,7 +887,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
879
887
  /** Move trace files older than maxAgeMs into archive/YYYY-MM/ subdirectories. */
880
888
  archiveOldTraces() {
881
889
  const cutoff = Date.now() - this.maxAgeMs;
882
- let archived = 0;
890
+ const _archived = 0;
883
891
  for (const dir of this.allWatchDirs) {
884
892
  if (!fs.existsSync(dir)) continue;
885
893
  try {
@@ -896,7 +904,8 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
896
904
  try {
897
905
  const entries = fs.readdirSync(dir, { withFileTypes: true });
898
906
  for (const entry of entries) {
899
- if (entry.name.startsWith(".") || entry.name === "archive" || this.userSkipDirs.has(entry.name)) continue;
907
+ if (entry.name.startsWith(".") || entry.name === "archive" || this.userSkipDirs.has(entry.name))
908
+ continue;
900
909
  const fullPath = path.join(dir, entry.name);
901
910
  if (entry.isDirectory()) {
902
911
  archived += this.archiveDirectory(fullPath, cutoff, depth + 1);
@@ -2142,7 +2151,9 @@ import * as os from "os";
2142
2151
  import * as path2 from "path";
2143
2152
  import { fileURLToPath } from "url";
2144
2153
  var __cliDirname = path2.dirname(fileURLToPath(import.meta.url));
2145
- var VERSION = JSON.parse(fs2.readFileSync(path2.resolve(__cliDirname, "../package.json"), "utf-8")).version;
2154
+ var VERSION = JSON.parse(
2155
+ fs2.readFileSync(path2.resolve(__cliDirname, "../package.json"), "utf-8")
2156
+ ).version;
2146
2157
  function getLanAddress() {
2147
2158
  const interfaces = os.networkInterfaces();
2148
2159
  for (const name of Object.keys(interfaces)) {
@@ -2399,6 +2410,17 @@ var DashboardServer = class {
2399
2410
  userConfig;
2400
2411
  configPath;
2401
2412
  setupExpress() {
2413
+ this.app.use(
2414
+ "/api/",
2415
+ rateLimit({
2416
+ windowMs: 60 * 1e3,
2417
+ // 1 minute
2418
+ max: 300,
2419
+ // 300 requests per minute per IP
2420
+ standardHeaders: true,
2421
+ legacyHeaders: false
2422
+ })
2423
+ );
2402
2424
  if (this.config.enableCors) {
2403
2425
  this.app.use((_req, res, next) => {
2404
2426
  res.header("Access-Control-Allow-Origin", "*");
@@ -2425,10 +2447,6 @@ var DashboardServer = class {
2425
2447
  if (fs3.existsSync(clientDir)) {
2426
2448
  this.app.use(express.static(clientDir));
2427
2449
  }
2428
- const pkgVersion = JSON.parse(fs3.readFileSync(path3.resolve(__dirname, "../package.json"), "utf-8")).version;
2429
- this.app.get("/api/version", (_req, res) => {
2430
- res.json({ version: pkgVersion });
2431
- });
2432
2450
  this.app.get("/api/traces", (req, res) => {
2433
2451
  try {
2434
2452
  const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 50, 1), 200);
@@ -2645,6 +2663,7 @@ var DashboardServer = class {
2645
2663
  }
2646
2664
  });
2647
2665
  this.app.get("/api/process-model/:agentId", (req, res) => {
2666
+ var _a, _b;
2648
2667
  try {
2649
2668
  const agentId = req.params.agentId;
2650
2669
  const allTraces = this.watcher.getTracesByAgent(agentId);
@@ -2662,8 +2681,8 @@ var DashboardServer = class {
2662
2681
  const nodeArr = Object.values(nodes);
2663
2682
  const sorted = nodeArr.filter((n) => n.name && typeof n.startTime === "number" && n.startTime > 0).sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
2664
2683
  for (let i = 0; i < sorted.length - 1; i++) {
2665
- const from = sorted[i].name;
2666
- const to = sorted[i + 1].name;
2684
+ const from = (_a = sorted[i]) == null ? void 0 : _a.name;
2685
+ const to = (_b = sorted[i + 1]) == null ? void 0 : _b.name;
2667
2686
  const key = `${from}|||${to}`;
2668
2687
  transMap.set(key, (transMap.get(key) ?? 0) + 1);
2669
2688
  }
@@ -2762,7 +2781,11 @@ var DashboardServer = class {
2762
2781
  try {
2763
2782
  const reportPath = path3.join(somaVault, "..", "soma-report.json");
2764
2783
  if (!fs3.existsSync(reportPath)) {
2765
- return res.json({ available: false, teaser: false, message: "No report file yet. Run soma watch." });
2784
+ return res.json({
2785
+ available: false,
2786
+ teaser: false,
2787
+ message: "No report file yet. Run soma watch."
2788
+ });
2766
2789
  }
2767
2790
  const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2768
2791
  res.json(report);
@@ -2786,7 +2809,9 @@ var DashboardServer = class {
2786
2809
  available: true,
2787
2810
  layers: report.layers ?? { archive: 0, working: 0, emerging: 0, canon: 0 },
2788
2811
  governance: report.governance ?? { pending: 0, promoted: 0, rejected: 0 },
2789
- insights: (report.insights ?? []).filter((i) => i.layer === "emerging" && i.proposal_status === "pending"),
2812
+ insights: (report.insights ?? []).filter(
2813
+ (i) => i.layer === "emerging" && i.proposal_status === "pending"
2814
+ ),
2790
2815
  canon: (report.insights ?? []).filter((i) => i.layer === "canon"),
2791
2816
  generatedAt: report.generatedAt
2792
2817
  });
@@ -2795,21 +2820,23 @@ var DashboardServer = class {
2795
2820
  res.status(500).json({ available: false, message: "Failed to read governance data" });
2796
2821
  }
2797
2822
  });
2798
- const sanitizeArg = (s) => s.replace(/[^a-zA-Z0-9_\-.:]/g, "");
2799
- const sanitizeReason = (s) => s.replace(/["`$\\]/g, "").slice(0, 500);
2823
+ const isValidId = (s) => /^[a-zA-Z0-9_\-.:]+$/.test(s);
2800
2824
  this.app.post("/api/soma/governance/promote", (req, res) => {
2801
2825
  var _a;
2802
2826
  const somaVault = this.config.somaVault;
2803
2827
  if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2804
2828
  const { entryId } = req.body ?? {};
2805
- if (!entryId) return res.status(400).json({ error: "entryId required" });
2829
+ if (!entryId || !isValidId(String(entryId)))
2830
+ return res.status(400).json({ error: "Invalid entryId" });
2806
2831
  try {
2807
- const { execSync: execSync2 } = __require("child_process");
2808
- const safeId = sanitizeArg(String(entryId));
2809
- const result = execSync2(`npx soma governance promote ${safeId} --vault "${somaVault}"`, {
2810
- encoding: "utf-8",
2811
- timeout: 1e4
2812
- });
2832
+ const result = execFileSync(
2833
+ "npx",
2834
+ ["soma", "governance", "promote", String(entryId), "--vault", somaVault],
2835
+ {
2836
+ encoding: "utf-8",
2837
+ timeout: 1e4
2838
+ }
2839
+ );
2813
2840
  res.json({ success: true, message: result.trim() });
2814
2841
  } catch (error) {
2815
2842
  res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
@@ -2820,15 +2847,27 @@ var DashboardServer = class {
2820
2847
  const somaVault = this.config.somaVault;
2821
2848
  if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2822
2849
  const { entryId, reason } = req.body ?? {};
2823
- if (!entryId || !reason) return res.status(400).json({ error: "entryId and reason required" });
2850
+ if (!entryId || !isValidId(String(entryId)))
2851
+ return res.status(400).json({ error: "Invalid entryId" });
2852
+ if (!reason || typeof reason !== "string")
2853
+ return res.status(400).json({ error: "reason required" });
2824
2854
  try {
2825
- const { execSync: execSync2 } = __require("child_process");
2826
- const safeId = sanitizeArg(String(entryId));
2827
- const safeReason = sanitizeReason(String(reason));
2828
- const result = execSync2(`npx soma governance reject ${safeId} "${safeReason}" --vault "${somaVault}"`, {
2829
- encoding: "utf-8",
2830
- timeout: 1e4
2831
- });
2855
+ const result = execFileSync(
2856
+ "npx",
2857
+ [
2858
+ "soma",
2859
+ "governance",
2860
+ "reject",
2861
+ String(entryId),
2862
+ String(reason).slice(0, 500),
2863
+ "--vault",
2864
+ somaVault
2865
+ ],
2866
+ {
2867
+ encoding: "utf-8",
2868
+ timeout: 1e4
2869
+ }
2870
+ );
2832
2871
  res.json({ success: true, message: result.trim() });
2833
2872
  } catch (error) {
2834
2873
  res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
@@ -2838,13 +2877,16 @@ var DashboardServer = class {
2838
2877
  var _a;
2839
2878
  const somaVault = this.config.somaVault;
2840
2879
  if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2880
+ if (!isValidId(String(req.params.id))) return res.status(400).json({ error: "Invalid id" });
2841
2881
  try {
2842
- const { execSync: execSync2 } = __require("child_process");
2843
- const safeId = sanitizeArg(String(req.params.id));
2844
- const result = execSync2(`npx soma governance show ${safeId} --vault "${somaVault}"`, {
2845
- encoding: "utf-8",
2846
- timeout: 1e4
2847
- });
2882
+ const result = execFileSync(
2883
+ "npx",
2884
+ ["soma", "governance", "show", String(req.params.id), "--vault", somaVault],
2885
+ {
2886
+ encoding: "utf-8",
2887
+ timeout: 1e4
2888
+ }
2889
+ );
2848
2890
  res.json({ available: true, output: result.trim() });
2849
2891
  } catch (error) {
2850
2892
  res.status(404).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
@@ -2867,16 +2909,24 @@ var DashboardServer = class {
2867
2909
  const somaVault = this.config.somaVault;
2868
2910
  if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2869
2911
  const { name, enforcement, scope, conditions } = req.body ?? {};
2870
- if (!name) return res.status(400).json({ error: "name required" });
2912
+ if (!name || !isValidId(String(name)))
2913
+ return res.status(400).json({ error: "Invalid policy name" });
2914
+ const enf = String(enforcement || "warn");
2915
+ if (!isValidId(enf)) return res.status(400).json({ error: "Invalid enforcement value" });
2871
2916
  try {
2872
- const safeName = sanitizeArg(String(name));
2873
- const safeEnf = sanitizeArg(String(enforcement || "warn"));
2874
- const safeScope = sanitizeReason(String(scope || "all"));
2875
- const safeCond = sanitizeReason(String(conditions || ""));
2876
- const result = execSync(
2877
- `npx soma policy create "${safeName}" --enforcement ${safeEnf} --scope "${safeScope}" --conditions "${safeCond}" --vault "${somaVault}"`,
2878
- { encoding: "utf-8", timeout: 1e4 }
2879
- );
2917
+ const args = [
2918
+ "soma",
2919
+ "policy",
2920
+ "create",
2921
+ String(name),
2922
+ "--enforcement",
2923
+ enf,
2924
+ "--vault",
2925
+ somaVault
2926
+ ];
2927
+ if (scope) args.push("--scope", String(scope).slice(0, 500));
2928
+ if (conditions) args.push("--conditions", String(conditions).slice(0, 500));
2929
+ const result = execFileSync("npx", args, { encoding: "utf-8", timeout: 1e4 });
2880
2930
  res.json({ success: true, message: result.trim() });
2881
2931
  } catch (error) {
2882
2932
  res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
@@ -2886,11 +2936,16 @@ var DashboardServer = class {
2886
2936
  var _a;
2887
2937
  const somaVault = this.config.somaVault;
2888
2938
  if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2939
+ if (!isValidId(String(req.params.name)))
2940
+ return res.status(400).json({ error: "Invalid policy name" });
2889
2941
  try {
2890
- const safeName = sanitizeArg(String(req.params.name));
2891
- const result = execSync(
2892
- `npx soma policy delete "${safeName}" --vault "${somaVault}"`,
2893
- { encoding: "utf-8", timeout: 1e4 }
2942
+ const result = execFileSync(
2943
+ "npx",
2944
+ ["soma", "policy", "delete", String(req.params.name), "--vault", somaVault],
2945
+ {
2946
+ encoding: "utf-8",
2947
+ timeout: 1e4
2948
+ }
2894
2949
  );
2895
2950
  res.json({ success: true, message: result.trim() });
2896
2951
  } catch (error) {
@@ -2908,16 +2963,28 @@ var DashboardServer = class {
2908
2963
  ...(report.agents ?? []).map((a) => ({ ...a, type: "agent", id: a.name })),
2909
2964
  ...(report.insights ?? []).map((i, idx) => {
2910
2965
  var _a;
2911
- return { ...i, type: i.type || "insight", id: ((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || `insight-${idx}` };
2966
+ return {
2967
+ ...i,
2968
+ type: i.type || "insight",
2969
+ id: ((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || `insight-${idx}`
2970
+ };
2912
2971
  }),
2913
2972
  ...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
2914
2973
  ];
2915
- const { type, layer, q, limit: limitStr, offset: offsetStr } = req.query;
2974
+ const {
2975
+ type,
2976
+ layer,
2977
+ q,
2978
+ limit: limitStr,
2979
+ offset: offsetStr
2980
+ } = req.query;
2916
2981
  if (type) entities = entities.filter((e) => e.type === type);
2917
2982
  if (layer) entities = entities.filter((e) => e.layer === layer);
2918
2983
  if (q) {
2919
2984
  const lq = q.toLowerCase();
2920
- entities = entities.filter((e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq));
2985
+ entities = entities.filter(
2986
+ (e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq)
2987
+ );
2921
2988
  }
2922
2989
  const total = entities.length;
2923
2990
  const offset = parseInt(offsetStr || "0", 10);
@@ -3008,9 +3075,7 @@ var DashboardServer = class {
3008
3075
  const orphans = uniqueProcesses.filter(
3009
3076
  (p) => !allKnownPids.has(p.pid) && p.pid !== process.pid && p.pid !== process.ppid
3010
3077
  );
3011
- const problems = services.flatMap(
3012
- (s) => s.audit.problems.map((p) => `[${s.name}] ${p}`)
3013
- );
3078
+ const problems = services.flatMap((s) => s.audit.problems.map((p) => `[${s.name}] ${p}`));
3014
3079
  const result = {
3015
3080
  // Backward-compatible fields from primary service
3016
3081
  pidFile: (primary == null ? void 0 : primary.audit.pidFile) ?? null,
@@ -3065,19 +3130,24 @@ var DashboardServer = class {
3065
3130
  }
3066
3131
  } catch {
3067
3132
  }
3068
- const watched = [...new Set([
3069
- this.config.tracesDir,
3070
- ...this.config.dataDirs || [],
3071
- ...extraDirs
3072
- ].map((w) => path3.resolve(w)))];
3133
+ const watched = [
3134
+ ...new Set(
3135
+ [this.config.tracesDir, ...this.config.dataDirs || [], ...extraDirs].map(
3136
+ (w) => path3.resolve(w)
3137
+ )
3138
+ )
3139
+ ];
3073
3140
  const discovered = [];
3074
3141
  const svcNames = getSystemdServices(this.userConfig);
3075
3142
  if (svcNames.length > 0) {
3076
3143
  try {
3077
- const { execSync: execSync2 } = __require("child_process");
3078
- const raw = execSync2(
3079
- `systemctl --user show --property=ExecStart --no-pager ${svcNames.join(" ")} 2>/dev/null`,
3080
- { encoding: "utf8", timeout: 5e3 }
3144
+ const raw = execFileSync(
3145
+ "systemctl",
3146
+ ["--user", "show", "--property=ExecStart", "--no-pager", ...svcNames],
3147
+ {
3148
+ encoding: "utf8",
3149
+ timeout: 5e3
3150
+ }
3081
3151
  );
3082
3152
  for (const line of raw.split("\n")) {
3083
3153
  const match = line.match(/path=([^\s;]+)/);
@@ -3109,10 +3179,19 @@ var DashboardServer = class {
3109
3179
  this.app.post("/api/directories", express.json(), (req, res) => {
3110
3180
  try {
3111
3181
  const { add, remove } = req.body;
3112
- if (add && !fs3.existsSync(add)) {
3113
- return res.status(400).json({ error: `Directory does not exist: ${add}` });
3182
+ if (add) {
3183
+ const resolved = path3.resolve(add);
3184
+ if (resolved !== add || add.includes("..")) {
3185
+ return res.status(400).json({ error: "Invalid directory path" });
3186
+ }
3187
+ if (!fs3.existsSync(resolved)) {
3188
+ return res.status(400).json({ error: `Directory does not exist: ${add}` });
3189
+ }
3114
3190
  }
3115
- const configPath = path3.join(process.env.HOME ?? "/home/trader", ".agentflow/dashboard-config.json");
3191
+ const configPath = path3.join(
3192
+ process.env.HOME ?? "/home/trader",
3193
+ ".agentflow/dashboard-config.json"
3194
+ );
3116
3195
  let config = {};
3117
3196
  try {
3118
3197
  if (fs3.existsSync(configPath)) {
@@ -3322,13 +3401,31 @@ var DashboardServer = class {
3322
3401
  isVirtual: false
3323
3402
  });
3324
3403
  }
3325
- const rootSteps = new Set(model.steps);
3326
- const childSteps = new Set(model.transitions.map((t) => t.to));
3327
- const leafSteps = new Set(model.steps);
3328
- for (const t of model.transitions) {
3329
- }
3330
- nodes.push({ id: "[START]", label: "[START]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
3331
- nodes.push({ id: "[END]", label: "[END]", count: model.totalGraphs, frequency: 1, avgDuration: 0, failRate: 0, p95Duration: 0, isVirtual: true });
3404
+ const _rootSteps = new Set(model.steps);
3405
+ const _childSteps = new Set(model.transitions.map((t) => t.to));
3406
+ const _leafSteps = new Set(model.steps);
3407
+ for (const _t of model.transitions) {
3408
+ }
3409
+ nodes.push({
3410
+ id: "[START]",
3411
+ label: "[START]",
3412
+ count: model.totalGraphs,
3413
+ frequency: 1,
3414
+ avgDuration: 0,
3415
+ failRate: 0,
3416
+ p95Duration: 0,
3417
+ isVirtual: true
3418
+ });
3419
+ nodes.push({
3420
+ id: "[END]",
3421
+ label: "[END]",
3422
+ count: model.totalGraphs,
3423
+ frequency: 1,
3424
+ avgDuration: 0,
3425
+ failRate: 0,
3426
+ p95Duration: 0,
3427
+ isVirtual: true
3428
+ });
3332
3429
  const edges = model.transitions.map((t) => ({
3333
3430
  source: t.from,
3334
3431
  target: t.to,
@@ -3348,7 +3445,10 @@ var DashboardServer = class {
3348
3445
  }
3349
3446
  }
3350
3447
  const maxEdgeCount = Math.max(...edges.map((e) => e.count), 1);
3351
- const maxNodeCount = Math.max(...nodes.filter((n) => !n.isVirtual).map((n) => n.count), 1);
3448
+ const maxNodeCount = Math.max(
3449
+ ...nodes.filter((n) => !n.isVirtual).map((n) => n.count),
3450
+ 1
3451
+ );
3352
3452
  return { agentId, totalTraces: model.totalGraphs, nodes, edges, maxEdgeCount, maxNodeCount };
3353
3453
  }
3354
3454
  /**