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.
@@ -10,7 +10,7 @@ import { execSync } from "child_process";
10
10
  import * as fs3 from "fs";
11
11
  import { createServer } from "http";
12
12
  import * as path3 from "path";
13
- import { fileURLToPath } from "url";
13
+ import { fileURLToPath as fileURLToPath2 } from "url";
14
14
 
15
15
  // src/config.ts
16
16
  import { existsSync, readFileSync } from "fs";
@@ -94,6 +94,7 @@ import {
94
94
  getBottlenecks,
95
95
  loadGraph as loadGraph2
96
96
  } from "agentflow-core";
97
+ import chokidar2 from "chokidar";
97
98
  import express from "express";
98
99
  import { WebSocketServer } from "ws";
99
100
 
@@ -868,7 +869,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
868
869
  ...getSkipFiles(this.userConfig)
869
870
  ]);
870
871
  this.userSkipDirs = new Set(getSkipDirectories(this.userConfig));
871
- this.allWatchDirs = [this.tracesDir, ...this.dataDirs];
872
+ this.allWatchDirs = [...new Set([this.tracesDir, ...this.dataDirs].map((d) => path.resolve(d)))];
872
873
  this.ensureTracesDir();
873
874
  this.loadExistingFiles();
874
875
  this.archiveOldTraces();
@@ -2049,8 +2050,8 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
2049
2050
  return (b.lastModified || b.startTime) - (a.lastModified || a.startTime);
2050
2051
  });
2051
2052
  }
2052
- getTrace(filename) {
2053
- const candidates = [];
2053
+ getTrace(filename, agentId) {
2054
+ let candidates = [];
2054
2055
  const exact = this.traces.get(filename);
2055
2056
  if (exact) candidates.push(exact);
2056
2057
  if (filename.includes("::")) {
@@ -2075,6 +2076,13 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
2075
2076
  }
2076
2077
  if (candidates.length === 0) return void 0;
2077
2078
  if (candidates.length === 1) return candidates[0];
2079
+ if (agentId) {
2080
+ const agentMatches = candidates.filter((c) => c.agentId === agentId);
2081
+ if (agentMatches.length > 0) {
2082
+ candidates = agentMatches;
2083
+ }
2084
+ }
2085
+ if (candidates.length === 1) return candidates[0];
2078
2086
  let best = candidates[0];
2079
2087
  let bestNodeCount = best.nodes instanceof Map ? best.nodes.size : Object.keys(best.nodes ?? {}).length;
2080
2088
  for (let i = 1; i < candidates.length; i++) {
@@ -2132,7 +2140,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
2132
2140
  import * as fs2 from "fs";
2133
2141
  import * as os from "os";
2134
2142
  import * as path2 from "path";
2135
- var VERSION = "0.8.0";
2143
+ import { fileURLToPath } from "url";
2144
+ var __cliDirname = path2.dirname(fileURLToPath(import.meta.url));
2145
+ var VERSION = JSON.parse(fs2.readFileSync(path2.resolve(__cliDirname, "../package.json"), "utf-8")).version;
2136
2146
  function getLanAddress() {
2137
2147
  const interfaces = os.networkInterfaces();
2138
2148
  for (const name of Object.keys(interfaces)) {
@@ -2172,7 +2182,9 @@ function printBanner(config, traceCount, stats, configPath) {
2172
2182
  \u2192 http://localhost:${port}${isPublic && lan ? `
2173
2183
  \u2192 http://${lan}:${port} (LAN)` : ""}
2174
2184
 
2175
- Views: Agent Profile \xB7 Execution Detail \xB7 Governance
2185
+ Pages: Agents \xB7 SOMA
2186
+ Agent: Profile \xB7 Execution Detail
2187
+ SOMA: Intelligence \xB7 Review \xB7 Policies \xB7 Knowledge \xB7 Activity
2176
2188
  Tabs: Flame Chart \xB7 Agent Flow \xB7 Metrics \xB7 Dependencies
2177
2189
  State Machine \xB7 Summary \xB7 Transcript
2178
2190
 
@@ -2307,7 +2319,7 @@ Examples:
2307
2319
  }
2308
2320
 
2309
2321
  // src/server.ts
2310
- var __filename = fileURLToPath(import.meta.url);
2322
+ var __filename = fileURLToPath2(import.meta.url);
2311
2323
  var __dirname = path3.dirname(__filename);
2312
2324
  function serializeTrace(trace) {
2313
2325
  if (!trace) return trace;
@@ -2357,6 +2369,7 @@ var DashboardServer = class {
2357
2369
  this.setupExpress();
2358
2370
  this.setupWebSocket();
2359
2371
  this.setupTraceWatcher();
2372
+ this.setupSomaReportWatcher();
2360
2373
  let knowledgeCount = 0;
2361
2374
  for (const trace of this.watcher.getAllTraces()) {
2362
2375
  this.stats.processTrace(trace);
@@ -2412,6 +2425,10 @@ var DashboardServer = class {
2412
2425
  if (fs3.existsSync(clientDir)) {
2413
2426
  this.app.use(express.static(clientDir));
2414
2427
  }
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
+ });
2415
2432
  this.app.get("/api/traces", (req, res) => {
2416
2433
  try {
2417
2434
  const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 50, 1), 200);
@@ -2431,7 +2448,8 @@ var DashboardServer = class {
2431
2448
  });
2432
2449
  this.app.get("/api/traces/:filename", (req, res) => {
2433
2450
  try {
2434
- const trace = this.watcher.getTrace(req.params.filename);
2451
+ const agentId = typeof req.query.agent === "string" ? req.query.agent : void 0;
2452
+ const trace = this.watcher.getTrace(req.params.filename, agentId);
2435
2453
  if (!trace) {
2436
2454
  return res.status(404).json({ error: "Trace not found" });
2437
2455
  }
@@ -2715,6 +2733,27 @@ var DashboardServer = class {
2715
2733
  res.status(500).json({ error: "Failed to load agent statistics" });
2716
2734
  }
2717
2735
  });
2736
+ this.app.get("/api/soma/tier", (_req, res) => {
2737
+ const somaVault = this.config.somaVault;
2738
+ if (!somaVault) {
2739
+ return res.json({ tier: "teaser", somaVault: false, governanceAvailable: false });
2740
+ }
2741
+ try {
2742
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
2743
+ if (!fs3.existsSync(reportPath)) {
2744
+ return res.json({ tier: "free", somaVault: true, governanceAvailable: false });
2745
+ }
2746
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2747
+ const hasGovernance = report.governance && typeof report.governance.pending === "number";
2748
+ return res.json({
2749
+ tier: hasGovernance ? "pro" : "free",
2750
+ somaVault: true,
2751
+ governanceAvailable: !!hasGovernance
2752
+ });
2753
+ } catch {
2754
+ return res.json({ tier: "free", somaVault: true, governanceAvailable: false });
2755
+ }
2756
+ });
2718
2757
  this.app.get("/api/soma/report", (_req, res) => {
2719
2758
  const somaVault = this.config.somaVault;
2720
2759
  if (!somaVault) {
@@ -2811,6 +2850,118 @@ var DashboardServer = class {
2811
2850
  res.status(404).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2812
2851
  }
2813
2852
  });
2853
+ this.app.get("/api/soma/policies", (_req, res) => {
2854
+ const somaVault = this.config.somaVault;
2855
+ if (!somaVault) return res.json({ policies: [] });
2856
+ try {
2857
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
2858
+ if (!fs3.existsSync(reportPath)) return res.json({ policies: [] });
2859
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2860
+ res.json({ policies: report.policies ?? [] });
2861
+ } catch {
2862
+ res.json({ policies: [] });
2863
+ }
2864
+ });
2865
+ this.app.post("/api/soma/policies", express.json(), (req, res) => {
2866
+ var _a;
2867
+ const somaVault = this.config.somaVault;
2868
+ if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2869
+ const { name, enforcement, scope, conditions } = req.body ?? {};
2870
+ if (!name) return res.status(400).json({ error: "name required" });
2871
+ 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
+ );
2880
+ res.json({ success: true, message: result.trim() });
2881
+ } catch (error) {
2882
+ res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2883
+ }
2884
+ });
2885
+ this.app.delete("/api/soma/policies/:name", (req, res) => {
2886
+ var _a;
2887
+ const somaVault = this.config.somaVault;
2888
+ if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2889
+ 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 }
2894
+ );
2895
+ res.json({ success: true, message: result.trim() });
2896
+ } catch (error) {
2897
+ res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2898
+ }
2899
+ });
2900
+ this.app.get("/api/soma/vault/entities", (req, res) => {
2901
+ const somaVault = this.config.somaVault;
2902
+ if (!somaVault) return res.json({ entities: [], total: 0 });
2903
+ try {
2904
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
2905
+ if (!fs3.existsSync(reportPath)) return res.json({ entities: [], total: 0 });
2906
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2907
+ let entities = [
2908
+ ...(report.agents ?? []).map((a) => ({ ...a, type: "agent", id: a.name })),
2909
+ ...(report.insights ?? []).map((i, idx) => {
2910
+ var _a;
2911
+ return { ...i, type: i.type || "insight", id: ((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || `insight-${idx}` };
2912
+ }),
2913
+ ...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
2914
+ ];
2915
+ const { type, layer, q, limit: limitStr, offset: offsetStr } = req.query;
2916
+ if (type) entities = entities.filter((e) => e.type === type);
2917
+ if (layer) entities = entities.filter((e) => e.layer === layer);
2918
+ if (q) {
2919
+ const lq = q.toLowerCase();
2920
+ entities = entities.filter((e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq));
2921
+ }
2922
+ const total = entities.length;
2923
+ const offset = parseInt(offsetStr || "0", 10);
2924
+ const limit = Math.min(parseInt(limitStr || "50", 10), 200);
2925
+ entities = entities.slice(offset, offset + limit);
2926
+ res.json({ entities, total });
2927
+ } catch (error) {
2928
+ console.error("Vault entities error:", error);
2929
+ res.json({ entities: [], total: 0 });
2930
+ }
2931
+ });
2932
+ this.app.get("/api/soma/vault/entities/:type/:id", (req, res) => {
2933
+ const somaVault = this.config.somaVault;
2934
+ if (!somaVault) return res.status(404).json({ error: "Soma vault not configured" });
2935
+ try {
2936
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
2937
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
2938
+ const { type, id } = req.params;
2939
+ let entity = null;
2940
+ if (type === "agent") {
2941
+ entity = (report.agents ?? []).find((a) => a.name === id);
2942
+ } else if (type === "policy") {
2943
+ entity = (report.policies ?? []).find((p) => p.name === id);
2944
+ } else {
2945
+ entity = (report.insights ?? []).find(
2946
+ (i) => {
2947
+ var _a;
2948
+ return (((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || "") === id || i.title === id;
2949
+ }
2950
+ );
2951
+ }
2952
+ if (!entity) return res.status(404).json({ error: "Entity not found" });
2953
+ res.json({
2954
+ ...entity,
2955
+ type,
2956
+ id,
2957
+ body: entity.claim || entity.conditions || "",
2958
+ tags: entity.tags ?? [],
2959
+ related: entity.related ?? []
2960
+ });
2961
+ } catch {
2962
+ res.status(404).json({ error: "Entity not found" });
2963
+ }
2964
+ });
2814
2965
  this.app.get("/api/process-health", (_req, res) => {
2815
2966
  var _a, _b;
2816
2967
  try {
@@ -2914,11 +3065,11 @@ var DashboardServer = class {
2914
3065
  }
2915
3066
  } catch {
2916
3067
  }
2917
- const watched = [
3068
+ const watched = [...new Set([
2918
3069
  this.config.tracesDir,
2919
3070
  ...this.config.dataDirs || [],
2920
3071
  ...extraDirs
2921
- ];
3072
+ ].map((w) => path3.resolve(w)))];
2922
3073
  const discovered = [];
2923
3074
  const svcNames = getSystemdServices(this.userConfig);
2924
3075
  if (svcNames.length > 0) {
@@ -3072,6 +3223,41 @@ var DashboardServer = class {
3072
3223
  });
3073
3224
  });
3074
3225
  }
3226
+ /** Watch soma-report.json for changes and broadcast updates via WebSocket. */
3227
+ setupSomaReportWatcher() {
3228
+ const somaVault = this.config.somaVault;
3229
+ if (!somaVault) return;
3230
+ const reportPath = path3.join(somaVault, "..", "soma-report.json");
3231
+ const reportDir = path3.dirname(reportPath);
3232
+ if (!fs3.existsSync(reportDir)) return;
3233
+ let debounceTimer = null;
3234
+ const watcher = chokidar2.watch(reportPath, {
3235
+ ignoreInitial: true,
3236
+ persistent: true,
3237
+ awaitWriteFinish: { stabilityThreshold: 500 }
3238
+ });
3239
+ watcher.on("change", () => {
3240
+ if (debounceTimer) clearTimeout(debounceTimer);
3241
+ debounceTimer = setTimeout(() => {
3242
+ var _a, _b;
3243
+ try {
3244
+ const report = JSON.parse(fs3.readFileSync(reportPath, "utf-8"));
3245
+ this.broadcast({ type: "soma-report-updated", data: report });
3246
+ if (report.generatedAt) {
3247
+ this.broadcast({
3248
+ type: "soma-activity",
3249
+ data: {
3250
+ action: "report-updated",
3251
+ description: `Report updated: ${((_a = report.totals) == null ? void 0 : _a.agents) ?? 0} agents, ${((_b = report.totals) == null ? void 0 : _b.insights) ?? 0} insights`,
3252
+ timestamp: report.generatedAt
3253
+ }
3254
+ });
3255
+ }
3256
+ } catch {
3257
+ }
3258
+ }, 500);
3259
+ });
3260
+ }
3075
3261
  /**
3076
3262
  * Filter an agent's traces to valid ExecutionGraphs and convert via loadGraph().
3077
3263
  * Returns only traces with proper nodes (Map or non-empty object), skipping session-only traces.
package/dist/cli.cjs CHANGED
@@ -35,6 +35,7 @@ module.exports = __toCommonJS(cli_exports);
35
35
  var fs3 = __toESM(require("fs"), 1);
36
36
  var os = __toESM(require("os"), 1);
37
37
  var path3 = __toESM(require("path"), 1);
38
+ var import_node_url2 = require("url");
38
39
 
39
40
  // src/server.ts
40
41
  var import_node_child_process = require("child_process");
@@ -116,6 +117,7 @@ function getProcessPreference(config) {
116
117
 
117
118
  // src/server.ts
118
119
  var import_agentflow_core3 = require("agentflow-core");
120
+ var import_chokidar2 = __toESM(require("chokidar"), 1);
119
121
  var import_express = __toESM(require("express"), 1);
120
122
  var import_ws = require("ws");
121
123
 
@@ -890,7 +892,7 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
890
892
  ...getSkipFiles(this.userConfig)
891
893
  ]);
892
894
  this.userSkipDirs = new Set(getSkipDirectories(this.userConfig));
893
- this.allWatchDirs = [this.tracesDir, ...this.dataDirs];
895
+ this.allWatchDirs = [...new Set([this.tracesDir, ...this.dataDirs].map((d) => path.resolve(d)))];
894
896
  this.ensureTracesDir();
895
897
  this.loadExistingFiles();
896
898
  this.archiveOldTraces();
@@ -2071,8 +2073,8 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
2071
2073
  return (b.lastModified || b.startTime) - (a.lastModified || a.startTime);
2072
2074
  });
2073
2075
  }
2074
- getTrace(filename) {
2075
- const candidates = [];
2076
+ getTrace(filename, agentId) {
2077
+ let candidates = [];
2076
2078
  const exact = this.traces.get(filename);
2077
2079
  if (exact) candidates.push(exact);
2078
2080
  if (filename.includes("::")) {
@@ -2097,6 +2099,13 @@ var TraceWatcher = class _TraceWatcher extends import_node_events.EventEmitter {
2097
2099
  }
2098
2100
  if (candidates.length === 0) return void 0;
2099
2101
  if (candidates.length === 1) return candidates[0];
2102
+ if (agentId) {
2103
+ const agentMatches = candidates.filter((c) => c.agentId === agentId);
2104
+ if (agentMatches.length > 0) {
2105
+ candidates = agentMatches;
2106
+ }
2107
+ }
2108
+ if (candidates.length === 1) return candidates[0];
2100
2109
  let best = candidates[0];
2101
2110
  let bestNodeCount = best.nodes instanceof Map ? best.nodes.size : Object.keys(best.nodes ?? {}).length;
2102
2111
  for (let i = 1; i < candidates.length; i++) {
@@ -2202,6 +2211,7 @@ var DashboardServer = class {
2202
2211
  this.setupExpress();
2203
2212
  this.setupWebSocket();
2204
2213
  this.setupTraceWatcher();
2214
+ this.setupSomaReportWatcher();
2205
2215
  let knowledgeCount = 0;
2206
2216
  for (const trace of this.watcher.getAllTraces()) {
2207
2217
  this.stats.processTrace(trace);
@@ -2257,6 +2267,10 @@ var DashboardServer = class {
2257
2267
  if (fs2.existsSync(clientDir)) {
2258
2268
  this.app.use(import_express.default.static(clientDir));
2259
2269
  }
2270
+ const pkgVersion = JSON.parse(fs2.readFileSync(path2.resolve(__dirname, "../package.json"), "utf-8")).version;
2271
+ this.app.get("/api/version", (_req, res) => {
2272
+ res.json({ version: pkgVersion });
2273
+ });
2260
2274
  this.app.get("/api/traces", (req, res) => {
2261
2275
  try {
2262
2276
  const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 50, 1), 200);
@@ -2276,7 +2290,8 @@ var DashboardServer = class {
2276
2290
  });
2277
2291
  this.app.get("/api/traces/:filename", (req, res) => {
2278
2292
  try {
2279
- const trace = this.watcher.getTrace(req.params.filename);
2293
+ const agentId = typeof req.query.agent === "string" ? req.query.agent : void 0;
2294
+ const trace = this.watcher.getTrace(req.params.filename, agentId);
2280
2295
  if (!trace) {
2281
2296
  return res.status(404).json({ error: "Trace not found" });
2282
2297
  }
@@ -2560,6 +2575,27 @@ var DashboardServer = class {
2560
2575
  res.status(500).json({ error: "Failed to load agent statistics" });
2561
2576
  }
2562
2577
  });
2578
+ this.app.get("/api/soma/tier", (_req, res) => {
2579
+ const somaVault = this.config.somaVault;
2580
+ if (!somaVault) {
2581
+ return res.json({ tier: "teaser", somaVault: false, governanceAvailable: false });
2582
+ }
2583
+ try {
2584
+ const reportPath = path2.join(somaVault, "..", "soma-report.json");
2585
+ if (!fs2.existsSync(reportPath)) {
2586
+ return res.json({ tier: "free", somaVault: true, governanceAvailable: false });
2587
+ }
2588
+ const report = JSON.parse(fs2.readFileSync(reportPath, "utf-8"));
2589
+ const hasGovernance = report.governance && typeof report.governance.pending === "number";
2590
+ return res.json({
2591
+ tier: hasGovernance ? "pro" : "free",
2592
+ somaVault: true,
2593
+ governanceAvailable: !!hasGovernance
2594
+ });
2595
+ } catch {
2596
+ return res.json({ tier: "free", somaVault: true, governanceAvailable: false });
2597
+ }
2598
+ });
2563
2599
  this.app.get("/api/soma/report", (_req, res) => {
2564
2600
  const somaVault = this.config.somaVault;
2565
2601
  if (!somaVault) {
@@ -2656,6 +2692,118 @@ var DashboardServer = class {
2656
2692
  res.status(404).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2657
2693
  }
2658
2694
  });
2695
+ this.app.get("/api/soma/policies", (_req, res) => {
2696
+ const somaVault = this.config.somaVault;
2697
+ if (!somaVault) return res.json({ policies: [] });
2698
+ try {
2699
+ const reportPath = path2.join(somaVault, "..", "soma-report.json");
2700
+ if (!fs2.existsSync(reportPath)) return res.json({ policies: [] });
2701
+ const report = JSON.parse(fs2.readFileSync(reportPath, "utf-8"));
2702
+ res.json({ policies: report.policies ?? [] });
2703
+ } catch {
2704
+ res.json({ policies: [] });
2705
+ }
2706
+ });
2707
+ this.app.post("/api/soma/policies", import_express.default.json(), (req, res) => {
2708
+ var _a;
2709
+ const somaVault = this.config.somaVault;
2710
+ if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2711
+ const { name, enforcement, scope, conditions } = req.body ?? {};
2712
+ if (!name) return res.status(400).json({ error: "name required" });
2713
+ try {
2714
+ const safeName = sanitizeArg(String(name));
2715
+ const safeEnf = sanitizeArg(String(enforcement || "warn"));
2716
+ const safeScope = sanitizeReason(String(scope || "all"));
2717
+ const safeCond = sanitizeReason(String(conditions || ""));
2718
+ const result = (0, import_node_child_process.execSync)(
2719
+ `npx soma policy create "${safeName}" --enforcement ${safeEnf} --scope "${safeScope}" --conditions "${safeCond}" --vault "${somaVault}"`,
2720
+ { encoding: "utf-8", timeout: 1e4 }
2721
+ );
2722
+ res.json({ success: true, message: result.trim() });
2723
+ } catch (error) {
2724
+ res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2725
+ }
2726
+ });
2727
+ this.app.delete("/api/soma/policies/:name", (req, res) => {
2728
+ var _a;
2729
+ const somaVault = this.config.somaVault;
2730
+ if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
2731
+ try {
2732
+ const safeName = sanitizeArg(String(req.params.name));
2733
+ const result = (0, import_node_child_process.execSync)(
2734
+ `npx soma policy delete "${safeName}" --vault "${somaVault}"`,
2735
+ { encoding: "utf-8", timeout: 1e4 }
2736
+ );
2737
+ res.json({ success: true, message: result.trim() });
2738
+ } catch (error) {
2739
+ res.status(400).json({ error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message });
2740
+ }
2741
+ });
2742
+ this.app.get("/api/soma/vault/entities", (req, res) => {
2743
+ const somaVault = this.config.somaVault;
2744
+ if (!somaVault) return res.json({ entities: [], total: 0 });
2745
+ try {
2746
+ const reportPath = path2.join(somaVault, "..", "soma-report.json");
2747
+ if (!fs2.existsSync(reportPath)) return res.json({ entities: [], total: 0 });
2748
+ const report = JSON.parse(fs2.readFileSync(reportPath, "utf-8"));
2749
+ let entities = [
2750
+ ...(report.agents ?? []).map((a) => ({ ...a, type: "agent", id: a.name })),
2751
+ ...(report.insights ?? []).map((i, idx) => {
2752
+ var _a;
2753
+ return { ...i, type: i.type || "insight", id: ((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || `insight-${idx}` };
2754
+ }),
2755
+ ...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
2756
+ ];
2757
+ const { type, layer, q, limit: limitStr, offset: offsetStr } = req.query;
2758
+ if (type) entities = entities.filter((e) => e.type === type);
2759
+ if (layer) entities = entities.filter((e) => e.layer === layer);
2760
+ if (q) {
2761
+ const lq = q.toLowerCase();
2762
+ entities = entities.filter((e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq));
2763
+ }
2764
+ const total = entities.length;
2765
+ const offset = parseInt(offsetStr || "0", 10);
2766
+ const limit = Math.min(parseInt(limitStr || "50", 10), 200);
2767
+ entities = entities.slice(offset, offset + limit);
2768
+ res.json({ entities, total });
2769
+ } catch (error) {
2770
+ console.error("Vault entities error:", error);
2771
+ res.json({ entities: [], total: 0 });
2772
+ }
2773
+ });
2774
+ this.app.get("/api/soma/vault/entities/:type/:id", (req, res) => {
2775
+ const somaVault = this.config.somaVault;
2776
+ if (!somaVault) return res.status(404).json({ error: "Soma vault not configured" });
2777
+ try {
2778
+ const reportPath = path2.join(somaVault, "..", "soma-report.json");
2779
+ const report = JSON.parse(fs2.readFileSync(reportPath, "utf-8"));
2780
+ const { type, id } = req.params;
2781
+ let entity = null;
2782
+ if (type === "agent") {
2783
+ entity = (report.agents ?? []).find((a) => a.name === id);
2784
+ } else if (type === "policy") {
2785
+ entity = (report.policies ?? []).find((p) => p.name === id);
2786
+ } else {
2787
+ entity = (report.insights ?? []).find(
2788
+ (i) => {
2789
+ var _a;
2790
+ return (((_a = i.title) == null ? void 0 : _a.replace(/\s+/g, "-").toLowerCase()) || "") === id || i.title === id;
2791
+ }
2792
+ );
2793
+ }
2794
+ if (!entity) return res.status(404).json({ error: "Entity not found" });
2795
+ res.json({
2796
+ ...entity,
2797
+ type,
2798
+ id,
2799
+ body: entity.claim || entity.conditions || "",
2800
+ tags: entity.tags ?? [],
2801
+ related: entity.related ?? []
2802
+ });
2803
+ } catch {
2804
+ res.status(404).json({ error: "Entity not found" });
2805
+ }
2806
+ });
2659
2807
  this.app.get("/api/process-health", (_req, res) => {
2660
2808
  var _a, _b;
2661
2809
  try {
@@ -2759,11 +2907,11 @@ var DashboardServer = class {
2759
2907
  }
2760
2908
  } catch {
2761
2909
  }
2762
- const watched = [
2910
+ const watched = [...new Set([
2763
2911
  this.config.tracesDir,
2764
2912
  ...this.config.dataDirs || [],
2765
2913
  ...extraDirs
2766
- ];
2914
+ ].map((w) => path2.resolve(w)))];
2767
2915
  const discovered = [];
2768
2916
  const svcNames = getSystemdServices(this.userConfig);
2769
2917
  if (svcNames.length > 0) {
@@ -2917,6 +3065,41 @@ var DashboardServer = class {
2917
3065
  });
2918
3066
  });
2919
3067
  }
3068
+ /** Watch soma-report.json for changes and broadcast updates via WebSocket. */
3069
+ setupSomaReportWatcher() {
3070
+ const somaVault = this.config.somaVault;
3071
+ if (!somaVault) return;
3072
+ const reportPath = path2.join(somaVault, "..", "soma-report.json");
3073
+ const reportDir = path2.dirname(reportPath);
3074
+ if (!fs2.existsSync(reportDir)) return;
3075
+ let debounceTimer = null;
3076
+ const watcher = import_chokidar2.default.watch(reportPath, {
3077
+ ignoreInitial: true,
3078
+ persistent: true,
3079
+ awaitWriteFinish: { stabilityThreshold: 500 }
3080
+ });
3081
+ watcher.on("change", () => {
3082
+ if (debounceTimer) clearTimeout(debounceTimer);
3083
+ debounceTimer = setTimeout(() => {
3084
+ var _a, _b;
3085
+ try {
3086
+ const report = JSON.parse(fs2.readFileSync(reportPath, "utf-8"));
3087
+ this.broadcast({ type: "soma-report-updated", data: report });
3088
+ if (report.generatedAt) {
3089
+ this.broadcast({
3090
+ type: "soma-activity",
3091
+ data: {
3092
+ action: "report-updated",
3093
+ description: `Report updated: ${((_a = report.totals) == null ? void 0 : _a.agents) ?? 0} agents, ${((_b = report.totals) == null ? void 0 : _b.insights) ?? 0} insights`,
3094
+ timestamp: report.generatedAt
3095
+ }
3096
+ });
3097
+ }
3098
+ } catch {
3099
+ }
3100
+ }, 500);
3101
+ });
3102
+ }
2920
3103
  /**
2921
3104
  * Filter an agent's traces to valid ExecutionGraphs and convert via loadGraph().
2922
3105
  * Returns only traces with proper nodes (Map or non-empty object), skipping session-only traces.
@@ -3193,7 +3376,9 @@ if (import_meta.url === `file://${process.argv[1]}`) {
3193
3376
  }
3194
3377
 
3195
3378
  // src/cli.ts
3196
- var VERSION = "0.8.0";
3379
+ var import_meta2 = {};
3380
+ var __cliDirname = path3.dirname((0, import_node_url2.fileURLToPath)(import_meta2.url));
3381
+ var VERSION = JSON.parse(fs3.readFileSync(path3.resolve(__cliDirname, "../package.json"), "utf-8")).version;
3197
3382
  function getLanAddress() {
3198
3383
  const interfaces = os.networkInterfaces();
3199
3384
  for (const name of Object.keys(interfaces)) {
@@ -3233,7 +3418,9 @@ function printBanner(config, traceCount, stats, configPath) {
3233
3418
  \u2192 http://localhost:${port}${isPublic && lan ? `
3234
3419
  \u2192 http://${lan}:${port} (LAN)` : ""}
3235
3420
 
3236
- Views: Agent Profile \xB7 Execution Detail \xB7 Governance
3421
+ Pages: Agents \xB7 SOMA
3422
+ Agent: Profile \xB7 Execution Detail
3423
+ SOMA: Intelligence \xB7 Review \xB7 Policies \xB7 Knowledge \xB7 Activity
3237
3424
  Tabs: Flame Chart \xB7 Agent Flow \xB7 Metrics \xB7 Dependencies
3238
3425
  State Machine \xB7 Summary \xB7 Transcript
3239
3426
 
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startDashboard
3
- } from "./chunk-NZFXRZYU.js";
3
+ } from "./chunk-EG254FLY.js";
4
4
  export {
5
5
  startDashboard
6
6
  };