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/{chunk-NZFXRZYU.js → chunk-EG254FLY.js} +196 -10
- package/dist/cli.cjs +195 -8
- package/dist/cli.js +1 -1
- package/dist/client/assets/index-DCSWGDzI.js +50 -0
- package/dist/client/assets/{index-CNZqCErb.css → index-DHcSpTgM.css} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/index.cjs +199 -12
- package/dist/index.js +1 -1
- package/dist/server.cjs +199 -12
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/dist/client/assets/index-Cb5C1Pah.js +0 -50
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|