agentflow-dashboard 0.8.0 → 0.8.2

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/server.cjs CHANGED
@@ -36,7 +36,7 @@ var import_node_child_process = require("child_process");
36
36
  var fs3 = __toESM(require("fs"), 1);
37
37
  var import_node_http = require("http");
38
38
  var path3 = __toESM(require("path"), 1);
39
- var import_node_url = require("url");
39
+ var import_node_url2 = require("url");
40
40
 
41
41
  // src/config.ts
42
42
  var import_node_fs = require("fs");
@@ -111,6 +111,7 @@ function getProcessPreference(config) {
111
111
 
112
112
  // src/server.ts
113
113
  var import_agentflow_core3 = require("agentflow-core");
114
+ var import_chokidar2 = __toESM(require("chokidar"), 1);
114
115
  var import_express = __toESM(require("express"), 1);
115
116
  var import_ws = require("ws");
116
117
 
@@ -885,7 +886,7 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
885
886
  ...getSkipFiles(this.userConfig)
886
887
  ]);
887
888
  this.userSkipDirs = new Set(getSkipDirectories(this.userConfig));
888
- this.allWatchDirs = [this.tracesDir, ...this.dataDirs];
889
+ this.allWatchDirs = [...new Set([this.tracesDir, ...this.dataDirs].map((d) => path.resolve(d)))];
889
890
  this.ensureTracesDir();
890
891
  this.loadExistingFiles();
891
892
  this.archiveOldTraces();
@@ -2066,8 +2067,8 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
2066
2067
  return (b.lastModified || b.startTime) - (a.lastModified || a.startTime);
2067
2068
  });
2068
2069
  }
2069
- getTrace(filename) {
2070
- const candidates = [];
2070
+ getTrace(filename, agentId) {
2071
+ let candidates = [];
2071
2072
  const exact = this.traces.get(filename);
2072
2073
  if (exact) candidates.push(exact);
2073
2074
  if (filename.includes("::")) {
@@ -2092,6 +2093,13 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
2092
2093
  }
2093
2094
  if (candidates.length === 0) return void 0;
2094
2095
  if (candidates.length === 1) return candidates[0];
2096
+ if (agentId) {
2097
+ const agentMatches = candidates.filter((c) => c.agentId === agentId);
2098
+ if (agentMatches.length > 0) {
2099
+ candidates = agentMatches;
2100
+ }
2101
+ }
2102
+ if (candidates.length === 1) return candidates[0];
2095
2103
  let best = candidates[0];
2096
2104
  let bestNodeCount = best.nodes instanceof Map ? best.nodes.size : Object.keys(best.nodes ?? {}).length;
2097
2105
  for (let i = 1; i < candidates.length; i++) {
@@ -2149,7 +2157,10 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
2149
2157
  var fs2 = __toESM(require("fs"), 1);
2150
2158
  var os = __toESM(require("os"), 1);
2151
2159
  var path2 = __toESM(require("path"), 1);
2152
- var VERSION = "0.8.0";
2160
+ var import_node_url = require("url");
2161
+ var import_meta = {};
2162
+ var __cliDirname = path2.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
2163
+ var VERSION = JSON.parse(fs2.readFileSync(path2.resolve(__cliDirname, "../package.json"), "utf-8")).version;
2153
2164
  function getLanAddress() {
2154
2165
  const interfaces = os.networkInterfaces();
2155
2166
  for (const name of Object.keys(interfaces)) {
@@ -2189,7 +2200,9 @@ function printBanner(config, traceCount, stats, configPath) {
2189
2200
  \u2192 http://localhost:${port}${isPublic && lan ? `
2190
2201
  \u2192 http://${lan}:${port} (LAN)` : ""}
2191
2202
 
2192
- Views: Agent Profile \xB7 Execution Detail \xB7 Governance
2203
+ Pages: Agents \xB7 SOMA
2204
+ Agent: Profile \xB7 Execution Detail
2205
+ SOMA: Intelligence \xB7 Review \xB7 Policies \xB7 Knowledge \xB7 Activity
2193
2206
  Tabs: Flame Chart \xB7 Agent Flow \xB7 Metrics \xB7 Dependencies
2194
2207
  State Machine \xB7 Summary \xB7 Transcript
2195
2208
 
@@ -2324,8 +2337,8 @@ Examples:
2324
2337
  }
2325
2338
 
2326
2339
  // src/server.ts
2327
- var import_meta = {};
2328
- var __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
2340
+ var import_meta2 = {};
2341
+ var __filename = (0, import_node_url2.fileURLToPath)(import_meta2.url);
2329
2342
  var __dirname = path3.dirname(__filename);
2330
2343
  function serializeTrace(trace) {
2331
2344
  if (!trace) return trace;
@@ -2375,6 +2388,7 @@ var DashboardServer = class {
2375
2388
  this.setupExpress();
2376
2389
  this.setupWebSocket();
2377
2390
  this.setupTraceWatcher();
2391
+ this.setupSomaReportWatcher();
2378
2392
  let knowledgeCount = 0;
2379
2393
  for (const trace of this.watcher.getAllTraces()) {
2380
2394
  this.stats.processTrace(trace);
@@ -2430,6 +2444,10 @@ var DashboardServer = class {
2430
2444
  if (fs3.existsSync(clientDir)) {
2431
2445
  this.app.use(import_express.default.static(clientDir));
2432
2446
  }
2447
+ const pkgVersion = JSON.parse(fs3.readFileSync(path3.resolve(__dirname, "../package.json"), "utf-8")).version;
2448
+ this.app.get("/api/version", (_req, res) => {
2449
+ res.json({ version: pkgVersion });
2450
+ });
2433
2451
  this.app.get("/api/traces", (req, res) => {
2434
2452
  try {
2435
2453
  const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 50, 1), 200);
@@ -2449,7 +2467,8 @@ var DashboardServer = class {
2449
2467
  });
2450
2468
  this.app.get("/api/traces/:filename", (req, res) => {
2451
2469
  try {
2452
- const trace = this.watcher.getTrace(req.params.filename);
2470
+ const agentId = typeof req.query.agent === "string" ? req.query.agent : void 0;
2471
+ const trace = this.watcher.getTrace(req.params.filename, agentId);
2453
2472
  if (!trace) {
2454
2473
  return res.status(404).json({ error: "Trace not found" });
2455
2474
  }
@@ -2733,6 +2752,27 @@ var DashboardServer = class {
2733
2752
  res.status(500).json({ error: "Failed to load agent statistics" });
2734
2753
  }
2735
2754
  });
2755
+ this.app.get("/api/soma/tier", (_req, res) => {
2756
+ const somaVault = this.config.somaVault;
2757
+ if (!somaVault) {
2758
+ return res.json({ tier: "teaser", somaVault: false, governanceAvailable: false });
2759
+ }
2760
+ try {
2761
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
2762
+ if (!fs3.existsSync(reportPath)) {
2763
+ return res.json({ tier: "free", somaVault: true, governanceAvailable: false });
2764
+ }
2765
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2766
+ const hasGovernance = report.governance && typeof report.governance.pending === "number";
2767
+ return res.json({
2768
+ tier: hasGovernance ? "pro" : "free",
2769
+ somaVault: true,
2770
+ governanceAvailable: !!hasGovernance
2771
+ });
2772
+ } catch {
2773
+ return res.json({ tier: "free", somaVault: true, governanceAvailable: false });
2774
+ }
2775
+ });
2736
2776
  this.app.get("/api/soma/report", (_req, res) => {
2737
2777
  const somaVault = this.config.somaVault;
2738
2778
  if (!somaVault) {
@@ -2829,6 +2869,118 @@ var DashboardServer = class {
2829
2869
  res.status(404).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2830
2870
  }
2831
2871
  });
2872
+ this.app.get("/api/soma/policies", (_req, res) => {
2873
+ const somaVault = this.config.somaVault;
2874
+ if (!somaVault) return res.json({ policies: [] });
2875
+ try {
2876
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
2877
+ if (!fs3.existsSync(reportPath)) return res.json({ policies: [] });
2878
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2879
+ res.json({ policies: report.policies ?? [] });
2880
+ } catch {
2881
+ res.json({ policies: [] });
2882
+ }
2883
+ });
2884
+ this.app.post("/api/soma/policies", import_express.default.json(), (req, res) => {
2885
+ var _a;
2886
+ const somaVault = this.config.somaVault;
2887
+ if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2888
+ const { name, enforcement, scope, conditions } = req.body ?? {};
2889
+ if (!name) return res.status(400).json({ error: "name required" });
2890
+ try {
2891
+ const safeName = sanitizeArg(String(name));
2892
+ const safeEnf = sanitizeArg(String(enforcement || "warn"));
2893
+ const safeScope = sanitizeReason(String(scope || "all"));
2894
+ const safeCond = sanitizeReason(String(conditions || ""));
2895
+ const result = (0, import_node_child_process.execSync)(
2896
+ `npx soma policy create "${safeName}" --enforcement ${safeEnf} --scope "${safeScope}" --conditions "${safeCond}" --vault "${somaVault}"`,
2897
+ { encoding: "utf-8", timeout: 1e4 }
2898
+ );
2899
+ res.json({ success: true, message: result.trim() });
2900
+ } catch (error) {
2901
+ res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2902
+ }
2903
+ });
2904
+ this.app.delete("/api/soma/policies/:name", (req, res) => {
2905
+ var _a;
2906
+ const somaVault = this.config.somaVault;
2907
+ if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2908
+ try {
2909
+ const safeName = sanitizeArg(String(req.params.name));
2910
+ const result = (0, import_node_child_process.execSync)(
2911
+ `npx soma policy delete "${safeName}" --vault "${somaVault}"`,
2912
+ { encoding: "utf-8", timeout: 1e4 }
2913
+ );
2914
+ res.json({ success: true, message: result.trim() });
2915
+ } catch (error) {
2916
+ res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2917
+ }
2918
+ });
2919
+ this.app.get("/api/soma/vault/entities", (req, res) => {
2920
+ const somaVault = this.config.somaVault;
2921
+ if (!somaVault) return res.json({ entities: [], total: 0 });
2922
+ try {
2923
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
2924
+ if (!fs3.existsSync(reportPath)) return res.json({ entities: [], total: 0 });
2925
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2926
+ let entities = [
2927
+ ...(report.agents ?? []).map((a) => ({ ...a, type: "agent", id: a.name })),
2928
+ ...(report.insights ?? []).map((i, idx) => {
2929
+ var _a;
2930
+ return { ...i, type: i.type || "insight", id: ((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || `insight-${idx}` };
2931
+ }),
2932
+ ...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
2933
+ ];
2934
+ const { type, layer, q, limit: limitStr, offset: offsetStr } = req.query;
2935
+ if (type) entities = entities.filter((e) => e.type === type);
2936
+ if (layer) entities = entities.filter((e) => e.layer === layer);
2937
+ if (q) {
2938
+ const lq = q.toLowerCase();
2939
+ entities = entities.filter((e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq));
2940
+ }
2941
+ const total = entities.length;
2942
+ const offset = parseInt(offsetStr || "0", 10);
2943
+ const limit = Math.min(parseInt(limitStr || "50", 10), 200);
2944
+ entities = entities.slice(offset, offset + limit);
2945
+ res.json({ entities, total });
2946
+ } catch (error) {
2947
+ console.error("Vault entities error:", error);
2948
+ res.json({ entities: [], total: 0 });
2949
+ }
2950
+ });
2951
+ this.app.get("/api/soma/vault/entities/:type/:id", (req, res) => {
2952
+ const somaVault = this.config.somaVault;
2953
+ if (!somaVault) return res.status(404).json({ error: "Soma vault not configured" });
2954
+ try {
2955
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
2956
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2957
+ const { type, id } = req.params;
2958
+ let entity = null;
2959
+ if (type === "agent") {
2960
+ entity = (report.agents ?? []).find((a) => a.name === id);
2961
+ } else if (type === "policy") {
2962
+ entity = (report.policies ?? []).find((p) => p.name === id);
2963
+ } else {
2964
+ entity = (report.insights ?? []).find(
2965
+ (i) => {
2966
+ var _a;
2967
+ return (((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || "") === id || i.title === id;
2968
+ }
2969
+ );
2970
+ }
2971
+ if (!entity) return res.status(404).json({ error: "Entity not found" });
2972
+ res.json({
2973
+ ...entity,
2974
+ type,
2975
+ id,
2976
+ body: entity.claim || entity.conditions || "",
2977
+ tags: entity.tags ?? [],
2978
+ related: entity.related ?? []
2979
+ });
2980
+ } catch {
2981
+ res.status(404).json({ error: "Entity not found" });
2982
+ }
2983
+ });
2832
2984
  this.app.get("/api/process-health", (_req, res) => {
2833
2985
  var _a, _b;
2834
2986
  try {
@@ -2932,11 +3084,11 @@ var DashboardServer = class {
2932
3084
  }
2933
3085
  } catch {
2934
3086
  }
2935
- const watched = [
3087
+ const watched = [...new Set([
2936
3088
  this.config.tracesDir,
2937
3089
  ...this.config.dataDirs || [],
2938
3090
  ...extraDirs
2939
- ];
3091
+ ].map((w) => path3.resolve(w)))];
2940
3092
  const discovered = [];
2941
3093
  const svcNames = getSystemdServices(this.userConfig);
2942
3094
  if (svcNames.length > 0) {
@@ -3090,6 +3242,41 @@ var DashboardServer = class {
3090
3242
  });
3091
3243
  });
3092
3244
  }
3245
+ /** Watch soma-report.json for changes and broadcast updates via WebSocket. */
3246
+ setupSomaReportWatcher() {
3247
+ const somaVault = this.config.somaVault;
3248
+ if (!somaVault) return;
3249
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
3250
+ const reportDir = path3.dirname(reportPath);
3251
+ if (!fs3.existsSync(reportDir)) return;
3252
+ let debounceTimer = null;
3253
+ const watcher = import_chokidar2.default.watch(reportPath, {
3254
+ ignoreInitial: true,
3255
+ persistent: true,
3256
+ awaitWriteFinish: { stabilityThreshold: 500 }
3257
+ });
3258
+ watcher.on("change", () => {
3259
+ if (debounceTimer) clearTimeout(debounceTimer);
3260
+ debounceTimer = setTimeout(() => {
3261
+ var _a, _b;
3262
+ try {
3263
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
3264
+ this.broadcast({ type: "soma-report-updated", data: report });
3265
+ if (report.generatedAt) {
3266
+ this.broadcast({
3267
+ type: "soma-activity",
3268
+ data: {
3269
+ action: "report-updated",
3270
+ description: `Report updated: ${((_a = report.totals) == null ? void 0 : _a.agents) ?? 0} agents, ${((_b = report.totals) == null ? void 0 : _b.insights) ?? 0} insights`,
3271
+ timestamp: report.generatedAt
3272
+ }
3273
+ });
3274
+ }
3275
+ } catch {
3276
+ }
3277
+ }, 500);
3278
+ });
3279
+ }
3093
3280
  /**
3094
3281
  * Filter an agent's traces to valid ExecutionGraphs and convert via loadGraph().
3095
3282
  * Returns only traces with proper nodes (Map or non-empty object), skipping session-only traces.
@@ -3361,7 +3548,7 @@ var DashboardServer = class {
3361
3548
  return this.watcher.getAllTraces();
3362
3549
  }
3363
3550
  };
3364
- if (import_meta.url === `file://${process.argv[1]}`) {
3551
+ if (import_meta2.url === `file://${process.argv[1]}`) {
3365
3552
  startDashboard().catch(console.error);
3366
3553
  }
3367
3554
  // Annotate the CommonJS export names for ESM import in node:
package/dist/server.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  DashboardServer
3
- } from "./chunk-NZFXRZYU.js";
3
+ } from "./chunk-EG254FLY.js";
4
4
  export {
5
5
  DashboardServer
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentflow-dashboard",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Real-time monitoring dashboard for AgentFlow - Visualize agent execution graphs and performance",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",