agentflow-dashboard 0.8.3 → 0.9.0
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-3RG5ZIWI.js +10 -0
- package/dist/chunk-LZXEKWRO.js +1220 -0
- package/dist/{chunk-E5RJCBK2.js → chunk-NEERJBUV.js} +1866 -361
- package/dist/cli.cjs +6287 -374
- package/dist/cli.js +2 -1
- package/dist/client/assets/index-DgJkzB6k.css +1 -0
- package/dist/client/assets/index-c7yJZALo.js +10 -0
- package/dist/client/index.html +2 -2
- package/dist/dist-H4QMTTY3.js +3223 -0
- package/dist/index.cjs +6287 -374
- package/dist/index.js +2 -1
- package/dist/ops-intel-PL6GGIKL.js +31 -0
- package/dist/server.cjs +6287 -374
- package/dist/server.js +2 -1
- package/package.json +26 -8
- package/dist/client/assets/index-BgEw2MGK.js +0 -50
- package/dist/client/assets/index-DHcSpTgM.css +0 -1
|
@@ -1,16 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
}
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-3RG5ZIWI.js";
|
|
7
4
|
|
|
8
5
|
// src/server.ts
|
|
9
|
-
import { execSync } from "child_process";
|
|
10
|
-
import * as
|
|
6
|
+
import { execFileSync, execSync } from "child_process";
|
|
7
|
+
import * as fs4 from "fs";
|
|
11
8
|
import { createServer } from "http";
|
|
12
|
-
import * as
|
|
9
|
+
import * as path4 from "path";
|
|
13
10
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11
|
+
import {
|
|
12
|
+
auditProcesses,
|
|
13
|
+
createExecutionEvent,
|
|
14
|
+
createKnowledgeStore,
|
|
15
|
+
discoverAllProcessConfigs,
|
|
16
|
+
discoverProcess,
|
|
17
|
+
findVariants,
|
|
18
|
+
getBottlenecks,
|
|
19
|
+
loadGraph as loadGraph2,
|
|
20
|
+
toReceipt
|
|
21
|
+
} from "agentflow-core";
|
|
22
|
+
import chokidar2 from "chokidar";
|
|
23
|
+
import express from "express";
|
|
24
|
+
import rateLimit from "express-rate-limit";
|
|
25
|
+
import { WebSocketServer } from "ws";
|
|
14
26
|
|
|
15
27
|
// src/config.ts
|
|
16
28
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -82,21 +94,70 @@ function getAgentDetection(config) {
|
|
|
82
94
|
function getProcessPreference(config) {
|
|
83
95
|
return config.processPreference ?? null;
|
|
84
96
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
function getExternalCommands(config) {
|
|
98
|
+
return config.externalCommands ?? {};
|
|
99
|
+
}
|
|
100
|
+
function getValidatedExternalCommands(config) {
|
|
101
|
+
var _a, _b, _c, _d;
|
|
102
|
+
const externalCommands = getExternalCommands(config);
|
|
103
|
+
const errors = [];
|
|
104
|
+
const validatedCommands = {};
|
|
105
|
+
if (!externalCommands.commands) {
|
|
106
|
+
return { commands: {}, errors: [] };
|
|
107
|
+
}
|
|
108
|
+
for (const [commandId, command] of Object.entries(externalCommands.commands)) {
|
|
109
|
+
try {
|
|
110
|
+
if (!commandId.match(/^[a-z][a-z0-9-_]*$/i)) {
|
|
111
|
+
errors.push(`Invalid command ID "${commandId}": must start with letter and contain only letters, numbers, hyphens, and underscores`);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (!((_a = command.name) == null ? void 0 : _a.trim())) {
|
|
115
|
+
errors.push(`Command "${commandId}": name is required`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (!((_b = command.command) == null ? void 0 : _b.trim())) {
|
|
119
|
+
errors.push(`Command "${commandId}": command is required`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const validatedCommand = {
|
|
123
|
+
name: command.name.trim(),
|
|
124
|
+
command: command.command.trim(),
|
|
125
|
+
args: command.args ?? [],
|
|
126
|
+
cwd: expandTilde(command.cwd ?? externalCommands.globalCwd ?? process.cwd()),
|
|
127
|
+
env: { ...externalCommands.globalEnv, ...command.env },
|
|
128
|
+
timeout: command.timeout ?? externalCommands.globalTimeout ?? 6e4,
|
|
129
|
+
description: ((_c = command.description) == null ? void 0 : _c.trim()) ?? "",
|
|
130
|
+
category: ((_d = command.category) == null ? void 0 : _d.trim()) ?? "general",
|
|
131
|
+
allowConcurrent: command.allowConcurrent ?? false
|
|
132
|
+
};
|
|
133
|
+
if (validatedCommand.timeout <= 0 || validatedCommand.timeout > 6e5) {
|
|
134
|
+
errors.push(`Command "${commandId}": timeout must be between 1ms and 600000ms (10 minutes)`);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
if (validatedCommand.cwd.startsWith("/")) {
|
|
138
|
+
try {
|
|
139
|
+
const fs5 = __require("fs");
|
|
140
|
+
const stats = fs5.statSync(validatedCommand.cwd);
|
|
141
|
+
if (!stats.isDirectory()) {
|
|
142
|
+
errors.push(`Command "${commandId}": cwd "${validatedCommand.cwd}" is not a directory`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
} catch (err) {
|
|
146
|
+
errors.push(`Command "${commandId}": cwd "${validatedCommand.cwd}" does not exist or is not accessible`);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
validatedCommands[commandId] = validatedCommand;
|
|
151
|
+
} catch (error) {
|
|
152
|
+
errors.push(`Command "${commandId}": validation error - ${error.message}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { commands: validatedCommands, errors };
|
|
156
|
+
}
|
|
157
|
+
function getExternalCommand(config, commandId) {
|
|
158
|
+
const { commands } = getValidatedExternalCommands(config);
|
|
159
|
+
return commands[commandId] ?? null;
|
|
160
|
+
}
|
|
100
161
|
|
|
101
162
|
// src/adapters/agentflow.ts
|
|
102
163
|
var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
@@ -109,7 +170,14 @@ var SKIP_FILES = /* @__PURE__ */ new Set([
|
|
|
109
170
|
"models.json",
|
|
110
171
|
"config.json"
|
|
111
172
|
]);
|
|
112
|
-
var SKIP_SUFFIXES = [
|
|
173
|
+
var SKIP_SUFFIXES = [
|
|
174
|
+
"-state.json",
|
|
175
|
+
"-config.json",
|
|
176
|
+
"-watch-state.json",
|
|
177
|
+
".tmp",
|
|
178
|
+
".bak",
|
|
179
|
+
".backup"
|
|
180
|
+
];
|
|
113
181
|
var AgentFlowAdapter = class {
|
|
114
182
|
name = "agentflow";
|
|
115
183
|
detect(_dirPath) {
|
|
@@ -168,6 +236,7 @@ var OpenClawAdapter = class {
|
|
|
168
236
|
return filePath.includes("/cron/runs/") || filePath.includes("\\cron\\runs\\");
|
|
169
237
|
}
|
|
170
238
|
parse(filePath) {
|
|
239
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
171
240
|
const traces = [];
|
|
172
241
|
try {
|
|
173
242
|
const content = readFileSync2(filePath, "utf-8");
|
|
@@ -187,34 +256,100 @@ var OpenClawAdapter = class {
|
|
|
187
256
|
const jobName = (job == null ? void 0 : job.name) ?? jobId;
|
|
188
257
|
const startTime = entry.runAtMs ?? entry.ts;
|
|
189
258
|
const duration = entry.durationMs ?? 0;
|
|
259
|
+
const runStatus = entry.status === "ok" ? "completed" : entry.status === "error" ? "failed" : "unknown";
|
|
260
|
+
const summary = entry.summary ?? "";
|
|
261
|
+
const nodes = {};
|
|
262
|
+
const rootChildren = [];
|
|
263
|
+
let stepIdx = 0;
|
|
264
|
+
let m;
|
|
265
|
+
const tableRe = /\|\s*\*?\*?(\d+)\.\s*\*?\*?([^|]+)\*?\*?\s*\|\s*([^|]+)\s*\|\s*([^|]*)\|/g;
|
|
266
|
+
while ((m = tableRe.exec(summary)) !== null) {
|
|
267
|
+
stepIdx++;
|
|
268
|
+
const nodeId = `step-${stepIdx}`;
|
|
269
|
+
rootChildren.push(nodeId);
|
|
270
|
+
nodes[nodeId] = {
|
|
271
|
+
id: nodeId,
|
|
272
|
+
type: "tool",
|
|
273
|
+
name: ((_a = m[2]) == null ? void 0 : _a.trim().replace(/\*+/g, "")) || `Step ${stepIdx}`,
|
|
274
|
+
status: ((_b = m[3]) == null ? void 0 : _b.includes("\u274C")) || ((_c = m[3]) == null ? void 0 : _c.toLowerCase().includes("fail")) ? "failed" : "completed",
|
|
275
|
+
startTime: startTime + (stepIdx - 1) * Math.floor(duration / Math.max(1, stepIdx + 1)),
|
|
276
|
+
endTime: startTime + stepIdx * Math.floor(duration / Math.max(1, stepIdx + 1)),
|
|
277
|
+
parentId: "root",
|
|
278
|
+
children: [],
|
|
279
|
+
metadata: { detail: ((_d = m[4]) == null ? void 0 : _d.trim()) || "" }
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (stepIdx === 0) {
|
|
283
|
+
const listRe = /^\s*(\d+)\.\s*\*?\*?([^*\n]+)\*?\*?\s*(?:[-\u2014]\s*(.+))?$/gm;
|
|
284
|
+
while ((m = listRe.exec(summary)) !== null) {
|
|
285
|
+
if ((_e = m[2]) == null ? void 0 : _e.trim().startsWith("#")) continue;
|
|
286
|
+
stepIdx++;
|
|
287
|
+
const nodeId = `step-${stepIdx}`;
|
|
288
|
+
const detail = ((_f = m[3]) == null ? void 0 : _f.trim()) || "";
|
|
289
|
+
rootChildren.push(nodeId);
|
|
290
|
+
nodes[nodeId] = {
|
|
291
|
+
id: nodeId,
|
|
292
|
+
type: "tool",
|
|
293
|
+
name: ((_g = m[2]) == null ? void 0 : _g.trim().replace(/\*+/g, "")) || `Step ${stepIdx}`,
|
|
294
|
+
status: detail.toLowerCase().includes("fail") ? "failed" : "completed",
|
|
295
|
+
startTime: startTime + (stepIdx - 1) * Math.floor(duration / Math.max(1, stepIdx + 1)),
|
|
296
|
+
endTime: startTime + stepIdx * Math.floor(duration / Math.max(1, stepIdx + 1)),
|
|
297
|
+
parentId: "root",
|
|
298
|
+
children: [],
|
|
299
|
+
metadata: { detail }
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (stepIdx === 0 && summary.length > 50) {
|
|
304
|
+
const sectionRe = /(?:^|\n)\s*(?:#{1,3}|(?:\*\*[^*\n]{3,50}\*\*))\s*(.+)/g;
|
|
305
|
+
while ((m = sectionRe.exec(summary)) !== null) {
|
|
306
|
+
const heading = ((_h = m[1]) == null ? void 0 : _h.trim().replace(/\*+/g, "").replace(/^#+\s*/, "")) || "";
|
|
307
|
+
if (!heading || heading.length < 3 || heading.startsWith("|") || heading.startsWith("---"))
|
|
308
|
+
continue;
|
|
309
|
+
stepIdx++;
|
|
310
|
+
const nodeId = `section-${stepIdx}`;
|
|
311
|
+
rootChildren.push(nodeId);
|
|
312
|
+
nodes[nodeId] = {
|
|
313
|
+
id: nodeId,
|
|
314
|
+
type: "custom",
|
|
315
|
+
name: heading.slice(0, 60),
|
|
316
|
+
status: "completed",
|
|
317
|
+
startTime: startTime + (stepIdx - 1) * Math.floor(duration / Math.max(1, stepIdx + 1)),
|
|
318
|
+
endTime: startTime + stepIdx * Math.floor(duration / Math.max(1, stepIdx + 1)),
|
|
319
|
+
parentId: "root",
|
|
320
|
+
children: [],
|
|
321
|
+
metadata: {}
|
|
322
|
+
};
|
|
323
|
+
if (stepIdx >= 10) break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
nodes["root"] = {
|
|
327
|
+
id: "root",
|
|
328
|
+
type: "cron-job",
|
|
329
|
+
name: jobName,
|
|
330
|
+
status: runStatus,
|
|
331
|
+
startTime,
|
|
332
|
+
endTime: startTime + duration,
|
|
333
|
+
parentId: null,
|
|
334
|
+
children: rootChildren,
|
|
335
|
+
metadata: {
|
|
336
|
+
jobId,
|
|
337
|
+
summary: entry.summary,
|
|
338
|
+
error: entry.error,
|
|
339
|
+
delivered: entry.delivered,
|
|
340
|
+
deliveryStatus: entry.deliveryStatus
|
|
341
|
+
}
|
|
342
|
+
};
|
|
190
343
|
const trace = {
|
|
191
344
|
id: entry.sessionId ?? `${jobId}-${entry.ts}`,
|
|
192
345
|
agentId: `openclaw:${jobId}`,
|
|
193
346
|
name: jobName,
|
|
194
|
-
status:
|
|
347
|
+
status: runStatus,
|
|
195
348
|
startTime,
|
|
196
349
|
endTime: startTime + duration,
|
|
197
350
|
trigger: "cron",
|
|
198
351
|
source: "openclaw",
|
|
199
|
-
nodes
|
|
200
|
-
root: {
|
|
201
|
-
id: "root",
|
|
202
|
-
type: "cron-job",
|
|
203
|
-
name: jobName,
|
|
204
|
-
status: entry.status === "ok" ? "completed" : entry.status === "error" ? "failed" : "unknown",
|
|
205
|
-
startTime,
|
|
206
|
-
endTime: startTime + duration,
|
|
207
|
-
parentId: null,
|
|
208
|
-
children: [],
|
|
209
|
-
metadata: {
|
|
210
|
-
jobId,
|
|
211
|
-
summary: entry.summary,
|
|
212
|
-
error: entry.error,
|
|
213
|
-
delivered: entry.delivered,
|
|
214
|
-
deliveryStatus: entry.deliveryStatus
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
},
|
|
352
|
+
nodes,
|
|
218
353
|
metadata: {
|
|
219
354
|
model: entry.model,
|
|
220
355
|
provider: entry.provider,
|
|
@@ -234,7 +369,7 @@ var OpenClawAdapter = class {
|
|
|
234
369
|
};
|
|
235
370
|
|
|
236
371
|
// src/adapters/otel.ts
|
|
237
|
-
import { existsSync as existsSync3, readFileSync as readFileSync3
|
|
372
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3 } from "fs";
|
|
238
373
|
import { join as join3 } from "path";
|
|
239
374
|
var SPAN_TYPE_MAP = {
|
|
240
375
|
"gen_ai.chat": "llm",
|
|
@@ -386,8 +521,14 @@ registerAdapter(new AgentFlowAdapter());
|
|
|
386
521
|
var PURPOSE_KEYWORDS = [
|
|
387
522
|
{ keywords: ["email", "mail", "inbox", "smtp"], group: "Email Processors" },
|
|
388
523
|
{ keywords: ["monitor", "watch", "alert", "surveillance"], group: "Monitors" },
|
|
389
|
-
{
|
|
390
|
-
|
|
524
|
+
{
|
|
525
|
+
keywords: ["digest", "newsletter", "summary", "report", "briefing"],
|
|
526
|
+
group: "Digests & Reports"
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
keywords: ["curator", "janitor", "distiller", "surveyor", "worker", "indexer"],
|
|
530
|
+
group: "Workers"
|
|
531
|
+
},
|
|
391
532
|
{ keywords: ["cron", "schedule", "timer", "periodic"], group: "Scheduled Jobs" },
|
|
392
533
|
{ keywords: ["search", "scrape", "crawl", "fetch"], group: "Data Collection" },
|
|
393
534
|
{ keywords: ["embed", "vector", "index"], group: "Embeddings" }
|
|
@@ -419,6 +560,7 @@ function capitalize(s) {
|
|
|
419
560
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
420
561
|
}
|
|
421
562
|
function deduplicateAgents(agents) {
|
|
563
|
+
var _a, _b, _c, _d;
|
|
422
564
|
const tagged = agents.map((a) => ({
|
|
423
565
|
...a,
|
|
424
566
|
...extractSource(a.agentId)
|
|
@@ -435,14 +577,15 @@ function deduplicateAgents(agents) {
|
|
|
435
577
|
const mergedIds = /* @__PURE__ */ new Set();
|
|
436
578
|
const mergedAgents = [];
|
|
437
579
|
for (const [_key, group] of suffixGroups) {
|
|
438
|
-
const suffix = extractSuffix(group[0].localId);
|
|
439
580
|
if (group.length < 2) continue;
|
|
581
|
+
const suffix = extractSuffix(((_a = group[0]) == null ? void 0 : _a.localId) ?? "");
|
|
582
|
+
if (!suffix) continue;
|
|
440
583
|
const prefixes = new Set(group.map((a) => a.localId.split("-")[0]));
|
|
441
584
|
if (prefixes.size < 2) continue;
|
|
442
585
|
const longPrefixes = [...prefixes].filter((p) => p !== suffix && p.length > 2);
|
|
443
586
|
if (longPrefixes.length >= 2) continue;
|
|
444
587
|
const merged = {
|
|
445
|
-
agentId: group[0].source === "agentflow" ? suffix : `${group[0].source}:${suffix}`,
|
|
588
|
+
agentId: ((_b = group[0]) == null ? void 0 : _b.source) === "agentflow" ? suffix : `${(_c = group[0]) == null ? void 0 : _c.source}:${suffix}`,
|
|
446
589
|
displayName: suffix,
|
|
447
590
|
totalExecutions: group.reduce((s, a) => s + a.totalExecutions, 0),
|
|
448
591
|
successfulExecutions: group.reduce((s, a) => s + a.successfulExecutions, 0),
|
|
@@ -453,7 +596,7 @@ function deduplicateAgents(agents) {
|
|
|
453
596
|
triggers: {},
|
|
454
597
|
recentActivity: group.flatMap((a) => a.recentActivity).sort((a, b) => b.timestamp - a.timestamp).slice(0, 50),
|
|
455
598
|
sources: group.map((a) => a.agentId),
|
|
456
|
-
adapterSource: group[0].source
|
|
599
|
+
adapterSource: (_d = group[0]) == null ? void 0 : _d.source
|
|
457
600
|
};
|
|
458
601
|
merged.successRate = merged.totalExecutions > 0 ? merged.successfulExecutions / merged.totalExecutions * 100 : 0;
|
|
459
602
|
const totalExecTime = group.reduce((s, a) => s + a.avgExecutionTime * a.totalExecutions, 0);
|
|
@@ -513,13 +656,470 @@ function groupAgents(agents) {
|
|
|
513
656
|
return { groups };
|
|
514
657
|
}
|
|
515
658
|
|
|
659
|
+
// src/command-executor.ts
|
|
660
|
+
import * as fs from "fs";
|
|
661
|
+
import * as path from "path";
|
|
662
|
+
import { spawn } from "child_process";
|
|
663
|
+
var CommandExecutor = class {
|
|
664
|
+
constructor(config, options = {}) {
|
|
665
|
+
this.config = config;
|
|
666
|
+
var _a;
|
|
667
|
+
this.maxConcurrentExecutions = options.maxConcurrentExecutions ?? ((_a = config.externalCommands) == null ? void 0 : _a.maxConcurrentExecutions) ?? 5;
|
|
668
|
+
}
|
|
669
|
+
executions = /* @__PURE__ */ new Map();
|
|
670
|
+
runningProcesses = /* @__PURE__ */ new Map();
|
|
671
|
+
executionCounter = 0;
|
|
672
|
+
maxConcurrentExecutions;
|
|
673
|
+
/**
|
|
674
|
+
* Execute an external command with security validation
|
|
675
|
+
*/
|
|
676
|
+
async executeCommand(request) {
|
|
677
|
+
var _a, _b;
|
|
678
|
+
const executionId = this.generateExecutionId();
|
|
679
|
+
try {
|
|
680
|
+
const validation = this.validateExecutionRequest(request);
|
|
681
|
+
if (validation.length > 0) {
|
|
682
|
+
return this.createFailedResult(executionId, request.commandId, validation[0].message);
|
|
683
|
+
}
|
|
684
|
+
const command = getExternalCommand(this.config, request.commandId);
|
|
685
|
+
if (!command) {
|
|
686
|
+
return this.createFailedResult(executionId, request.commandId, `Command "${request.commandId}" not found in configuration`);
|
|
687
|
+
}
|
|
688
|
+
if (!this.canStartExecution(command)) {
|
|
689
|
+
return this.createFailedResult(
|
|
690
|
+
executionId,
|
|
691
|
+
request.commandId,
|
|
692
|
+
`Cannot start command: ${command.allowConcurrent ? "concurrent execution limit reached" : "command already running"}`
|
|
693
|
+
);
|
|
694
|
+
}
|
|
695
|
+
const result = {
|
|
696
|
+
executionId,
|
|
697
|
+
started: false,
|
|
698
|
+
command,
|
|
699
|
+
startedAt: Date.now(),
|
|
700
|
+
stdout: "",
|
|
701
|
+
stderr: "",
|
|
702
|
+
status: "running"
|
|
703
|
+
};
|
|
704
|
+
this.executions.set(executionId, result);
|
|
705
|
+
const sanitizedArgs = this.sanitizeArguments([
|
|
706
|
+
...command.args,
|
|
707
|
+
...request.additionalArgs ?? []
|
|
708
|
+
]);
|
|
709
|
+
const executionTimeout = request.timeout ?? command.timeout;
|
|
710
|
+
const sanitizedEnv = this.sanitizeEnvironment(command.env);
|
|
711
|
+
const childProcess = spawn(command.command, sanitizedArgs, {
|
|
712
|
+
cwd: command.cwd,
|
|
713
|
+
env: { ...process.env, ...sanitizedEnv },
|
|
714
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
715
|
+
// stdin ignored, capture stdout/stderr
|
|
716
|
+
detached: false
|
|
717
|
+
// Keep process attached for proper cleanup
|
|
718
|
+
});
|
|
719
|
+
result.started = true;
|
|
720
|
+
result.pid = childProcess.pid;
|
|
721
|
+
this.runningProcesses.set(executionId, childProcess);
|
|
722
|
+
const timeoutHandle = setTimeout(() => {
|
|
723
|
+
this.killExecution(executionId, "timeout");
|
|
724
|
+
}, executionTimeout);
|
|
725
|
+
(_a = childProcess.stdout) == null ? void 0 : _a.on("data", (data) => {
|
|
726
|
+
result.stdout += data.toString();
|
|
727
|
+
});
|
|
728
|
+
(_b = childProcess.stderr) == null ? void 0 : _b.on("data", (data) => {
|
|
729
|
+
result.stderr += data.toString();
|
|
730
|
+
});
|
|
731
|
+
childProcess.on("close", (code, signal) => {
|
|
732
|
+
clearTimeout(timeoutHandle);
|
|
733
|
+
this.runningProcesses.delete(executionId);
|
|
734
|
+
result.completedAt = Date.now();
|
|
735
|
+
result.duration = result.completedAt - result.startedAt;
|
|
736
|
+
result.exitCode = code ?? void 0;
|
|
737
|
+
if (signal) {
|
|
738
|
+
result.status = result.status === "running" ? "killed" : result.status;
|
|
739
|
+
result.error = `Process killed by signal: ${signal}`;
|
|
740
|
+
} else if (code === 0) {
|
|
741
|
+
result.status = "completed";
|
|
742
|
+
} else {
|
|
743
|
+
result.status = "failed";
|
|
744
|
+
result.error = `Process exited with code: ${code}`;
|
|
745
|
+
}
|
|
746
|
+
this.logExecution(result, request.context);
|
|
747
|
+
});
|
|
748
|
+
childProcess.on("error", (error) => {
|
|
749
|
+
clearTimeout(timeoutHandle);
|
|
750
|
+
this.runningProcesses.delete(executionId);
|
|
751
|
+
result.completedAt = Date.now();
|
|
752
|
+
result.duration = result.completedAt - result.startedAt;
|
|
753
|
+
result.status = "failed";
|
|
754
|
+
result.error = `Process error: ${error.message}`;
|
|
755
|
+
this.logExecution(result, request.context);
|
|
756
|
+
});
|
|
757
|
+
return result;
|
|
758
|
+
} catch (error) {
|
|
759
|
+
return this.createFailedResult(
|
|
760
|
+
executionId,
|
|
761
|
+
request.commandId,
|
|
762
|
+
`Execution setup failed: ${error.message}`
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Get execution status by ID
|
|
768
|
+
*/
|
|
769
|
+
getExecution(executionId) {
|
|
770
|
+
return this.executions.get(executionId) ?? null;
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Get all executions (recent first)
|
|
774
|
+
*/
|
|
775
|
+
getAllExecutions(limit = 100) {
|
|
776
|
+
return Array.from(this.executions.values()).sort((a, b) => b.startedAt - a.startedAt).slice(0, limit);
|
|
777
|
+
}
|
|
778
|
+
/**
|
|
779
|
+
* Kill a running execution
|
|
780
|
+
*/
|
|
781
|
+
killExecution(executionId, reason = "manual") {
|
|
782
|
+
const execution = this.executions.get(executionId);
|
|
783
|
+
const process2 = this.runningProcesses.get(executionId);
|
|
784
|
+
if (!execution || !process2) {
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
process2.kill("SIGTERM");
|
|
789
|
+
setTimeout(() => {
|
|
790
|
+
if (this.runningProcesses.has(executionId)) {
|
|
791
|
+
process2.kill("SIGKILL");
|
|
792
|
+
}
|
|
793
|
+
}, 5e3);
|
|
794
|
+
if (reason === "timeout") {
|
|
795
|
+
execution.status = "timeout";
|
|
796
|
+
execution.error = "Command execution timed out";
|
|
797
|
+
} else {
|
|
798
|
+
execution.status = "killed";
|
|
799
|
+
execution.error = `Command killed: ${reason}`;
|
|
800
|
+
}
|
|
801
|
+
return true;
|
|
802
|
+
} catch (error) {
|
|
803
|
+
execution.error = `Failed to kill process: ${error.message}`;
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Get currently running executions
|
|
809
|
+
*/
|
|
810
|
+
getRunningExecutions() {
|
|
811
|
+
return Array.from(this.executions.values()).filter((exec) => exec.status === "running");
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Clean up old execution records
|
|
815
|
+
*/
|
|
816
|
+
cleanupExecutions(maxAge = 24 * 60 * 60 * 1e3) {
|
|
817
|
+
const cutoff = Date.now() - maxAge;
|
|
818
|
+
let cleaned = 0;
|
|
819
|
+
for (const [id, execution] of this.executions.entries()) {
|
|
820
|
+
if (execution.status !== "running" && execution.startedAt < cutoff) {
|
|
821
|
+
this.executions.delete(id);
|
|
822
|
+
cleaned++;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
return cleaned;
|
|
826
|
+
}
|
|
827
|
+
validateExecutionRequest(request) {
|
|
828
|
+
var _a;
|
|
829
|
+
const errors = [];
|
|
830
|
+
if (!((_a = request.commandId) == null ? void 0 : _a.trim())) {
|
|
831
|
+
errors.push({
|
|
832
|
+
type: "validation_error",
|
|
833
|
+
message: "Command ID is required"
|
|
834
|
+
});
|
|
835
|
+
return errors;
|
|
836
|
+
}
|
|
837
|
+
const { commands, errors: configErrors } = getValidatedExternalCommands(this.config);
|
|
838
|
+
if (configErrors.length > 0) {
|
|
839
|
+
errors.push({
|
|
840
|
+
type: "config_error",
|
|
841
|
+
message: `Configuration errors: ${configErrors.join(", ")}`
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
if (!commands[request.commandId]) {
|
|
845
|
+
errors.push({
|
|
846
|
+
type: "validation_error",
|
|
847
|
+
message: `Command "${request.commandId}" not found in configuration`,
|
|
848
|
+
commandId: request.commandId
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
if (request.additionalArgs) {
|
|
852
|
+
for (const arg of request.additionalArgs) {
|
|
853
|
+
if (this.containsUnsafeContent(arg)) {
|
|
854
|
+
errors.push({
|
|
855
|
+
type: "security_violation",
|
|
856
|
+
message: `Unsafe content detected in additional arguments`,
|
|
857
|
+
commandId: request.commandId
|
|
858
|
+
});
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
if (request.timeout !== void 0 && (request.timeout <= 0 || request.timeout > 6e5)) {
|
|
864
|
+
errors.push({
|
|
865
|
+
type: "validation_error",
|
|
866
|
+
message: "Timeout must be between 1ms and 600000ms (10 minutes)",
|
|
867
|
+
commandId: request.commandId
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
return errors;
|
|
871
|
+
}
|
|
872
|
+
containsUnsafeContent(content) {
|
|
873
|
+
const dangerousPatterns = [
|
|
874
|
+
/[;&|`$(){}[\]]/,
|
|
875
|
+
// Shell metacharacters
|
|
876
|
+
/\.\./,
|
|
877
|
+
// Directory traversal
|
|
878
|
+
/\/dev\/|\/proc\/|\/sys\//,
|
|
879
|
+
// System paths
|
|
880
|
+
/rm\s+-rf|rm\s+-f/i,
|
|
881
|
+
// Dangerous rm commands
|
|
882
|
+
/chmod|chown|sudo/i,
|
|
883
|
+
// Privilege escalation
|
|
884
|
+
/curl|wget|nc|telnet/i
|
|
885
|
+
// Network commands
|
|
886
|
+
];
|
|
887
|
+
return dangerousPatterns.some((pattern) => pattern.test(content));
|
|
888
|
+
}
|
|
889
|
+
sanitizeArguments(args) {
|
|
890
|
+
return args.map((arg) => {
|
|
891
|
+
let sanitized = arg.replace(/[\x00-\x1f\x7f]/g, "");
|
|
892
|
+
sanitized = sanitized.trim();
|
|
893
|
+
if (sanitized.length > 1e3) {
|
|
894
|
+
sanitized = sanitized.substring(0, 1e3);
|
|
895
|
+
}
|
|
896
|
+
return sanitized;
|
|
897
|
+
}).filter((arg) => arg.length > 0);
|
|
898
|
+
}
|
|
899
|
+
sanitizeEnvironment(env) {
|
|
900
|
+
const sanitized = {};
|
|
901
|
+
if (!env) return sanitized;
|
|
902
|
+
for (const [key, value] of Object.entries(env)) {
|
|
903
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
const sanitizedValue = value.replace(/[\x00-\x1f\x7f]/g, "").trim();
|
|
907
|
+
if (sanitizedValue.length > 0 && sanitizedValue.length <= 4096) {
|
|
908
|
+
sanitized[key] = sanitizedValue;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return sanitized;
|
|
912
|
+
}
|
|
913
|
+
canStartExecution(command) {
|
|
914
|
+
const running = this.getRunningExecutions();
|
|
915
|
+
if (running.length >= this.maxConcurrentExecutions) {
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
if (!command.allowConcurrent) {
|
|
919
|
+
const commandRunning = running.some(
|
|
920
|
+
(exec) => exec.command.name === command.name
|
|
921
|
+
);
|
|
922
|
+
if (commandRunning) {
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
generateExecutionId() {
|
|
929
|
+
return `exec_${Date.now()}_${++this.executionCounter}`;
|
|
930
|
+
}
|
|
931
|
+
createFailedResult(executionId, commandId, error) {
|
|
932
|
+
const result = {
|
|
933
|
+
executionId,
|
|
934
|
+
started: false,
|
|
935
|
+
command: {
|
|
936
|
+
name: commandId,
|
|
937
|
+
command: "unknown",
|
|
938
|
+
timeout: 0
|
|
939
|
+
},
|
|
940
|
+
startedAt: Date.now(),
|
|
941
|
+
completedAt: Date.now(),
|
|
942
|
+
duration: 0,
|
|
943
|
+
stdout: "",
|
|
944
|
+
stderr: "",
|
|
945
|
+
status: "failed",
|
|
946
|
+
error
|
|
947
|
+
};
|
|
948
|
+
this.executions.set(executionId, result);
|
|
949
|
+
return result;
|
|
950
|
+
}
|
|
951
|
+
logExecution(result, context) {
|
|
952
|
+
var _a;
|
|
953
|
+
const logEntry = {
|
|
954
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
955
|
+
executionId: result.executionId,
|
|
956
|
+
commandId: result.command.name,
|
|
957
|
+
commandLine: `${result.command.command} ${((_a = result.command.args) == null ? void 0 : _a.join(" ")) ?? ""}`.trim(),
|
|
958
|
+
status: result.status,
|
|
959
|
+
duration: result.duration,
|
|
960
|
+
exitCode: result.exitCode,
|
|
961
|
+
pid: result.pid,
|
|
962
|
+
cwd: result.command.cwd,
|
|
963
|
+
timeout: result.command.timeout,
|
|
964
|
+
context,
|
|
965
|
+
hasOutput: result.stdout.length > 0,
|
|
966
|
+
hasError: result.stderr.length > 0,
|
|
967
|
+
outputSize: result.stdout.length,
|
|
968
|
+
errorSize: result.stderr.length,
|
|
969
|
+
error: result.error,
|
|
970
|
+
userAgent: (context == null ? void 0 : context.userId) ? `user:${context.userId}` : "system",
|
|
971
|
+
sessionId: context == null ? void 0 : context.sessionId,
|
|
972
|
+
requestId: context == null ? void 0 : context.requestId
|
|
973
|
+
};
|
|
974
|
+
console.log(`[CommandExecution] ${JSON.stringify(logEntry)}`);
|
|
975
|
+
this.writeAuditLog(logEntry, result);
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Write detailed audit log to file for compliance and debugging
|
|
979
|
+
*/
|
|
980
|
+
writeAuditLog(logEntry, result) {
|
|
981
|
+
var _a, _b, _c, _d;
|
|
982
|
+
try {
|
|
983
|
+
const auditDir = path.join(process.cwd(), ".agentflow", "audit");
|
|
984
|
+
const auditFile = path.join(auditDir, `command-executions-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.jsonl`);
|
|
985
|
+
if (!fs.existsSync(auditDir)) {
|
|
986
|
+
fs.mkdirSync(auditDir, { recursive: true });
|
|
987
|
+
}
|
|
988
|
+
const auditEntry = {
|
|
989
|
+
...logEntry,
|
|
990
|
+
// Additional audit fields
|
|
991
|
+
startedAt: new Date(result.startedAt).toISOString(),
|
|
992
|
+
completedAt: result.completedAt ? new Date(result.completedAt).toISOString() : null,
|
|
993
|
+
commandArgs: result.command.args,
|
|
994
|
+
allowConcurrent: result.command.allowConcurrent,
|
|
995
|
+
category: result.command.category,
|
|
996
|
+
description: result.command.description,
|
|
997
|
+
// Include first/last lines of output for audit trail
|
|
998
|
+
outputPreview: result.stdout ? {
|
|
999
|
+
firstLine: ((_a = result.stdout.split("\n")[0]) == null ? void 0 : _a.slice(0, 200)) || "",
|
|
1000
|
+
lastLine: ((_b = result.stdout.split("\n").slice(-1)[0]) == null ? void 0 : _b.slice(0, 200)) || "",
|
|
1001
|
+
totalLines: result.stdout.split("\n").length
|
|
1002
|
+
} : null,
|
|
1003
|
+
errorPreview: result.stderr ? {
|
|
1004
|
+
firstLine: ((_c = result.stderr.split("\n")[0]) == null ? void 0 : _c.slice(0, 200)) || "",
|
|
1005
|
+
lastLine: ((_d = result.stderr.split("\n").slice(-1)[0]) == null ? void 0 : _d.slice(0, 200)) || "",
|
|
1006
|
+
totalLines: result.stderr.split("\n").length
|
|
1007
|
+
} : null
|
|
1008
|
+
};
|
|
1009
|
+
fs.appendFileSync(auditFile, JSON.stringify(auditEntry) + "\n", "utf-8");
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
console.warn(`[CommandExecutor] Failed to write audit log: ${error.message}`);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Get audit trail for a specific execution
|
|
1016
|
+
*/
|
|
1017
|
+
getAuditTrail(executionId) {
|
|
1018
|
+
try {
|
|
1019
|
+
const auditDir = path.join(process.cwd(), ".agentflow", "audit");
|
|
1020
|
+
const auditEntries = [];
|
|
1021
|
+
if (!fs.existsSync(auditDir)) {
|
|
1022
|
+
return auditEntries;
|
|
1023
|
+
}
|
|
1024
|
+
const files = fs.readdirSync(auditDir).filter((f) => f.startsWith("command-executions-") && f.endsWith(".jsonl")).sort().slice(-7);
|
|
1025
|
+
for (const file of files) {
|
|
1026
|
+
const filePath = path.join(auditDir, file);
|
|
1027
|
+
try {
|
|
1028
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1029
|
+
const lines = content.trim().split("\n").filter((line) => line.trim());
|
|
1030
|
+
for (const line of lines) {
|
|
1031
|
+
try {
|
|
1032
|
+
const entry = JSON.parse(line);
|
|
1033
|
+
if (entry.executionId === executionId) {
|
|
1034
|
+
auditEntries.push(entry);
|
|
1035
|
+
}
|
|
1036
|
+
} catch {
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
} catch {
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
return auditEntries.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
console.warn(`[CommandExecutor] Failed to get audit trail: ${error.message}`);
|
|
1045
|
+
return [];
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Get audit statistics
|
|
1050
|
+
*/
|
|
1051
|
+
getAuditStats(days = 7) {
|
|
1052
|
+
try {
|
|
1053
|
+
const auditDir = path.join(process.cwd(), ".agentflow", "audit");
|
|
1054
|
+
const stats = {
|
|
1055
|
+
totalExecutions: 0,
|
|
1056
|
+
successfulExecutions: 0,
|
|
1057
|
+
failedExecutions: 0,
|
|
1058
|
+
averageDuration: 0,
|
|
1059
|
+
commandFrequency: {},
|
|
1060
|
+
userActivity: {}
|
|
1061
|
+
};
|
|
1062
|
+
if (!fs.existsSync(auditDir)) {
|
|
1063
|
+
return stats;
|
|
1064
|
+
}
|
|
1065
|
+
const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
1066
|
+
let totalDuration = 0;
|
|
1067
|
+
const files = fs.readdirSync(auditDir).filter((f) => f.startsWith("command-executions-") && f.endsWith(".jsonl")).sort().slice(-days);
|
|
1068
|
+
for (const file of files) {
|
|
1069
|
+
const filePath = path.join(auditDir, file);
|
|
1070
|
+
try {
|
|
1071
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
1072
|
+
const lines = content.trim().split("\n").filter((line) => line.trim());
|
|
1073
|
+
for (const line of lines) {
|
|
1074
|
+
try {
|
|
1075
|
+
const entry = JSON.parse(line);
|
|
1076
|
+
const entryTime = new Date(entry.timestamp).getTime();
|
|
1077
|
+
if (entryTime < cutoff) continue;
|
|
1078
|
+
stats.totalExecutions++;
|
|
1079
|
+
if (entry.status === "completed") {
|
|
1080
|
+
stats.successfulExecutions++;
|
|
1081
|
+
} else if (["failed", "timeout", "killed"].includes(entry.status)) {
|
|
1082
|
+
stats.failedExecutions++;
|
|
1083
|
+
}
|
|
1084
|
+
if (entry.duration) {
|
|
1085
|
+
totalDuration += entry.duration;
|
|
1086
|
+
}
|
|
1087
|
+
const cmd = entry.commandId || "unknown";
|
|
1088
|
+
stats.commandFrequency[cmd] = (stats.commandFrequency[cmd] || 0) + 1;
|
|
1089
|
+
const user = entry.userAgent || "unknown";
|
|
1090
|
+
stats.userActivity[user] = (stats.userActivity[user] || 0) + 1;
|
|
1091
|
+
} catch {
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
} catch {
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
stats.averageDuration = stats.totalExecutions > 0 ? totalDuration / stats.totalExecutions : 0;
|
|
1098
|
+
return stats;
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
console.warn(`[CommandExecutor] Failed to get audit stats: ${error.message}`);
|
|
1101
|
+
return {
|
|
1102
|
+
totalExecutions: 0,
|
|
1103
|
+
successfulExecutions: 0,
|
|
1104
|
+
failedExecutions: 0,
|
|
1105
|
+
averageDuration: 0,
|
|
1106
|
+
commandFrequency: {},
|
|
1107
|
+
userActivity: {}
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1112
|
+
function createCommandExecutor(config, options) {
|
|
1113
|
+
return new CommandExecutor(config, options);
|
|
1114
|
+
}
|
|
1115
|
+
|
|
516
1116
|
// src/stats.ts
|
|
517
1117
|
import { getFailures, getHungNodes, getStats } from "agentflow-core";
|
|
518
1118
|
var AgentStats = class {
|
|
519
1119
|
agentMetrics = /* @__PURE__ */ new Map();
|
|
520
1120
|
processedTraces = /* @__PURE__ */ new Set();
|
|
521
1121
|
processTrace(trace) {
|
|
522
|
-
const traceKey = `${trace.filename || trace.
|
|
1122
|
+
const traceKey = `${trace.agentId}#${trace.filename || trace.id}-${trace.startTime}`;
|
|
523
1123
|
if (this.processedTraces.has(traceKey)) {
|
|
524
1124
|
return;
|
|
525
1125
|
}
|
|
@@ -690,8 +1290,8 @@ var AgentStats = class {
|
|
|
690
1290
|
|
|
691
1291
|
// src/watcher.ts
|
|
692
1292
|
import { EventEmitter } from "events";
|
|
693
|
-
import * as
|
|
694
|
-
import * as
|
|
1293
|
+
import * as fs2 from "fs";
|
|
1294
|
+
import * as path2 from "path";
|
|
695
1295
|
import { loadGraph } from "agentflow-core";
|
|
696
1296
|
import chokidar from "chokidar";
|
|
697
1297
|
|
|
@@ -739,13 +1339,19 @@ function extractKeyValuePairs(line) {
|
|
|
739
1339
|
const kvRegex = /(\w+)=('(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|\S+)/g;
|
|
740
1340
|
let match;
|
|
741
1341
|
while ((match = kvRegex.exec(clean)) !== null) {
|
|
742
|
-
|
|
743
|
-
|
|
1342
|
+
const key = match[1];
|
|
1343
|
+
const value = match[2];
|
|
1344
|
+
if (!key || !value) continue;
|
|
1345
|
+
if (key === "Z" || key === "m") continue;
|
|
1346
|
+
pairs[key] = parseValue(value);
|
|
744
1347
|
}
|
|
745
1348
|
return pairs;
|
|
746
1349
|
}
|
|
747
1350
|
function detectComponent(action, kvPairs) {
|
|
748
|
-
if (action.includes("."))
|
|
1351
|
+
if (action.includes(".")) {
|
|
1352
|
+
const parts = action.split(".");
|
|
1353
|
+
return parts[0] || action;
|
|
1354
|
+
}
|
|
749
1355
|
if (kvPairs.component) return String(kvPairs.component);
|
|
750
1356
|
if (kvPairs.service) return String(kvPairs.service);
|
|
751
1357
|
if (kvPairs.module) return String(kvPairs.module);
|
|
@@ -783,7 +1389,9 @@ function detectActivityPattern(line) {
|
|
|
783
1389
|
const pairs = {};
|
|
784
1390
|
for (const m of kvMatches) {
|
|
785
1391
|
const [key, value] = m.split("=", 2);
|
|
786
|
-
|
|
1392
|
+
if (key && value !== void 0) {
|
|
1393
|
+
pairs[key] = parseValue(value);
|
|
1394
|
+
}
|
|
787
1395
|
}
|
|
788
1396
|
timestamp = parseTimestamp(pairs.timestamp || pairs.time) || Date.now();
|
|
789
1397
|
level = String(pairs.level || "info");
|
|
@@ -795,7 +1403,7 @@ function detectActivityPattern(line) {
|
|
|
795
1403
|
const logMatch = line.match(
|
|
796
1404
|
/^(\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[.\d]*Z?)\s+(\w+)?\s*:?\s*(.+)/
|
|
797
1405
|
);
|
|
798
|
-
if (logMatch) {
|
|
1406
|
+
if (logMatch && logMatch[1]) {
|
|
799
1407
|
timestamp = new Date(logMatch[1]).getTime();
|
|
800
1408
|
level = logMatch[2] || "info";
|
|
801
1409
|
action = logMatch[3] || "";
|
|
@@ -833,11 +1441,38 @@ function getUniversalNodeStatus(activity) {
|
|
|
833
1441
|
if (op.match(/complete|finish|end|done/i)) return "completed";
|
|
834
1442
|
return "completed";
|
|
835
1443
|
}
|
|
836
|
-
function openClawSessionIdToAgent(sessionId) {
|
|
1444
|
+
function openClawSessionIdToAgent(sessionId, lookupMap) {
|
|
1445
|
+
if (lookupMap == null ? void 0 : lookupMap.has(sessionId)) {
|
|
1446
|
+
return lookupMap.get(sessionId);
|
|
1447
|
+
}
|
|
837
1448
|
const firstSegment = sessionId.split("-")[0];
|
|
838
1449
|
if (firstSegment) return firstSegment;
|
|
839
1450
|
return "openclaw";
|
|
840
1451
|
}
|
|
1452
|
+
function parseOpenClawSessionKey(key) {
|
|
1453
|
+
const parts = key.split(":");
|
|
1454
|
+
if (parts.length < 3 || parts[0] !== "agent") return null;
|
|
1455
|
+
const agentName = parts[1];
|
|
1456
|
+
const kind = parts[2];
|
|
1457
|
+
if (parts.length === 3 && kind === agentName) {
|
|
1458
|
+
return `openclaw:${agentName}`;
|
|
1459
|
+
}
|
|
1460
|
+
if (kind === "cron" && parts.length >= 4) {
|
|
1461
|
+
const runIdx = parts.indexOf("run", 4);
|
|
1462
|
+
const jobParts = runIdx > 0 ? parts.slice(3, runIdx) : parts.slice(3);
|
|
1463
|
+
const jobId = jobParts.join("-");
|
|
1464
|
+
return jobId ? `openclaw:${jobId}` : null;
|
|
1465
|
+
}
|
|
1466
|
+
if (parts.length >= 5) {
|
|
1467
|
+
const target = parts[4];
|
|
1468
|
+
return `openclaw:${kind}:${target}`;
|
|
1469
|
+
}
|
|
1470
|
+
if (parts.length >= 4) {
|
|
1471
|
+
const target = parts[3];
|
|
1472
|
+
return `openclaw:${kind}:${target}`;
|
|
1473
|
+
}
|
|
1474
|
+
return null;
|
|
1475
|
+
}
|
|
841
1476
|
|
|
842
1477
|
// src/watcher.ts
|
|
843
1478
|
var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
@@ -848,19 +1483,21 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
848
1483
|
allWatchDirs;
|
|
849
1484
|
maxAgeMs;
|
|
850
1485
|
userConfig;
|
|
1486
|
+
/** Maps OpenClaw session UUIDs to resolved agentIds (populated from sessions.json) */
|
|
1487
|
+
sessionAgentMap = /* @__PURE__ */ new Map();
|
|
851
1488
|
constructor(tracesDirOrOptions) {
|
|
852
1489
|
super();
|
|
853
1490
|
const defaultMaxAgeMs = 48 * 60 * 60 * 1e3;
|
|
854
1491
|
const envHours = process.env.AGENTFLOW_TRACE_WINDOW_HOURS;
|
|
855
1492
|
const envMaxAgeMs = envHours ? parseFloat(envHours) * 60 * 60 * 1e3 : void 0;
|
|
856
1493
|
if (typeof tracesDirOrOptions === "string") {
|
|
857
|
-
this.tracesDir =
|
|
1494
|
+
this.tracesDir = path2.resolve(tracesDirOrOptions);
|
|
858
1495
|
this.dataDirs = [];
|
|
859
1496
|
this.maxAgeMs = envMaxAgeMs ?? defaultMaxAgeMs;
|
|
860
1497
|
this.userConfig = {};
|
|
861
1498
|
} else {
|
|
862
|
-
this.tracesDir =
|
|
863
|
-
this.dataDirs = (tracesDirOrOptions.dataDirs || []).map((d) =>
|
|
1499
|
+
this.tracesDir = path2.resolve(tracesDirOrOptions.tracesDir);
|
|
1500
|
+
this.dataDirs = (tracesDirOrOptions.dataDirs || []).map((d) => path2.resolve(d));
|
|
864
1501
|
this.maxAgeMs = envMaxAgeMs ?? tracesDirOrOptions.maxAgeMs ?? defaultMaxAgeMs;
|
|
865
1502
|
this.userConfig = tracesDirOrOptions.userConfig ?? {};
|
|
866
1503
|
}
|
|
@@ -869,7 +1506,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
869
1506
|
...getSkipFiles(this.userConfig)
|
|
870
1507
|
]);
|
|
871
1508
|
this.userSkipDirs = new Set(getSkipDirectories(this.userConfig));
|
|
872
|
-
this.allWatchDirs = [
|
|
1509
|
+
this.allWatchDirs = [
|
|
1510
|
+
...new Set([this.tracesDir, ...this.dataDirs].map((d) => path2.resolve(d)))
|
|
1511
|
+
];
|
|
873
1512
|
this.ensureTracesDir();
|
|
874
1513
|
this.loadExistingFiles();
|
|
875
1514
|
this.archiveOldTraces();
|
|
@@ -879,9 +1518,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
879
1518
|
/** Move trace files older than maxAgeMs into archive/YYYY-MM/ subdirectories. */
|
|
880
1519
|
archiveOldTraces() {
|
|
881
1520
|
const cutoff = Date.now() - this.maxAgeMs;
|
|
882
|
-
|
|
1521
|
+
const _archived = 0;
|
|
883
1522
|
for (const dir of this.allWatchDirs) {
|
|
884
|
-
if (!
|
|
1523
|
+
if (!fs2.existsSync(dir)) continue;
|
|
885
1524
|
try {
|
|
886
1525
|
this.archiveDirectory(dir, cutoff, 0);
|
|
887
1526
|
} catch (error) {
|
|
@@ -891,27 +1530,28 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
891
1530
|
}
|
|
892
1531
|
archiveDirectory(dir, cutoff, depth) {
|
|
893
1532
|
if (depth > 10) return 0;
|
|
894
|
-
if (
|
|
1533
|
+
if (path2.basename(dir) === "archive") return 0;
|
|
895
1534
|
let archived = 0;
|
|
896
1535
|
try {
|
|
897
|
-
const entries =
|
|
1536
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
898
1537
|
for (const entry of entries) {
|
|
899
|
-
if (entry.name.startsWith(".") || entry.name === "archive" || this.userSkipDirs.has(entry.name))
|
|
900
|
-
|
|
1538
|
+
if (entry.name.startsWith(".") || entry.name === "archive" || this.userSkipDirs.has(entry.name))
|
|
1539
|
+
continue;
|
|
1540
|
+
const fullPath = path2.join(dir, entry.name);
|
|
901
1541
|
if (entry.isDirectory()) {
|
|
902
1542
|
archived += this.archiveDirectory(fullPath, cutoff, depth + 1);
|
|
903
1543
|
continue;
|
|
904
1544
|
}
|
|
905
1545
|
if (!entry.isFile() || !this.isSupportedFile(entry.name)) continue;
|
|
906
1546
|
try {
|
|
907
|
-
const stats =
|
|
1547
|
+
const stats = fs2.statSync(fullPath);
|
|
908
1548
|
if (stats.mtimeMs >= cutoff) continue;
|
|
909
1549
|
const mtime = new Date(stats.mtimeMs);
|
|
910
1550
|
const yearMonth = `${mtime.getFullYear()}-${String(mtime.getMonth() + 1).padStart(2, "0")}`;
|
|
911
|
-
const archiveDir =
|
|
912
|
-
|
|
913
|
-
const dest =
|
|
914
|
-
|
|
1551
|
+
const archiveDir = path2.join(this.tracesDir, "archive", yearMonth);
|
|
1552
|
+
fs2.mkdirSync(archiveDir, { recursive: true });
|
|
1553
|
+
const dest = path2.join(archiveDir, entry.name);
|
|
1554
|
+
fs2.renameSync(fullPath, dest);
|
|
915
1555
|
const key = this.traceKey(fullPath);
|
|
916
1556
|
this.traces.delete(key);
|
|
917
1557
|
archived++;
|
|
@@ -923,16 +1563,19 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
923
1563
|
return archived;
|
|
924
1564
|
}
|
|
925
1565
|
ensureTracesDir() {
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1566
|
+
try {
|
|
1567
|
+
if (!fs2.existsSync(this.tracesDir)) {
|
|
1568
|
+
fs2.mkdirSync(this.tracesDir, { recursive: true });
|
|
1569
|
+
console.log(`Created traces directory: ${this.tracesDir}`);
|
|
1570
|
+
}
|
|
1571
|
+
} catch {
|
|
929
1572
|
}
|
|
930
1573
|
}
|
|
931
1574
|
loadExistingFiles() {
|
|
932
1575
|
let totalFiles = 0;
|
|
933
1576
|
let totalDirectories = 0;
|
|
934
1577
|
for (const dir of this.allWatchDirs) {
|
|
935
|
-
if (!
|
|
1578
|
+
if (!fs2.existsSync(dir)) continue;
|
|
936
1579
|
try {
|
|
937
1580
|
totalDirectories++;
|
|
938
1581
|
const loadedFiles = this.scanDirectoryRecursive(dir);
|
|
@@ -950,16 +1593,16 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
950
1593
|
if (depth > 10) return 0;
|
|
951
1594
|
let fileCount = 0;
|
|
952
1595
|
try {
|
|
953
|
-
const entries =
|
|
1596
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
954
1597
|
for (const entry of entries) {
|
|
955
1598
|
if (entry.name.startsWith(".")) continue;
|
|
956
1599
|
if (entry.name === "archive") continue;
|
|
957
1600
|
if (this.userSkipDirs.has(entry.name)) continue;
|
|
958
|
-
const fullPath =
|
|
1601
|
+
const fullPath = path2.join(dir, entry.name);
|
|
959
1602
|
if (entry.isFile()) {
|
|
960
1603
|
if (this.isSupportedFile(entry.name)) {
|
|
961
1604
|
try {
|
|
962
|
-
const mtime =
|
|
1605
|
+
const mtime = fs2.statSync(fullPath).mtimeMs;
|
|
963
1606
|
if (Date.now() - mtime > this.maxAgeMs) continue;
|
|
964
1607
|
} catch {
|
|
965
1608
|
continue;
|
|
@@ -1011,7 +1654,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1011
1654
|
];
|
|
1012
1655
|
/** Load a file using the adapter registry, falling back to built-in parsing. */
|
|
1013
1656
|
loadFile(filePath) {
|
|
1014
|
-
const filename =
|
|
1657
|
+
const filename = path2.basename(filePath);
|
|
1015
1658
|
if (this.skipFiles.has(filename)) return false;
|
|
1016
1659
|
if (_TraceWatcher.SKIP_SUFFIXES.some((s) => filename.endsWith(s))) return false;
|
|
1017
1660
|
const adapter = findAdapter(filePath);
|
|
@@ -1064,9 +1707,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1064
1707
|
metadata: { ...trace.metadata, adapterSource: adapterName },
|
|
1065
1708
|
sessionEvents: trace.sessionEvents ?? [],
|
|
1066
1709
|
sourceType: "session",
|
|
1067
|
-
filename:
|
|
1710
|
+
filename: path2.basename(filePath),
|
|
1068
1711
|
lastModified: Date.now(),
|
|
1069
|
-
sourceDir:
|
|
1712
|
+
sourceDir: path2.dirname(filePath)
|
|
1070
1713
|
};
|
|
1071
1714
|
const key = `${adapterName}:${trace.id}`;
|
|
1072
1715
|
this.traces.set(key, watched);
|
|
@@ -1079,9 +1722,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1079
1722
|
}
|
|
1080
1723
|
loadLogFile(filePath) {
|
|
1081
1724
|
try {
|
|
1082
|
-
const content =
|
|
1083
|
-
const filename =
|
|
1084
|
-
const stats =
|
|
1725
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
1726
|
+
const filename = path2.basename(filePath);
|
|
1727
|
+
const stats = fs2.statSync(filePath);
|
|
1085
1728
|
if (filename.startsWith("openclaw-") || filePath.includes("openclaw")) {
|
|
1086
1729
|
const result = this.loadOpenClawLogFile(content, filename, filePath, stats);
|
|
1087
1730
|
if (result) return true;
|
|
@@ -1099,8 +1742,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1099
1742
|
trace.filename = filename;
|
|
1100
1743
|
trace.lastModified = stats.mtime.getTime();
|
|
1101
1744
|
trace.sourceType = trace.sourceType || "trace";
|
|
1102
|
-
trace.sourceDir =
|
|
1103
|
-
const
|
|
1745
|
+
trace.sourceDir = path2.dirname(filePath);
|
|
1746
|
+
const traceAgentId = trace.agentId;
|
|
1747
|
+
const key = traces.length === 1 ? this.traceKey(filePath, traceAgentId) : `${this.traceKey(filePath, traceAgentId)}-${i}`;
|
|
1104
1748
|
this.traces.set(key, trace);
|
|
1105
1749
|
}
|
|
1106
1750
|
return traces.length > 0;
|
|
@@ -1161,7 +1805,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1161
1805
|
trace.sourceType = "session";
|
|
1162
1806
|
}
|
|
1163
1807
|
if (traces.length === 0) {
|
|
1164
|
-
const stats =
|
|
1808
|
+
const stats = fs2.statSync(filePath);
|
|
1165
1809
|
traces.push({
|
|
1166
1810
|
id: "",
|
|
1167
1811
|
rootNodeId: "root",
|
|
@@ -1201,10 +1845,10 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1201
1845
|
const pathAgent = this.extractAgentFromPath(filePath);
|
|
1202
1846
|
const detection = getAgentDetection(this.userConfig);
|
|
1203
1847
|
if (detection.filePatterns) {
|
|
1204
|
-
const
|
|
1848
|
+
const basename4 = path2.basename(filePath, path2.extname(filePath));
|
|
1205
1849
|
for (const [pattern, template] of Object.entries(detection.filePatterns)) {
|
|
1206
1850
|
const re = new RegExp(`^(${pattern})$`);
|
|
1207
|
-
const match =
|
|
1851
|
+
const match = basename4.match(re);
|
|
1208
1852
|
if (match) {
|
|
1209
1853
|
const resolved = template.replace("${match}", match[1]);
|
|
1210
1854
|
return this.normaliseAgentId(resolved);
|
|
@@ -1214,8 +1858,8 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1214
1858
|
return this.normaliseAgentId(pathAgent);
|
|
1215
1859
|
}
|
|
1216
1860
|
extractAgentFromPath(filePath) {
|
|
1217
|
-
const filename =
|
|
1218
|
-
const pathParts = filePath.split(
|
|
1861
|
+
const filename = path2.basename(filePath, path2.extname(filePath));
|
|
1862
|
+
const pathParts = filePath.split(path2.sep);
|
|
1219
1863
|
const detection = getAgentDetection(this.userConfig);
|
|
1220
1864
|
let pathPrefix = "";
|
|
1221
1865
|
if (detection.pathPatterns) {
|
|
@@ -1286,7 +1930,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1286
1930
|
if ((inner == null ? void 0 : inner.payloads) && ((_a = inner == null ? void 0 : inner.meta) == null ? void 0 : _a.agentMeta)) {
|
|
1287
1931
|
const agentMeta = inner.meta.agentMeta;
|
|
1288
1932
|
const sessionId = agentMeta.sessionId || "unknown";
|
|
1289
|
-
const agentName = openClawSessionIdToAgent(sessionId);
|
|
1933
|
+
const agentName = openClawSessionIdToAgent(sessionId, this.sessionAgentMap);
|
|
1290
1934
|
const timestamp = parsed.time ? new Date(parsed.time).getTime() : ((_b = parsed._meta) == null ? void 0 : _b.date) ? new Date(parsed._meta.date).getTime() : stats.mtime.getTime();
|
|
1291
1935
|
const texts = (inner.payloads || []).map((p) => p.text || "").filter(Boolean);
|
|
1292
1936
|
if (!sessions.has(sessionId)) {
|
|
@@ -1310,7 +1954,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1310
1954
|
if (parsed.payloads && ((_d = parsed.meta) == null ? void 0 : _d.agentMeta)) {
|
|
1311
1955
|
const agentMeta = parsed.meta.agentMeta;
|
|
1312
1956
|
const sessionId = agentMeta.sessionId || "unknown";
|
|
1313
|
-
const agentName = openClawSessionIdToAgent(sessionId);
|
|
1957
|
+
const agentName = openClawSessionIdToAgent(sessionId, this.sessionAgentMap);
|
|
1314
1958
|
const timestamp = parsed.time ? new Date(parsed.time).getTime() : ((_e = parsed._meta) == null ? void 0 : _e.date) ? new Date(parsed._meta.date).getTime() : stats.mtime.getTime();
|
|
1315
1959
|
const texts = (parsed.payloads || []).map((p) => p.text || "").filter(Boolean);
|
|
1316
1960
|
if (!sessions.has(sessionId)) {
|
|
@@ -1418,7 +2062,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1418
2062
|
filename,
|
|
1419
2063
|
lastModified: stats.mtime.getTime(),
|
|
1420
2064
|
sourceType: "session",
|
|
1421
|
-
sourceDir:
|
|
2065
|
+
sourceDir: path2.dirname(filePath),
|
|
1422
2066
|
sessionEvents,
|
|
1423
2067
|
tokenUsage: {
|
|
1424
2068
|
input: totalInput,
|
|
@@ -1433,7 +2077,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1433
2077
|
source: "openclaw-log"
|
|
1434
2078
|
}
|
|
1435
2079
|
};
|
|
1436
|
-
const key = sessions.size === 1 ? this.traceKey(filePath) : `${this.traceKey(filePath)}-${traceIndex}`;
|
|
2080
|
+
const key = sessions.size === 1 ? this.traceKey(filePath, agentId) : `${this.traceKey(filePath, agentId)}-${traceIndex}`;
|
|
1437
2081
|
this.traces.set(key, trace);
|
|
1438
2082
|
traceIndex++;
|
|
1439
2083
|
}
|
|
@@ -1441,23 +2085,26 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1441
2085
|
}
|
|
1442
2086
|
loadTraceFile(filePath) {
|
|
1443
2087
|
try {
|
|
1444
|
-
const content =
|
|
1445
|
-
const filename =
|
|
2088
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
2089
|
+
const filename = path2.basename(filePath);
|
|
1446
2090
|
if (filename === "sessions.json") {
|
|
1447
2091
|
return this.loadSessionsIndex(filePath, content);
|
|
1448
2092
|
}
|
|
1449
2093
|
const graph = loadGraph(content);
|
|
1450
|
-
const stats =
|
|
2094
|
+
const stats = fs2.statSync(filePath);
|
|
1451
2095
|
graph.filename = filename;
|
|
1452
2096
|
graph.lastModified = stats.mtime.getTime();
|
|
1453
2097
|
graph.sourceType = "trace";
|
|
1454
|
-
graph.sourceDir =
|
|
2098
|
+
graph.sourceDir = path2.dirname(filePath);
|
|
2099
|
+
if (!graph.agentId || graph.agentId === "unknown") {
|
|
2100
|
+
graph.agentId = this.extractAgentFromPath(filePath);
|
|
2101
|
+
}
|
|
1455
2102
|
if (graph.nodes instanceof Map) {
|
|
1456
2103
|
for (const node of graph.nodes.values()) {
|
|
1457
2104
|
if (!node.children) node.children = [];
|
|
1458
2105
|
}
|
|
1459
2106
|
}
|
|
1460
|
-
this.traces.set(this.traceKey(filePath), graph);
|
|
2107
|
+
this.traces.set(this.traceKey(filePath, graph.agentId), graph);
|
|
1461
2108
|
return true;
|
|
1462
2109
|
} catch {
|
|
1463
2110
|
return false;
|
|
@@ -1465,11 +2112,12 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1465
2112
|
}
|
|
1466
2113
|
/** Parse sessions.json index to discover agents and their sessions. */
|
|
1467
2114
|
loadSessionsIndex(filePath, content) {
|
|
2115
|
+
var _a;
|
|
1468
2116
|
try {
|
|
1469
2117
|
const data = JSON.parse(content);
|
|
1470
2118
|
if (typeof data !== "object" || data === null) return false;
|
|
1471
|
-
const stats =
|
|
1472
|
-
const pathParts = filePath.split(
|
|
2119
|
+
const stats = fs2.statSync(filePath);
|
|
2120
|
+
const pathParts = filePath.split(path2.sep);
|
|
1473
2121
|
const agentsIndex = pathParts.lastIndexOf("agents");
|
|
1474
2122
|
if (agentsIndex === -1 || agentsIndex + 1 >= pathParts.length) return false;
|
|
1475
2123
|
const agentName = pathParts[agentsIndex + 1];
|
|
@@ -1480,11 +2128,19 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1480
2128
|
const session = sessionData;
|
|
1481
2129
|
const sessionId = session.sessionId;
|
|
1482
2130
|
if (!sessionId) continue;
|
|
2131
|
+
const resolvedAgentId = parseOpenClawSessionKey(sessionKey) ?? agentId;
|
|
2132
|
+
this.sessionAgentMap.set(String(sessionId), resolvedAgentId);
|
|
1483
2133
|
const existingKey = Array.from(this.traces.keys()).find((k) => {
|
|
1484
2134
|
const t = this.traces.get(k);
|
|
1485
2135
|
return (t == null ? void 0 : t.id) === sessionId || (t == null ? void 0 : t.traceId) === sessionId;
|
|
1486
2136
|
});
|
|
1487
|
-
if (existingKey)
|
|
2137
|
+
if (existingKey) {
|
|
2138
|
+
const existing = this.traces.get(existingKey);
|
|
2139
|
+
if (existing && (existing.agentId === agentId || ((_a = existing.agentId) == null ? void 0 : _a.startsWith("openclaw-main")))) {
|
|
2140
|
+
existing.agentId = resolvedAgentId;
|
|
2141
|
+
}
|
|
2142
|
+
continue;
|
|
2143
|
+
}
|
|
1488
2144
|
const updatedAt = session.updatedAt || stats.mtime.getTime();
|
|
1489
2145
|
const label = session.label || sessionKey.split(":").pop() || sessionId;
|
|
1490
2146
|
const chatType = session.chatType || (sessionKey.includes("cron") ? "cron" : "direct");
|
|
@@ -1513,7 +2169,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1513
2169
|
edges: [],
|
|
1514
2170
|
events: [],
|
|
1515
2171
|
startTime: updatedAt,
|
|
1516
|
-
agentId,
|
|
2172
|
+
agentId: resolvedAgentId,
|
|
1517
2173
|
trigger,
|
|
1518
2174
|
name: label,
|
|
1519
2175
|
traceId: sessionId,
|
|
@@ -1521,7 +2177,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1521
2177
|
filename: `${agentName}-${sessionId.slice(0, 8)}.index`,
|
|
1522
2178
|
lastModified: updatedAt,
|
|
1523
2179
|
sourceType: "session",
|
|
1524
|
-
sourceDir:
|
|
2180
|
+
sourceDir: path2.dirname(filePath),
|
|
1525
2181
|
sessionEvents: [],
|
|
1526
2182
|
tokenUsage: { input: 0, output: 0, total: 0, cost: 0 },
|
|
1527
2183
|
metadata: {
|
|
@@ -1531,7 +2187,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1531
2187
|
agentName
|
|
1532
2188
|
}
|
|
1533
2189
|
};
|
|
1534
|
-
const key = `${this.traceKey(filePath)}-${sessionId.slice(0, 12)}`;
|
|
2190
|
+
const key = `${this.traceKey(filePath, agentId)}-${sessionId.slice(0, 12)}`;
|
|
1535
2191
|
this.traces.set(key, trace);
|
|
1536
2192
|
loaded++;
|
|
1537
2193
|
}
|
|
@@ -1544,7 +2200,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1544
2200
|
loadSessionFile(filePath) {
|
|
1545
2201
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
|
|
1546
2202
|
try {
|
|
1547
|
-
const content =
|
|
2203
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
1548
2204
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
1549
2205
|
if (lines.length === 0) return false;
|
|
1550
2206
|
const rawEvents = [];
|
|
@@ -1560,13 +2216,13 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1560
2216
|
return this.loadCronRunFile(rawEvents, filePath);
|
|
1561
2217
|
}
|
|
1562
2218
|
const sessionEvent = rawEvents.find((e) => e.type === "session");
|
|
1563
|
-
const sessionId = (sessionEvent == null ? void 0 : sessionEvent.id) ||
|
|
2219
|
+
const sessionId = (sessionEvent == null ? void 0 : sessionEvent.id) || path2.basename(filePath, ".jsonl");
|
|
1564
2220
|
const sessionTimestamp = (sessionEvent == null ? void 0 : sessionEvent.timestamp) || ((_a = rawEvents[0]) == null ? void 0 : _a.timestamp);
|
|
1565
2221
|
const startTime = sessionTimestamp ? new Date(sessionTimestamp).getTime() : 0;
|
|
1566
2222
|
if (!startTime) return false;
|
|
1567
|
-
const parentDir =
|
|
1568
|
-
const grandParentDir =
|
|
1569
|
-
const greatGrandParentDir =
|
|
2223
|
+
const parentDir = path2.basename(path2.dirname(filePath));
|
|
2224
|
+
const grandParentDir = path2.basename(path2.dirname(path2.dirname(filePath)));
|
|
2225
|
+
const greatGrandParentDir = path2.basename(path2.dirname(path2.dirname(path2.dirname(filePath))));
|
|
1570
2226
|
let agentName;
|
|
1571
2227
|
if (parentDir === "sessions" && greatGrandParentDir === "agents") {
|
|
1572
2228
|
agentName = grandParentDir;
|
|
@@ -1585,6 +2241,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1585
2241
|
}
|
|
1586
2242
|
}
|
|
1587
2243
|
}
|
|
2244
|
+
if (this.sessionAgentMap.has(sessionId)) {
|
|
2245
|
+
agentId = this.sessionAgentMap.get(sessionId);
|
|
2246
|
+
}
|
|
1588
2247
|
const modelEvent = rawEvents.find((e) => e.type === "model_change");
|
|
1589
2248
|
const provider = (modelEvent == null ? void 0 : modelEvent.provider) || "";
|
|
1590
2249
|
const modelId = (modelEvent == null ? void 0 : modelEvent.modelId) || "";
|
|
@@ -1788,7 +2447,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1788
2447
|
if (role === "toolResult") {
|
|
1789
2448
|
const toolCallId = ((_j = contentBlocks[0]) == null ? void 0 : _j.toolCallId) || evt.parentId;
|
|
1790
2449
|
const resultContent = contentBlocks.map((b) => b.text || b.content || "").join("\n");
|
|
1791
|
-
const hasError = contentBlocks.some(
|
|
2450
|
+
const hasError = contentBlocks.some(
|
|
2451
|
+
(b) => b.isError || b.error
|
|
2452
|
+
);
|
|
1792
2453
|
const errorText = hasError ? resultContent : void 0;
|
|
1793
2454
|
sessionEvents.push({
|
|
1794
2455
|
type: "tool_result",
|
|
@@ -1816,7 +2477,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1816
2477
|
}
|
|
1817
2478
|
}
|
|
1818
2479
|
}
|
|
1819
|
-
const fileStat =
|
|
2480
|
+
const fileStat = fs2.statSync(filePath);
|
|
1820
2481
|
const fileAge = Date.now() - fileStat.mtime.getTime();
|
|
1821
2482
|
const lastEvt = rawEvents[rawEvents.length - 1];
|
|
1822
2483
|
const hasToolError = sessionEvents.some((e) => e.type === "tool_result" && e.toolError);
|
|
@@ -1864,7 +2525,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1864
2525
|
"gen_ai.request.model": modelId
|
|
1865
2526
|
}
|
|
1866
2527
|
});
|
|
1867
|
-
const filename =
|
|
2528
|
+
const filename = path2.basename(filePath);
|
|
1868
2529
|
const trace = {
|
|
1869
2530
|
id: sessionId,
|
|
1870
2531
|
nodes,
|
|
@@ -1880,7 +2541,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1880
2541
|
filename,
|
|
1881
2542
|
lastModified: fileStat.mtime.getTime(),
|
|
1882
2543
|
sourceType: "session",
|
|
1883
|
-
sourceDir:
|
|
2544
|
+
sourceDir: path2.dirname(filePath),
|
|
1884
2545
|
sessionEvents,
|
|
1885
2546
|
tokenUsage,
|
|
1886
2547
|
metadata: {
|
|
@@ -1894,93 +2555,180 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
1894
2555
|
sessionVersion: sessionEvent == null ? void 0 : sessionEvent.version
|
|
1895
2556
|
}
|
|
1896
2557
|
};
|
|
1897
|
-
this.traces.set(this.traceKey(filePath), trace);
|
|
2558
|
+
this.traces.set(this.traceKey(filePath, agentId), trace);
|
|
1898
2559
|
return true;
|
|
1899
2560
|
} catch {
|
|
1900
2561
|
return false;
|
|
1901
2562
|
}
|
|
1902
2563
|
}
|
|
1903
|
-
/** Parse cron run JSONL files
|
|
2564
|
+
/** Parse cron run JSONL files — creates one trace per execution run. */
|
|
1904
2565
|
loadCronRunFile(rawEvents, filePath) {
|
|
1905
|
-
var _a, _b, _c;
|
|
2566
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
1906
2567
|
try {
|
|
1907
|
-
const filename =
|
|
1908
|
-
const
|
|
1909
|
-
const fileStat =
|
|
1910
|
-
const
|
|
1911
|
-
let
|
|
2568
|
+
const filename = path2.basename(filePath);
|
|
2569
|
+
const fileJobId = String(((_a = rawEvents[0]) == null ? void 0 : _a.jobId) || path2.basename(filePath, ".jsonl"));
|
|
2570
|
+
const fileStat = fs2.statSync(filePath);
|
|
2571
|
+
const agentId = `openclaw:${fileJobId}`;
|
|
2572
|
+
let loaded = 0;
|
|
1912
2573
|
for (const evt of rawEvents) {
|
|
1913
2574
|
const ts = evt.ts || Date.now();
|
|
1914
|
-
const
|
|
1915
|
-
const
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
2575
|
+
const runAtMs = evt.runAtMs || ts;
|
|
2576
|
+
const durationMs = evt.durationMs || 0;
|
|
2577
|
+
const status = evt.status === "ok" ? "completed" : "failed";
|
|
2578
|
+
const sessionId = String(evt.sessionId || `${fileJobId}-${ts}`);
|
|
2579
|
+
const summary = String(evt.summary || evt.error || "");
|
|
2580
|
+
const model = String(evt.model || "");
|
|
2581
|
+
const usage = evt.usage || {};
|
|
2582
|
+
const rootId = `cron-${sessionId.slice(0, 12)}`;
|
|
2583
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
2584
|
+
const children = [];
|
|
2585
|
+
nodes.set(rootId, {
|
|
2586
|
+
id: rootId,
|
|
2587
|
+
type: "agent",
|
|
2588
|
+
name: `${fileJobId} run`,
|
|
2589
|
+
startTime: runAtMs,
|
|
2590
|
+
endTime: runAtMs + durationMs,
|
|
2591
|
+
status,
|
|
2592
|
+
parentId: void 0,
|
|
2593
|
+
children,
|
|
2594
|
+
metadata: {
|
|
2595
|
+
jobId: fileJobId,
|
|
2596
|
+
model,
|
|
2597
|
+
sessionId,
|
|
2598
|
+
durationMs
|
|
2599
|
+
}
|
|
1923
2600
|
});
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
2601
|
+
let stepIdx = 0;
|
|
2602
|
+
const tablePattern = /\|\s*\*?\*?(\d+)\.\s*\*?\*?([^|]+)\*?\*?\s*\|\s*([^|]+)\s*\|\s*([^|]*)\|/g;
|
|
2603
|
+
let m;
|
|
2604
|
+
while ((m = tablePattern.exec(summary)) !== null) {
|
|
2605
|
+
stepIdx++;
|
|
2606
|
+
const stepName = ((_b = m[2]) == null ? void 0 : _b.trim().replace(/\*+/g, "")) || `Step ${stepIdx}`;
|
|
2607
|
+
const stepDetail = ((_c = m[4]) == null ? void 0 : _c.trim()) || "";
|
|
2608
|
+
const nodeId = `step-${stepIdx}`;
|
|
2609
|
+
children.push(nodeId);
|
|
2610
|
+
nodes.set(nodeId, {
|
|
2611
|
+
id: nodeId,
|
|
2612
|
+
type: "tool",
|
|
2613
|
+
name: stepName,
|
|
2614
|
+
startTime: runAtMs + (stepIdx - 1) * Math.floor(durationMs / Math.max(1, stepIdx + 1)),
|
|
2615
|
+
endTime: runAtMs + stepIdx * Math.floor(durationMs / Math.max(1, stepIdx + 1)),
|
|
2616
|
+
status: ((_d = m[3]) == null ? void 0 : _d.includes("\u274C")) || ((_e = m[3]) == null ? void 0 : _e.includes("Fail")) ? "failed" : "completed",
|
|
2617
|
+
parentId: rootId,
|
|
2618
|
+
children: [],
|
|
2619
|
+
metadata: { detail: stepDetail }
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
if (stepIdx === 0) {
|
|
2623
|
+
const listPattern = /^\s*(\d+)\.\s*\*?\*?([^*\n]+)\*?\*?\s*(?:[-—]\s*(.+))?$/gm;
|
|
2624
|
+
while ((m = listPattern.exec(summary)) !== null) {
|
|
2625
|
+
if ((_f = m[2]) == null ? void 0 : _f.trim().startsWith("#")) continue;
|
|
2626
|
+
stepIdx++;
|
|
2627
|
+
const stepName = ((_g = m[2]) == null ? void 0 : _g.trim().replace(/\*+/g, "")) || `Step ${stepIdx}`;
|
|
2628
|
+
const stepDetail = ((_h = m[3]) == null ? void 0 : _h.trim()) || "";
|
|
2629
|
+
const nodeId = `step-${stepIdx}`;
|
|
2630
|
+
children.push(nodeId);
|
|
2631
|
+
nodes.set(nodeId, {
|
|
2632
|
+
id: nodeId,
|
|
2633
|
+
type: "tool",
|
|
2634
|
+
name: stepName,
|
|
2635
|
+
startTime: runAtMs + (stepIdx - 1) * Math.floor(durationMs / Math.max(1, stepIdx + 1)),
|
|
2636
|
+
endTime: runAtMs + stepIdx * Math.floor(durationMs / Math.max(1, stepIdx + 1)),
|
|
2637
|
+
status: stepDetail.toLowerCase().includes("fail") ? "failed" : "completed",
|
|
2638
|
+
parentId: rootId,
|
|
2639
|
+
children: [],
|
|
2640
|
+
metadata: { detail: stepDetail }
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
if (stepIdx === 0 && summary) {
|
|
2645
|
+
const sumId = "summary-0";
|
|
2646
|
+
children.push(sumId);
|
|
2647
|
+
nodes.set(sumId, {
|
|
2648
|
+
id: sumId,
|
|
2649
|
+
type: "tool",
|
|
2650
|
+
name: status === "completed" ? "Execution" : evt.error ? String(evt.error) : "Execution",
|
|
2651
|
+
startTime: runAtMs,
|
|
2652
|
+
endTime: runAtMs + durationMs,
|
|
2653
|
+
status,
|
|
2654
|
+
parentId: rootId,
|
|
2655
|
+
children: [],
|
|
2656
|
+
metadata: { summary: summary.slice(0, 500) }
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
const sessionEvents = [
|
|
2660
|
+
{
|
|
2661
|
+
type: "system",
|
|
2662
|
+
timestamp: runAtMs,
|
|
2663
|
+
name: `${fileJobId} started`,
|
|
2664
|
+
content: `Model: ${model}`,
|
|
2665
|
+
id: `start-${ts}`
|
|
2666
|
+
}
|
|
2667
|
+
];
|
|
2668
|
+
if (summary) {
|
|
2669
|
+
sessionEvents.push({
|
|
2670
|
+
type: "assistant",
|
|
2671
|
+
timestamp: runAtMs + durationMs,
|
|
2672
|
+
name: status === "completed" ? "Completed" : "Failed",
|
|
2673
|
+
content: summary,
|
|
2674
|
+
id: `result-${ts}`
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
const trace = {
|
|
2678
|
+
id: sessionId,
|
|
2679
|
+
nodes,
|
|
2680
|
+
edges: [],
|
|
2681
|
+
events: [],
|
|
2682
|
+
startTime: runAtMs,
|
|
2683
|
+
agentId,
|
|
2684
|
+
trigger: "cron",
|
|
2685
|
+
name: `${fileJobId} ${new Date(runAtMs).toISOString().slice(0, 16)}`,
|
|
2686
|
+
traceId: sessionId,
|
|
2687
|
+
spanId: sessionId,
|
|
2688
|
+
filename,
|
|
2689
|
+
lastModified: fileStat.mtime.getTime(),
|
|
2690
|
+
sourceType: "session",
|
|
2691
|
+
sourceDir: path2.dirname(filePath),
|
|
2692
|
+
sessionEvents,
|
|
2693
|
+
tokenUsage: {
|
|
2694
|
+
input: usage.promptTokens || usage.input || 0,
|
|
2695
|
+
output: usage.completionTokens || usage.output || 0,
|
|
2696
|
+
total: usage.totalTokens || usage.total || 0,
|
|
2697
|
+
cost: 0
|
|
2698
|
+
},
|
|
2699
|
+
metadata: { jobId: fileJobId, model, source: "cron-run", sessionId }
|
|
2700
|
+
};
|
|
2701
|
+
const key = `${this.traceKey(filePath, agentId)}-${sessionId.slice(0, 12)}`;
|
|
2702
|
+
this.traces.set(key, trace);
|
|
2703
|
+
loaded++;
|
|
2704
|
+
}
|
|
2705
|
+
return loaded > 0;
|
|
1961
2706
|
} catch {
|
|
1962
2707
|
return false;
|
|
1963
2708
|
}
|
|
1964
2709
|
}
|
|
1965
|
-
/** Unique key for a file across directories. */
|
|
1966
|
-
traceKey(filePath) {
|
|
2710
|
+
/** Unique key for a file across directories. Includes agentId to prevent collisions between agents. */
|
|
2711
|
+
traceKey(filePath, agentId) {
|
|
2712
|
+
let fileKey;
|
|
1967
2713
|
for (const dir of this.allWatchDirs) {
|
|
1968
2714
|
if (filePath.startsWith(dir)) {
|
|
1969
|
-
const dirParts = dir.split(
|
|
2715
|
+
const dirParts = dir.split(path2.sep).filter(Boolean);
|
|
1970
2716
|
const dirSuffix = dirParts.slice(-2).join("/");
|
|
1971
|
-
|
|
2717
|
+
fileKey = `${path2.relative(dir, filePath).replace(/\\/g, "/")}@${dirSuffix}`;
|
|
2718
|
+
return agentId ? `${fileKey}#${agentId}` : fileKey;
|
|
1972
2719
|
}
|
|
1973
2720
|
}
|
|
1974
|
-
|
|
2721
|
+
fileKey = filePath;
|
|
2722
|
+
return agentId ? `${fileKey}#${agentId}` : fileKey;
|
|
1975
2723
|
}
|
|
1976
2724
|
startWatching() {
|
|
1977
2725
|
for (const dir of this.allWatchDirs) {
|
|
1978
|
-
if (!
|
|
2726
|
+
if (!fs2.existsSync(dir)) continue;
|
|
1979
2727
|
const patterns = [
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2728
|
+
path2.join(dir, "**/*.json"),
|
|
2729
|
+
path2.join(dir, "**/*.jsonl"),
|
|
2730
|
+
path2.join(dir, "**/*.log"),
|
|
2731
|
+
path2.join(dir, "**/*.trace")
|
|
1984
2732
|
];
|
|
1985
2733
|
const watcher = chokidar.watch(patterns, {
|
|
1986
2734
|
ignored: [
|
|
@@ -2006,9 +2754,9 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
2006
2754
|
// Allow deep nesting for OpenClaw agents/*/sessions/
|
|
2007
2755
|
});
|
|
2008
2756
|
watcher.on("add", (filePath) => {
|
|
2009
|
-
if (this.isSupportedFile(
|
|
2010
|
-
const relativePath =
|
|
2011
|
-
console.log(`New file: ${relativePath} (in ${
|
|
2757
|
+
if (this.isSupportedFile(path2.basename(filePath))) {
|
|
2758
|
+
const relativePath = path2.relative(dir, filePath);
|
|
2759
|
+
console.log(`New file: ${relativePath} (in ${path2.basename(dir)})`);
|
|
2012
2760
|
if (this.loadFile(filePath)) {
|
|
2013
2761
|
const key = this.traceKey(filePath);
|
|
2014
2762
|
const trace = this.traces.get(key);
|
|
@@ -2019,7 +2767,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
2019
2767
|
}
|
|
2020
2768
|
});
|
|
2021
2769
|
watcher.on("change", (filePath) => {
|
|
2022
|
-
if (this.isSupportedFile(
|
|
2770
|
+
if (this.isSupportedFile(path2.basename(filePath))) {
|
|
2023
2771
|
if (this.loadFile(filePath)) {
|
|
2024
2772
|
const key = this.traceKey(filePath);
|
|
2025
2773
|
const trace = this.traces.get(key);
|
|
@@ -2030,7 +2778,7 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
2030
2778
|
}
|
|
2031
2779
|
});
|
|
2032
2780
|
watcher.on("unlink", (filePath) => {
|
|
2033
|
-
if (this.isSupportedFile(
|
|
2781
|
+
if (this.isSupportedFile(path2.basename(filePath))) {
|
|
2034
2782
|
const key = this.traceKey(filePath);
|
|
2035
2783
|
this.traces.delete(key);
|
|
2036
2784
|
this.emit("trace-removed", key);
|
|
@@ -2054,6 +2802,10 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
2054
2802
|
let candidates = [];
|
|
2055
2803
|
const exact = this.traces.get(filename);
|
|
2056
2804
|
if (exact) candidates.push(exact);
|
|
2805
|
+
if (agentId && !filename.includes("#")) {
|
|
2806
|
+
const agentKeyed = this.traces.get(`${filename}#${agentId}`);
|
|
2807
|
+
if (agentKeyed) candidates.push(agentKeyed);
|
|
2808
|
+
}
|
|
2057
2809
|
if (filename.includes("::")) {
|
|
2058
2810
|
const [fname, startTimeStr] = filename.split("::");
|
|
2059
2811
|
const startTime = Number(startTimeStr);
|
|
@@ -2074,19 +2826,21 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
2074
2826
|
candidates.push(trace);
|
|
2075
2827
|
}
|
|
2076
2828
|
}
|
|
2829
|
+
candidates = [...new Set(candidates)];
|
|
2077
2830
|
if (candidates.length === 0) return void 0;
|
|
2078
2831
|
if (candidates.length === 1) return candidates[0];
|
|
2079
2832
|
if (agentId) {
|
|
2080
2833
|
const agentMatches = candidates.filter((c) => c.agentId === agentId);
|
|
2081
|
-
if (agentMatches.length
|
|
2082
|
-
|
|
2083
|
-
|
|
2834
|
+
if (agentMatches.length === 1) return agentMatches[0];
|
|
2835
|
+
if (agentMatches.length > 1) candidates = agentMatches;
|
|
2836
|
+
if (agentMatches.length === 0) return void 0;
|
|
2084
2837
|
}
|
|
2085
|
-
if (candidates.length === 1) return candidates[0];
|
|
2086
2838
|
let best = candidates[0];
|
|
2839
|
+
if (!best) return void 0;
|
|
2087
2840
|
let bestNodeCount = best.nodes instanceof Map ? best.nodes.size : Object.keys(best.nodes ?? {}).length;
|
|
2088
2841
|
for (let i = 1; i < candidates.length; i++) {
|
|
2089
2842
|
const c = candidates[i];
|
|
2843
|
+
if (!c) continue;
|
|
2090
2844
|
const nc = c.nodes instanceof Map ? c.nodes.size : Object.keys(c.nodes ?? {}).length;
|
|
2091
2845
|
if (nc > bestNodeCount) {
|
|
2092
2846
|
best = c;
|
|
@@ -2137,12 +2891,14 @@ var TraceWatcher = class _TraceWatcher extends EventEmitter {
|
|
|
2137
2891
|
};
|
|
2138
2892
|
|
|
2139
2893
|
// src/cli.ts
|
|
2140
|
-
import * as
|
|
2894
|
+
import * as fs3 from "fs";
|
|
2141
2895
|
import * as os from "os";
|
|
2142
|
-
import * as
|
|
2896
|
+
import * as path3 from "path";
|
|
2143
2897
|
import { fileURLToPath } from "url";
|
|
2144
|
-
var __cliDirname =
|
|
2145
|
-
var VERSION = JSON.parse(
|
|
2898
|
+
var __cliDirname = path3.dirname(fileURLToPath(import.meta.url));
|
|
2899
|
+
var VERSION = JSON.parse(
|
|
2900
|
+
fs3.readFileSync(path3.resolve(__cliDirname, "../package.json"), "utf-8")
|
|
2901
|
+
).version;
|
|
2146
2902
|
function getLanAddress() {
|
|
2147
2903
|
const interfaces = os.networkInterfaces();
|
|
2148
2904
|
for (const name of Object.keys(interfaces)) {
|
|
@@ -2246,9 +3002,9 @@ async function startDashboard() {
|
|
|
2246
3002
|
if (!config.somaVault && process.env.SOMA_VAULT) {
|
|
2247
3003
|
config.somaVault = process.env.SOMA_VAULT;
|
|
2248
3004
|
}
|
|
2249
|
-
const tracesPath =
|
|
2250
|
-
if (!
|
|
2251
|
-
|
|
3005
|
+
const tracesPath = path3.resolve(config.tracesDir);
|
|
3006
|
+
if (!fs3.existsSync(tracesPath)) {
|
|
3007
|
+
fs3.mkdirSync(tracesPath, { recursive: true });
|
|
2252
3008
|
}
|
|
2253
3009
|
config.tracesDir = tracesPath;
|
|
2254
3010
|
console.log("\nStarting AgentFlow Dashboard...\n");
|
|
@@ -2320,13 +3076,77 @@ Examples:
|
|
|
2320
3076
|
|
|
2321
3077
|
// src/server.ts
|
|
2322
3078
|
var __filename = fileURLToPath2(import.meta.url);
|
|
2323
|
-
var __dirname =
|
|
3079
|
+
var __dirname = path4.dirname(__filename);
|
|
3080
|
+
function safePath(segment) {
|
|
3081
|
+
return path4.basename(segment.replace(/\.\./g, ""));
|
|
3082
|
+
}
|
|
3083
|
+
function parseVaultFrontmatter(content) {
|
|
3084
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
3085
|
+
if (!fmMatch) return null;
|
|
3086
|
+
const fm = {};
|
|
3087
|
+
const lines = fmMatch[1].split("\n");
|
|
3088
|
+
let currentKey = "";
|
|
3089
|
+
let collectingList = null;
|
|
3090
|
+
for (const line of lines) {
|
|
3091
|
+
if (line.match(/^\s*-\s/) && currentKey) {
|
|
3092
|
+
if (!collectingList) collectingList = [];
|
|
3093
|
+
const val2 = line.replace(/^\s*-\s*/, "").trim().replace(/^["']|["']$/g, "");
|
|
3094
|
+
collectingList.push(val2);
|
|
3095
|
+
continue;
|
|
3096
|
+
}
|
|
3097
|
+
if (collectingList && currentKey) {
|
|
3098
|
+
fm[currentKey] = collectingList;
|
|
3099
|
+
collectingList = null;
|
|
3100
|
+
}
|
|
3101
|
+
const colonIdx = line.indexOf(":");
|
|
3102
|
+
if (colonIdx === -1 || line.startsWith(" ")) continue;
|
|
3103
|
+
currentKey = line.slice(0, colonIdx).trim();
|
|
3104
|
+
let val = line.slice(colonIdx + 1).trim();
|
|
3105
|
+
if (val === "") continue;
|
|
3106
|
+
if (val.startsWith("[") && val.endsWith("]")) {
|
|
3107
|
+
try {
|
|
3108
|
+
fm[currentKey] = JSON.parse(val);
|
|
3109
|
+
} catch {
|
|
3110
|
+
fm[currentKey] = val;
|
|
3111
|
+
}
|
|
3112
|
+
currentKey = "";
|
|
3113
|
+
continue;
|
|
3114
|
+
}
|
|
3115
|
+
if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
|
|
3116
|
+
val = val.slice(1, -1);
|
|
3117
|
+
}
|
|
3118
|
+
if (val === "true") fm[currentKey] = true;
|
|
3119
|
+
else if (val === "false") fm[currentKey] = false;
|
|
3120
|
+
else if (/^\d+(\.\d+)?$/.test(val)) fm[currentKey] = Number(val);
|
|
3121
|
+
else fm[currentKey] = val;
|
|
3122
|
+
currentKey = "";
|
|
3123
|
+
}
|
|
3124
|
+
if (collectingList && currentKey) {
|
|
3125
|
+
fm[currentKey] = collectingList;
|
|
3126
|
+
}
|
|
3127
|
+
for (const key of ["tags", "related", "evidence", "evidence_links", "sourceIds"]) {
|
|
3128
|
+
if (fm[key] && !Array.isArray(fm[key])) {
|
|
3129
|
+
const str = String(fm[key]);
|
|
3130
|
+
if (str.startsWith("[")) {
|
|
3131
|
+
try {
|
|
3132
|
+
fm[key] = JSON.parse(str);
|
|
3133
|
+
} catch {
|
|
3134
|
+
fm[key] = [str];
|
|
3135
|
+
}
|
|
3136
|
+
} else {
|
|
3137
|
+
fm[key] = [str];
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
if (!fm[key]) fm[key] = [];
|
|
3141
|
+
}
|
|
3142
|
+
return fm;
|
|
3143
|
+
}
|
|
2324
3144
|
function serializeTrace(trace) {
|
|
2325
3145
|
if (!trace) return trace;
|
|
2326
3146
|
const obj = { ...trace };
|
|
2327
|
-
if (
|
|
3147
|
+
if (trace.nodes instanceof Map) {
|
|
2328
3148
|
const nodesObj = {};
|
|
2329
|
-
for (const [key, value] of
|
|
3149
|
+
for (const [key, value] of trace.nodes) {
|
|
2330
3150
|
nodesObj[key] = value;
|
|
2331
3151
|
}
|
|
2332
3152
|
obj.nodes = nodesObj;
|
|
@@ -2340,11 +3160,11 @@ var DashboardServer = class {
|
|
|
2340
3160
|
this.userConfig = userCfg;
|
|
2341
3161
|
this.configPath = cfgPath;
|
|
2342
3162
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
2343
|
-
const dashConfigPath =
|
|
3163
|
+
const dashConfigPath = path4.join(home, ".agentflow/dashboard-config.json");
|
|
2344
3164
|
if (!config.dataDirs) config.dataDirs = [];
|
|
2345
3165
|
try {
|
|
2346
|
-
if (
|
|
2347
|
-
const saved = JSON.parse(
|
|
3166
|
+
if (fs4.existsSync(dashConfigPath)) {
|
|
3167
|
+
const saved = JSON.parse(fs4.readFileSync(dashConfigPath, "utf-8"));
|
|
2348
3168
|
const extraDirs = saved.extraDirs ?? [];
|
|
2349
3169
|
for (const d of extraDirs) {
|
|
2350
3170
|
if (!config.dataDirs.includes(d)) config.dataDirs.push(d);
|
|
@@ -2353,7 +3173,7 @@ var DashboardServer = class {
|
|
|
2353
3173
|
} catch {
|
|
2354
3174
|
}
|
|
2355
3175
|
for (const p of getDiscoveryPaths(this.userConfig)) {
|
|
2356
|
-
if (
|
|
3176
|
+
if (fs4.existsSync(p) && !config.dataDirs.includes(p)) {
|
|
2357
3177
|
config.dataDirs.push(p);
|
|
2358
3178
|
}
|
|
2359
3179
|
}
|
|
@@ -2364,8 +3184,9 @@ var DashboardServer = class {
|
|
|
2364
3184
|
});
|
|
2365
3185
|
this.stats = new AgentStats();
|
|
2366
3186
|
this.knowledgeStore = createKnowledgeStore({
|
|
2367
|
-
baseDir:
|
|
3187
|
+
baseDir: path4.join(config.tracesDir, "..", ".agentflow", "knowledge")
|
|
2368
3188
|
});
|
|
3189
|
+
this.commandExecutor = createCommandExecutor(this.userConfig);
|
|
2369
3190
|
this.setupExpress();
|
|
2370
3191
|
this.setupWebSocket();
|
|
2371
3192
|
this.setupTraceWatcher();
|
|
@@ -2396,9 +3217,21 @@ var DashboardServer = class {
|
|
|
2396
3217
|
ts: 0
|
|
2397
3218
|
};
|
|
2398
3219
|
knowledgeStore;
|
|
3220
|
+
commandExecutor;
|
|
2399
3221
|
userConfig;
|
|
2400
3222
|
configPath;
|
|
2401
3223
|
setupExpress() {
|
|
3224
|
+
this.app.use(
|
|
3225
|
+
"/api/",
|
|
3226
|
+
rateLimit({
|
|
3227
|
+
windowMs: 60 * 1e3,
|
|
3228
|
+
// 1 minute
|
|
3229
|
+
max: 300,
|
|
3230
|
+
// 300 requests per minute per IP
|
|
3231
|
+
standardHeaders: true,
|
|
3232
|
+
legacyHeaders: false
|
|
3233
|
+
})
|
|
3234
|
+
);
|
|
2402
3235
|
if (this.config.enableCors) {
|
|
2403
3236
|
this.app.use((_req, res, next) => {
|
|
2404
3237
|
res.header("Access-Control-Allow-Origin", "*");
|
|
@@ -2409,11 +3242,11 @@ var DashboardServer = class {
|
|
|
2409
3242
|
next();
|
|
2410
3243
|
});
|
|
2411
3244
|
}
|
|
2412
|
-
const pkgDir =
|
|
2413
|
-
const clientDir =
|
|
2414
|
-
const clientIndex =
|
|
2415
|
-
const srcDir =
|
|
2416
|
-
const needsBuild = !
|
|
3245
|
+
const pkgDir = path4.join(__dirname, "..");
|
|
3246
|
+
const clientDir = path4.join(pkgDir, "dist/client");
|
|
3247
|
+
const clientIndex = path4.join(clientDir, "index.html");
|
|
3248
|
+
const srcDir = path4.join(pkgDir, "src/client");
|
|
3249
|
+
const needsBuild = !fs4.existsSync(clientIndex) || fs4.existsSync(srcDir) && this.isClientStale(srcDir, clientDir);
|
|
2417
3250
|
if (needsBuild) {
|
|
2418
3251
|
try {
|
|
2419
3252
|
console.log("Building dashboard client...");
|
|
@@ -2422,7 +3255,7 @@ var DashboardServer = class {
|
|
|
2422
3255
|
console.warn("Client build failed \u2014 dashboard UI may be stale:", err.message);
|
|
2423
3256
|
}
|
|
2424
3257
|
}
|
|
2425
|
-
if (
|
|
3258
|
+
if (fs4.existsSync(clientDir)) {
|
|
2426
3259
|
this.app.use(express.static(clientDir));
|
|
2427
3260
|
}
|
|
2428
3261
|
this.app.get("/api/traces", (req, res) => {
|
|
@@ -2469,6 +3302,20 @@ var DashboardServer = class {
|
|
|
2469
3302
|
res.status(500).json({ error: "Failed to load trace events" });
|
|
2470
3303
|
}
|
|
2471
3304
|
});
|
|
3305
|
+
this.app.get("/api/traces/:filename/receipt", (req, res) => {
|
|
3306
|
+
try {
|
|
3307
|
+
const trace = this.watcher.getTrace(req.params.filename);
|
|
3308
|
+
if (!trace) {
|
|
3309
|
+
return res.status(404).json({ error: "Trace not found" });
|
|
3310
|
+
}
|
|
3311
|
+
const serialized = serializeTrace(trace);
|
|
3312
|
+
const graph = loadGraph2(serialized);
|
|
3313
|
+
const receipt = toReceipt(graph);
|
|
3314
|
+
res.json(receipt);
|
|
3315
|
+
} catch (_error) {
|
|
3316
|
+
res.status(500).json({ error: "Failed to generate receipt" });
|
|
3317
|
+
}
|
|
3318
|
+
});
|
|
2472
3319
|
this.app.get("/api/agents", (req, res) => {
|
|
2473
3320
|
try {
|
|
2474
3321
|
const raw = this.stats.getAgentsList();
|
|
@@ -2591,19 +3438,37 @@ var DashboardServer = class {
|
|
|
2591
3438
|
res.status(500).json({ error: "Failed to build process graph" });
|
|
2592
3439
|
}
|
|
2593
3440
|
});
|
|
2594
|
-
this.app.get("/api/agents/:agentId/variants", (req, res) => {
|
|
3441
|
+
this.app.get("/api/agents/:agentId/variants", async (req, res) => {
|
|
2595
3442
|
try {
|
|
2596
3443
|
const agentId = req.params.agentId;
|
|
3444
|
+
const byModel = req.query.by === "model";
|
|
2597
3445
|
const graphs = this.getGraphTraces(agentId);
|
|
2598
3446
|
if (graphs.length === 0) {
|
|
2599
|
-
return res.json({ agentId, totalTraces: 0, variants: [] });
|
|
3447
|
+
return res.json({ agentId, totalTraces: 0, variants: [], modelVariants: [] });
|
|
2600
3448
|
}
|
|
2601
3449
|
const variants = findVariants(graphs).map((v) => ({
|
|
2602
3450
|
pathSignature: v.pathSignature,
|
|
2603
3451
|
count: v.count,
|
|
2604
3452
|
percentage: v.percentage
|
|
2605
3453
|
}));
|
|
2606
|
-
|
|
3454
|
+
let modelVariants = [];
|
|
3455
|
+
if (byModel) {
|
|
3456
|
+
try {
|
|
3457
|
+
const { findVariantsWithModel } = await import(
|
|
3458
|
+
/* webpackIgnore: true */
|
|
3459
|
+
"./ops-intel-PL6GGIKL.js"
|
|
3460
|
+
);
|
|
3461
|
+
modelVariants = findVariantsWithModel(graphs, { includeModel: true }).map(
|
|
3462
|
+
(v) => ({
|
|
3463
|
+
pathSignature: v.pathSignature,
|
|
3464
|
+
count: v.count,
|
|
3465
|
+
percentage: v.percentage
|
|
3466
|
+
})
|
|
3467
|
+
);
|
|
3468
|
+
} catch {
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
res.json({ agentId, totalTraces: graphs.length, variants, modelVariants });
|
|
2607
3472
|
} catch (error) {
|
|
2608
3473
|
console.error("Variants error:", error);
|
|
2609
3474
|
res.status(500).json({ error: "Failed to compute variants" });
|
|
@@ -2628,6 +3493,147 @@ var DashboardServer = class {
|
|
|
2628
3493
|
res.status(500).json({ error: "Failed to compute bottlenecks" });
|
|
2629
3494
|
}
|
|
2630
3495
|
});
|
|
3496
|
+
this.app.get("/api/agents/:agentId/health-briefing", async (req, res) => {
|
|
3497
|
+
const somaVault = this.config.somaVault;
|
|
3498
|
+
if (!somaVault) return res.status(404).json({ error: "Soma vault not configured" });
|
|
3499
|
+
try {
|
|
3500
|
+
const agentId = req.params.agentId;
|
|
3501
|
+
const agentFile = path4.join(
|
|
3502
|
+
somaVault,
|
|
3503
|
+
"agent",
|
|
3504
|
+
`${safePath(agentId.replace(/:/g, "-"))}.md`
|
|
3505
|
+
);
|
|
3506
|
+
let agentData = {};
|
|
3507
|
+
if (fs4.existsSync(agentFile)) {
|
|
3508
|
+
agentData = parseVaultFrontmatter(fs4.readFileSync(agentFile, "utf-8")) ?? {};
|
|
3509
|
+
}
|
|
3510
|
+
const totalExecutions = Number(agentData.totalExecutions ?? 0);
|
|
3511
|
+
const failureRate = Number(agentData.failureRate ?? 0);
|
|
3512
|
+
const failureCount = Number(agentData.failureCount ?? 0);
|
|
3513
|
+
const status = failureRate > 0.5 ? "critical" : failureRate > 0.1 ? "degraded" : "healthy";
|
|
3514
|
+
const knowledgeTypes = ["decision", "insight", "constraint", "contradiction", "policy"];
|
|
3515
|
+
const intelligence = [];
|
|
3516
|
+
for (const kt of knowledgeTypes) {
|
|
3517
|
+
const dir = path4.join(somaVault, kt);
|
|
3518
|
+
if (!fs4.existsSync(dir)) continue;
|
|
3519
|
+
for (const f of fs4.readdirSync(dir)) {
|
|
3520
|
+
if (!f.endsWith(".md")) continue;
|
|
3521
|
+
try {
|
|
3522
|
+
const content = fs4.readFileSync(path4.join(dir, f), "utf-8");
|
|
3523
|
+
if (!content.includes(agentId) && !content.includes(agentId.replace(/:/g, "-")))
|
|
3524
|
+
continue;
|
|
3525
|
+
const parsed = parseVaultFrontmatter(content);
|
|
3526
|
+
if (!parsed) continue;
|
|
3527
|
+
intelligence.push({
|
|
3528
|
+
type: String(parsed.type ?? kt),
|
|
3529
|
+
name: String(parsed.name ?? f.replace(".md", "")),
|
|
3530
|
+
claim: String(parsed.claim ?? "").slice(0, 150),
|
|
3531
|
+
confidence: parsed.confidence
|
|
3532
|
+
});
|
|
3533
|
+
} catch {
|
|
3534
|
+
}
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
const agentDir = path4.join(somaVault, "agent");
|
|
3538
|
+
const peers = [];
|
|
3539
|
+
if (fs4.existsSync(agentDir)) {
|
|
3540
|
+
for (const f of fs4.readdirSync(agentDir)) {
|
|
3541
|
+
if (!f.endsWith(".md")) continue;
|
|
3542
|
+
const p = parseVaultFrontmatter(fs4.readFileSync(path4.join(agentDir, f), "utf-8"));
|
|
3543
|
+
if (!p) continue;
|
|
3544
|
+
const runs = Number(p.totalExecutions ?? 0);
|
|
3545
|
+
if (runs > 0) {
|
|
3546
|
+
peers.push({
|
|
3547
|
+
name: String(p.name ?? p.agentId ?? f.replace(".md", "")),
|
|
3548
|
+
successRate: 1 - Number(p.failureRate ?? 0),
|
|
3549
|
+
runs
|
|
3550
|
+
});
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
peers.sort((a, b) => b.successRate - a.successRate);
|
|
3555
|
+
let drift = null;
|
|
3556
|
+
try {
|
|
3557
|
+
const historyPath = path4.join(somaVault, "..", "conformance-history.json");
|
|
3558
|
+
if (fs4.existsSync(historyPath)) {
|
|
3559
|
+
const history = JSON.parse(fs4.readFileSync(historyPath, "utf-8"));
|
|
3560
|
+
const agentHistory = history.filter((e) => e.agentId === agentId);
|
|
3561
|
+
if (agentHistory.length >= 10) {
|
|
3562
|
+
try {
|
|
3563
|
+
const { detectDrift: dd } = await import(
|
|
3564
|
+
/* webpackIgnore: true */
|
|
3565
|
+
"./ops-intel-PL6GGIKL.js"
|
|
3566
|
+
);
|
|
3567
|
+
drift = dd(agentHistory);
|
|
3568
|
+
} catch {
|
|
3569
|
+
drift = { status: "stable", dataPoints: agentHistory.length };
|
|
3570
|
+
}
|
|
3571
|
+
} else {
|
|
3572
|
+
drift = { status: "insufficient_data", dataPoints: agentHistory.length };
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3575
|
+
} catch {
|
|
3576
|
+
}
|
|
3577
|
+
res.json({
|
|
3578
|
+
agentId,
|
|
3579
|
+
status,
|
|
3580
|
+
totalExecutions,
|
|
3581
|
+
failureRate,
|
|
3582
|
+
failureCount,
|
|
3583
|
+
intelligence: {
|
|
3584
|
+
total: intelligence.length,
|
|
3585
|
+
byType: Object.fromEntries(
|
|
3586
|
+
knowledgeTypes.map((t) => [t, intelligence.filter((i) => i.type === t)])
|
|
3587
|
+
)
|
|
3588
|
+
},
|
|
3589
|
+
peers,
|
|
3590
|
+
drift
|
|
3591
|
+
});
|
|
3592
|
+
} catch (error) {
|
|
3593
|
+
console.error("Health briefing error:", error);
|
|
3594
|
+
res.status(500).json({ error: "Failed to generate briefing" });
|
|
3595
|
+
}
|
|
3596
|
+
});
|
|
3597
|
+
this.app.get("/api/traces/:filename/decisions", (req, res) => {
|
|
3598
|
+
try {
|
|
3599
|
+
const trace = this.watcher.getTrace(req.params.filename);
|
|
3600
|
+
if (!trace) return res.status(404).json({ error: "Trace not found" });
|
|
3601
|
+
const serialized = serializeTrace(trace);
|
|
3602
|
+
const sessionEvents = trace.sessionEvents;
|
|
3603
|
+
let decisions = [];
|
|
3604
|
+
if (sessionEvents && sessionEvents.length > 0) {
|
|
3605
|
+
try {
|
|
3606
|
+
import("./ops-intel-PL6GGIKL.js").then(({ extractDecisionsFromSession, computePatternSignature }) => {
|
|
3607
|
+
decisions = extractDecisionsFromSession(sessionEvents);
|
|
3608
|
+
res.json({
|
|
3609
|
+
decisions,
|
|
3610
|
+
pattern: computePatternSignature(
|
|
3611
|
+
decisions
|
|
3612
|
+
)
|
|
3613
|
+
});
|
|
3614
|
+
}).catch(() => {
|
|
3615
|
+
res.json({ decisions: [], pattern: "" });
|
|
3616
|
+
});
|
|
3617
|
+
return;
|
|
3618
|
+
} catch {
|
|
3619
|
+
}
|
|
3620
|
+
}
|
|
3621
|
+
const graph = loadGraph2(serialized);
|
|
3622
|
+
const nodes = [...graph.nodes.values()];
|
|
3623
|
+
const toolNodes = nodes.filter((n) => n.type === "tool" || n.type === "action").sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
|
|
3624
|
+
decisions = toolNodes.map((n, i) => ({
|
|
3625
|
+
action: n.name,
|
|
3626
|
+
tool: n.name,
|
|
3627
|
+
outcome: n.status === "failed" ? "failed" : "ok",
|
|
3628
|
+
durationMs: n.endTime != null ? n.endTime - n.startTime : void 0,
|
|
3629
|
+
index: i
|
|
3630
|
+
}));
|
|
3631
|
+
const pattern = decisions.map((d) => d.action).join("\u2192");
|
|
3632
|
+
res.json({ decisions, pattern });
|
|
3633
|
+
} catch {
|
|
3634
|
+
res.status(500).json({ error: "Failed to extract decisions" });
|
|
3635
|
+
}
|
|
3636
|
+
});
|
|
2631
3637
|
this.app.get("/api/agents/:agentId/profile", (req, res) => {
|
|
2632
3638
|
try {
|
|
2633
3639
|
const profile = this.knowledgeStore.getAgentProfile(req.params.agentId);
|
|
@@ -2641,6 +3647,7 @@ var DashboardServer = class {
|
|
|
2641
3647
|
}
|
|
2642
3648
|
});
|
|
2643
3649
|
this.app.get("/api/process-model/:agentId", (req, res) => {
|
|
3650
|
+
var _a, _b;
|
|
2644
3651
|
try {
|
|
2645
3652
|
const agentId = req.params.agentId;
|
|
2646
3653
|
const allTraces = this.watcher.getTracesByAgent(agentId);
|
|
@@ -2658,8 +3665,8 @@ var DashboardServer = class {
|
|
|
2658
3665
|
const nodeArr = Object.values(nodes);
|
|
2659
3666
|
const sorted = nodeArr.filter((n) => n.name && typeof n.startTime === "number" && n.startTime > 0).sort((a, b) => (a.startTime ?? 0) - (b.startTime ?? 0));
|
|
2660
3667
|
for (let i = 0; i < sorted.length - 1; i++) {
|
|
2661
|
-
const from = sorted[i].name;
|
|
2662
|
-
const to = sorted[i + 1].name;
|
|
3668
|
+
const from = ((_a = sorted[i]) == null ? void 0 : _a.name) ?? "";
|
|
3669
|
+
const to = ((_b = sorted[i + 1]) == null ? void 0 : _b.name) ?? "";
|
|
2663
3670
|
const key = `${from}|||${to}`;
|
|
2664
3671
|
transMap.set(key, (transMap.get(key) ?? 0) + 1);
|
|
2665
3672
|
}
|
|
@@ -2682,7 +3689,7 @@ var DashboardServer = class {
|
|
|
2682
3689
|
const model = {
|
|
2683
3690
|
transitions: [...transMap.entries()].map(([key, count]) => {
|
|
2684
3691
|
const [from, to] = key.split("|||");
|
|
2685
|
-
return { from, to, count };
|
|
3692
|
+
return { from: from ?? "", to: to ?? "", count };
|
|
2686
3693
|
}),
|
|
2687
3694
|
nodeTypes: Object.fromEntries(nodeTypeMap)
|
|
2688
3695
|
};
|
|
@@ -2735,11 +3742,11 @@ var DashboardServer = class {
|
|
|
2735
3742
|
return res.json({ tier: "teaser", somaVault: false, governanceAvailable: false });
|
|
2736
3743
|
}
|
|
2737
3744
|
try {
|
|
2738
|
-
const reportPath =
|
|
2739
|
-
if (!
|
|
3745
|
+
const reportPath = path4.join(somaVault, "..", "soma-report.json");
|
|
3746
|
+
if (!fs4.existsSync(reportPath)) {
|
|
2740
3747
|
return res.json({ tier: "free", somaVault: true, governanceAvailable: false });
|
|
2741
3748
|
}
|
|
2742
|
-
const report = JSON.parse(
|
|
3749
|
+
const report = JSON.parse(fs4.readFileSync(reportPath, "utf-8"));
|
|
2743
3750
|
const hasGovernance = report.governance && typeof report.governance.pending === "number";
|
|
2744
3751
|
return res.json({
|
|
2745
3752
|
tier: hasGovernance ? "pro" : "free",
|
|
@@ -2756,11 +3763,15 @@ var DashboardServer = class {
|
|
|
2756
3763
|
return res.json({ available: false, teaser: true });
|
|
2757
3764
|
}
|
|
2758
3765
|
try {
|
|
2759
|
-
const reportPath =
|
|
2760
|
-
if (!
|
|
2761
|
-
return res.json({
|
|
3766
|
+
const reportPath = path4.join(somaVault, "..", "soma-report.json");
|
|
3767
|
+
if (!fs4.existsSync(reportPath)) {
|
|
3768
|
+
return res.json({
|
|
3769
|
+
available: false,
|
|
3770
|
+
teaser: false,
|
|
3771
|
+
message: "No report file yet. Run soma watch."
|
|
3772
|
+
});
|
|
2762
3773
|
}
|
|
2763
|
-
const report = JSON.parse(
|
|
3774
|
+
const report = JSON.parse(fs4.readFileSync(reportPath, "utf-8"));
|
|
2764
3775
|
res.json(report);
|
|
2765
3776
|
} catch (error) {
|
|
2766
3777
|
console.error("Soma report error:", error);
|
|
@@ -2773,17 +3784,21 @@ var DashboardServer = class {
|
|
|
2773
3784
|
return res.json({ available: false });
|
|
2774
3785
|
}
|
|
2775
3786
|
try {
|
|
2776
|
-
const reportPath =
|
|
2777
|
-
if (!
|
|
3787
|
+
const reportPath = path4.join(somaVault, "..", "soma-report.json");
|
|
3788
|
+
if (!fs4.existsSync(reportPath)) {
|
|
2778
3789
|
return res.json({ available: false, message: "No report file. Run soma report." });
|
|
2779
3790
|
}
|
|
2780
|
-
const report = JSON.parse(
|
|
3791
|
+
const report = JSON.parse(fs4.readFileSync(reportPath, "utf-8"));
|
|
2781
3792
|
res.json({
|
|
2782
3793
|
available: true,
|
|
2783
3794
|
layers: report.layers ?? { archive: 0, working: 0, emerging: 0, canon: 0 },
|
|
2784
3795
|
governance: report.governance ?? { pending: 0, promoted: 0, rejected: 0 },
|
|
2785
|
-
insights: (report.insights ?? []).filter(
|
|
2786
|
-
|
|
3796
|
+
insights: (report.insights ?? []).filter(
|
|
3797
|
+
(i) => i.layer === "emerging" && i.proposal_status === "pending"
|
|
3798
|
+
),
|
|
3799
|
+
canon: (report.insights ?? []).filter(
|
|
3800
|
+
(i) => i.layer === "canon"
|
|
3801
|
+
),
|
|
2787
3802
|
generatedAt: report.generatedAt
|
|
2788
3803
|
});
|
|
2789
3804
|
} catch (error) {
|
|
@@ -2791,24 +3806,28 @@ var DashboardServer = class {
|
|
|
2791
3806
|
res.status(500).json({ available: false, message: "Failed to read governance data" });
|
|
2792
3807
|
}
|
|
2793
3808
|
});
|
|
2794
|
-
const
|
|
2795
|
-
const sanitizeReason = (s) => s.replace(/["`$\\]/g, "").slice(0, 500);
|
|
3809
|
+
const isValidId = (s) => /^[a-zA-Z0-9_\-.:]+$/.test(s);
|
|
2796
3810
|
this.app.post("/api/soma/governance/promote", (req, res) => {
|
|
2797
3811
|
var _a;
|
|
2798
3812
|
const somaVault = this.config.somaVault;
|
|
2799
3813
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2800
3814
|
const { entryId } = req.body ?? {};
|
|
2801
|
-
if (!entryId
|
|
3815
|
+
if (!entryId || !isValidId(String(entryId)))
|
|
3816
|
+
return res.status(400).json({ error: "Invalid entryId" });
|
|
2802
3817
|
try {
|
|
2803
|
-
const
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
3818
|
+
const result = execFileSync(
|
|
3819
|
+
"npx",
|
|
3820
|
+
["soma", "governance", "promote", String(entryId), "--vault", somaVault],
|
|
3821
|
+
{
|
|
3822
|
+
encoding: "utf-8",
|
|
3823
|
+
timeout: 1e4
|
|
3824
|
+
}
|
|
3825
|
+
);
|
|
2809
3826
|
res.json({ success: true, message: result.trim() });
|
|
2810
3827
|
} catch (error) {
|
|
2811
|
-
res.status(400).json({
|
|
3828
|
+
res.status(400).json({
|
|
3829
|
+
error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message
|
|
3830
|
+
});
|
|
2812
3831
|
}
|
|
2813
3832
|
});
|
|
2814
3833
|
this.app.post("/api/soma/governance/reject", (req, res) => {
|
|
@@ -2816,43 +3835,62 @@ var DashboardServer = class {
|
|
|
2816
3835
|
const somaVault = this.config.somaVault;
|
|
2817
3836
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2818
3837
|
const { entryId, reason } = req.body ?? {};
|
|
2819
|
-
if (!entryId || !
|
|
3838
|
+
if (!entryId || !isValidId(String(entryId)))
|
|
3839
|
+
return res.status(400).json({ error: "Invalid entryId" });
|
|
3840
|
+
if (!reason || typeof reason !== "string")
|
|
3841
|
+
return res.status(400).json({ error: "reason required" });
|
|
2820
3842
|
try {
|
|
2821
|
-
const
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
3843
|
+
const result = execFileSync(
|
|
3844
|
+
"npx",
|
|
3845
|
+
[
|
|
3846
|
+
"soma",
|
|
3847
|
+
"governance",
|
|
3848
|
+
"reject",
|
|
3849
|
+
String(entryId),
|
|
3850
|
+
String(reason).slice(0, 500),
|
|
3851
|
+
"--vault",
|
|
3852
|
+
somaVault
|
|
3853
|
+
],
|
|
3854
|
+
{
|
|
3855
|
+
encoding: "utf-8",
|
|
3856
|
+
timeout: 1e4
|
|
3857
|
+
}
|
|
3858
|
+
);
|
|
2828
3859
|
res.json({ success: true, message: result.trim() });
|
|
2829
3860
|
} catch (error) {
|
|
2830
|
-
res.status(400).json({
|
|
3861
|
+
res.status(400).json({
|
|
3862
|
+
error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message
|
|
3863
|
+
});
|
|
2831
3864
|
}
|
|
2832
3865
|
});
|
|
2833
3866
|
this.app.get("/api/soma/governance/evidence/:id", (req, res) => {
|
|
2834
3867
|
var _a;
|
|
2835
3868
|
const somaVault = this.config.somaVault;
|
|
2836
3869
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
3870
|
+
if (!isValidId(String(req.params.id))) return res.status(400).json({ error: "Invalid id" });
|
|
2837
3871
|
try {
|
|
2838
|
-
const
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
3872
|
+
const result = execFileSync(
|
|
3873
|
+
"npx",
|
|
3874
|
+
["soma", "governance", "show", String(req.params.id), "--vault", somaVault],
|
|
3875
|
+
{
|
|
3876
|
+
encoding: "utf-8",
|
|
3877
|
+
timeout: 1e4
|
|
3878
|
+
}
|
|
3879
|
+
);
|
|
2844
3880
|
res.json({ available: true, output: result.trim() });
|
|
2845
3881
|
} catch (error) {
|
|
2846
|
-
res.status(404).json({
|
|
3882
|
+
res.status(404).json({
|
|
3883
|
+
error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message
|
|
3884
|
+
});
|
|
2847
3885
|
}
|
|
2848
3886
|
});
|
|
2849
3887
|
this.app.get("/api/soma/policies", (_req, res) => {
|
|
2850
3888
|
const somaVault = this.config.somaVault;
|
|
2851
3889
|
if (!somaVault) return res.json({ policies: [] });
|
|
2852
3890
|
try {
|
|
2853
|
-
const reportPath =
|
|
2854
|
-
if (!
|
|
2855
|
-
const report = JSON.parse(
|
|
3891
|
+
const reportPath = path4.join(somaVault, "..", "soma-report.json");
|
|
3892
|
+
if (!fs4.existsSync(reportPath)) return res.json({ policies: [] });
|
|
3893
|
+
const report = JSON.parse(fs4.readFileSync(reportPath, "utf-8"));
|
|
2856
3894
|
res.json({ policies: report.policies ?? [] });
|
|
2857
3895
|
} catch {
|
|
2858
3896
|
res.json({ policies: [] });
|
|
@@ -2863,57 +3901,102 @@ var DashboardServer = class {
|
|
|
2863
3901
|
const somaVault = this.config.somaVault;
|
|
2864
3902
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
2865
3903
|
const { name, enforcement, scope, conditions } = req.body ?? {};
|
|
2866
|
-
if (!name
|
|
3904
|
+
if (!name || !isValidId(String(name)))
|
|
3905
|
+
return res.status(400).json({ error: "Invalid policy name" });
|
|
3906
|
+
const enf = String(enforcement || "warn");
|
|
3907
|
+
if (!isValidId(enf)) return res.status(400).json({ error: "Invalid enforcement value" });
|
|
2867
3908
|
try {
|
|
2868
|
-
const
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
3909
|
+
const args = [
|
|
3910
|
+
"soma",
|
|
3911
|
+
"policy",
|
|
3912
|
+
"create",
|
|
3913
|
+
String(name),
|
|
3914
|
+
"--enforcement",
|
|
3915
|
+
enf,
|
|
3916
|
+
"--vault",
|
|
3917
|
+
somaVault
|
|
3918
|
+
];
|
|
3919
|
+
if (scope) args.push("--scope", String(scope).slice(0, 500));
|
|
3920
|
+
if (conditions) args.push("--conditions", String(conditions).slice(0, 500));
|
|
3921
|
+
const result = execFileSync("npx", args, { encoding: "utf-8", timeout: 1e4 });
|
|
2876
3922
|
res.json({ success: true, message: result.trim() });
|
|
2877
3923
|
} catch (error) {
|
|
2878
|
-
res.status(400).json({
|
|
3924
|
+
res.status(400).json({
|
|
3925
|
+
error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message
|
|
3926
|
+
});
|
|
2879
3927
|
}
|
|
2880
3928
|
});
|
|
2881
3929
|
this.app.delete("/api/soma/policies/:name", (req, res) => {
|
|
2882
3930
|
var _a;
|
|
2883
3931
|
const somaVault = this.config.somaVault;
|
|
2884
3932
|
if (!somaVault) return res.status(400).json({ error: "Soma vault not configured" });
|
|
3933
|
+
if (!isValidId(String(req.params.name)))
|
|
3934
|
+
return res.status(400).json({ error: "Invalid policy name" });
|
|
2885
3935
|
try {
|
|
2886
|
-
const
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
{
|
|
3936
|
+
const result = execFileSync(
|
|
3937
|
+
"npx",
|
|
3938
|
+
["soma", "policy", "delete", String(req.params.name), "--vault", somaVault],
|
|
3939
|
+
{
|
|
3940
|
+
encoding: "utf-8",
|
|
3941
|
+
timeout: 1e4
|
|
3942
|
+
}
|
|
2890
3943
|
);
|
|
2891
3944
|
res.json({ success: true, message: result.trim() });
|
|
2892
3945
|
} catch (error) {
|
|
2893
|
-
res.status(400).json({
|
|
3946
|
+
res.status(400).json({
|
|
3947
|
+
error: ((_a = error.stderr) == null ? void 0 : _a.trim()) || error.message
|
|
3948
|
+
});
|
|
2894
3949
|
}
|
|
2895
3950
|
});
|
|
2896
3951
|
this.app.get("/api/soma/vault/entities", (req, res) => {
|
|
2897
3952
|
const somaVault = this.config.somaVault;
|
|
2898
3953
|
if (!somaVault) return res.json({ entities: [], total: 0 });
|
|
2899
3954
|
try {
|
|
2900
|
-
const
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
}),
|
|
2909
|
-
...(report.policies ?? []).map((p) => ({ ...p, type: "policy", id: p.name }))
|
|
3955
|
+
const entityTypes = [
|
|
3956
|
+
"agent",
|
|
3957
|
+
"decision",
|
|
3958
|
+
"insight",
|
|
3959
|
+
"constraint",
|
|
3960
|
+
"contradiction",
|
|
3961
|
+
"policy",
|
|
3962
|
+
"archetype"
|
|
2910
3963
|
];
|
|
2911
|
-
|
|
3964
|
+
let entities = [];
|
|
3965
|
+
for (const entityType of entityTypes) {
|
|
3966
|
+
const dir = path4.join(somaVault, entityType);
|
|
3967
|
+
if (!fs4.existsSync(dir)) continue;
|
|
3968
|
+
for (const file of fs4.readdirSync(dir)) {
|
|
3969
|
+
if (!file.endsWith(".md")) continue;
|
|
3970
|
+
try {
|
|
3971
|
+
const content = fs4.readFileSync(path4.join(dir, file), "utf-8");
|
|
3972
|
+
const parsed = parseVaultFrontmatter(content);
|
|
3973
|
+
if (!parsed) continue;
|
|
3974
|
+
const body = content.slice(content.indexOf("---", 4) + 3).trim().slice(0, 500);
|
|
3975
|
+
entities.push({
|
|
3976
|
+
...parsed,
|
|
3977
|
+
type: parsed.type || entityType,
|
|
3978
|
+
id: parsed.id || file.replace(".md", ""),
|
|
3979
|
+
name: parsed.name || file.replace(".md", ""),
|
|
3980
|
+
body
|
|
3981
|
+
});
|
|
3982
|
+
} catch {
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
const {
|
|
3987
|
+
type,
|
|
3988
|
+
layer,
|
|
3989
|
+
q,
|
|
3990
|
+
limit: limitStr,
|
|
3991
|
+
offset: offsetStr
|
|
3992
|
+
} = req.query;
|
|
2912
3993
|
if (type) entities = entities.filter((e) => e.type === type);
|
|
2913
3994
|
if (layer) entities = entities.filter((e) => e.layer === layer);
|
|
2914
3995
|
if (q) {
|
|
2915
3996
|
const lq = q.toLowerCase();
|
|
2916
|
-
entities = entities.filter(
|
|
3997
|
+
entities = entities.filter(
|
|
3998
|
+
(e) => (e.name || e.title || "").toLowerCase().includes(lq) || (e.claim || e.body || "").toLowerCase().includes(lq)
|
|
3999
|
+
);
|
|
2917
4000
|
}
|
|
2918
4001
|
const total = entities.length;
|
|
2919
4002
|
const offset = parseInt(offsetStr || "0", 10);
|
|
@@ -2929,35 +4012,414 @@ var DashboardServer = class {
|
|
|
2929
4012
|
const somaVault = this.config.somaVault;
|
|
2930
4013
|
if (!somaVault) return res.status(404).json({ error: "Soma vault not configured" });
|
|
2931
4014
|
try {
|
|
2932
|
-
const
|
|
2933
|
-
const
|
|
2934
|
-
const
|
|
2935
|
-
|
|
4015
|
+
const type = safePath(req.params.type);
|
|
4016
|
+
const id = safePath(req.params.id);
|
|
4017
|
+
const filePath = path4.join(somaVault, type, `${id}.md`);
|
|
4018
|
+
if (!path4.resolve(filePath).startsWith(path4.resolve(somaVault))) {
|
|
4019
|
+
return res.status(400).json({ error: "Invalid path" });
|
|
4020
|
+
}
|
|
4021
|
+
if (!fs4.existsSync(filePath)) return res.status(404).json({ error: "Entity not found" });
|
|
4022
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
4023
|
+
const fm = parseVaultFrontmatter(content);
|
|
4024
|
+
if (!fm) return res.status(404).json({ error: "Entity not found" });
|
|
4025
|
+
const body = content.slice(content.indexOf("---", 4) + 3).trim();
|
|
4026
|
+
const agentKnowledge = [];
|
|
2936
4027
|
if (type === "agent") {
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
(
|
|
2943
|
-
|
|
2944
|
-
|
|
4028
|
+
const agentName = fm.name || fm.agentId || id;
|
|
4029
|
+
const knowledgeTypes = ["decision", "insight", "constraint", "contradiction", "policy"];
|
|
4030
|
+
for (const kt of knowledgeTypes) {
|
|
4031
|
+
const ktDir = path4.join(somaVault, kt);
|
|
4032
|
+
if (!fs4.existsSync(ktDir)) continue;
|
|
4033
|
+
for (const f of fs4.readdirSync(ktDir)) {
|
|
4034
|
+
if (!f.endsWith(".md")) continue;
|
|
4035
|
+
try {
|
|
4036
|
+
const c = fs4.readFileSync(path4.join(ktDir, f), "utf-8");
|
|
4037
|
+
if (!c.includes(String(agentName))) continue;
|
|
4038
|
+
const parsed = parseVaultFrontmatter(c);
|
|
4039
|
+
if (!parsed) continue;
|
|
4040
|
+
agentKnowledge.push({
|
|
4041
|
+
type: parsed.type || kt,
|
|
4042
|
+
id: parsed.id || f.replace(".md", ""),
|
|
4043
|
+
name: parsed.name || f.replace(".md", ""),
|
|
4044
|
+
claim: parsed.claim || "",
|
|
4045
|
+
confidence: parsed.confidence || "",
|
|
4046
|
+
layer: parsed.layer || ""
|
|
4047
|
+
});
|
|
4048
|
+
} catch {
|
|
4049
|
+
}
|
|
2945
4050
|
}
|
|
2946
|
-
|
|
4051
|
+
}
|
|
2947
4052
|
}
|
|
2948
|
-
if (!entity) return res.status(404).json({ error: "Entity not found" });
|
|
2949
4053
|
res.json({
|
|
2950
|
-
...
|
|
4054
|
+
...fm,
|
|
2951
4055
|
type,
|
|
2952
4056
|
id,
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
4057
|
+
name: fm.name || id,
|
|
4058
|
+
body: type === "agent" && !body ? `Agent with ${fm.totalExecutions ?? 0} executions, ${((1 - Number(fm.failureRate || 0)) * 100).toFixed(1)}% success rate.` : body,
|
|
4059
|
+
knowledge: agentKnowledge
|
|
2956
4060
|
});
|
|
2957
4061
|
} catch {
|
|
2958
4062
|
res.status(404).json({ error: "Entity not found" });
|
|
2959
4063
|
}
|
|
2960
4064
|
});
|
|
4065
|
+
this.app.get("/api/aicp/preflight", async (req, res) => {
|
|
4066
|
+
const agentId = req.query.agentId;
|
|
4067
|
+
if (!agentId) {
|
|
4068
|
+
return res.status(400).json({ error: "agentId query parameter required" });
|
|
4069
|
+
}
|
|
4070
|
+
const somaVault = this.config.somaVault;
|
|
4071
|
+
if (!somaVault) {
|
|
4072
|
+
return res.json({
|
|
4073
|
+
proceed: true,
|
|
4074
|
+
warnings: [],
|
|
4075
|
+
recommendations: [],
|
|
4076
|
+
available: false,
|
|
4077
|
+
_meta: { durationMs: 0 }
|
|
4078
|
+
});
|
|
4079
|
+
}
|
|
4080
|
+
try {
|
|
4081
|
+
const { evaluatePreflight } = await import(
|
|
4082
|
+
/* webpackIgnore: true */
|
|
4083
|
+
"./dist-H4QMTTY3.js"
|
|
4084
|
+
);
|
|
4085
|
+
const { createVault } = await import(
|
|
4086
|
+
/* webpackIgnore: true */
|
|
4087
|
+
"./dist-H4QMTTY3.js"
|
|
4088
|
+
);
|
|
4089
|
+
const vault = createVault({ baseDir: somaVault });
|
|
4090
|
+
const result = evaluatePreflight(vault, safePath(agentId));
|
|
4091
|
+
res.json(result);
|
|
4092
|
+
} catch {
|
|
4093
|
+
res.json({
|
|
4094
|
+
proceed: true,
|
|
4095
|
+
warnings: [],
|
|
4096
|
+
recommendations: [],
|
|
4097
|
+
available: false,
|
|
4098
|
+
_meta: { durationMs: 0 }
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
});
|
|
4102
|
+
this.app.get("/api/soma/efficiency", async (_req, res) => {
|
|
4103
|
+
try {
|
|
4104
|
+
const allTraces = this.watcher.getAllTraces().map(serializeTrace);
|
|
4105
|
+
const graphs = [];
|
|
4106
|
+
for (const t of allTraces) {
|
|
4107
|
+
try {
|
|
4108
|
+
if (t.sourceType === "session" || t.sourceType === "log") continue;
|
|
4109
|
+
if (!t.rootNodeId && !t.rootId) continue;
|
|
4110
|
+
const nodes = t.nodes;
|
|
4111
|
+
if (!nodes || typeof nodes === "object" && Object.keys(nodes).length === 0) continue;
|
|
4112
|
+
graphs.push(loadGraph2(t));
|
|
4113
|
+
} catch {
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
4116
|
+
try {
|
|
4117
|
+
const { getEfficiency } = await import(
|
|
4118
|
+
/* webpackIgnore: true */
|
|
4119
|
+
"./ops-intel-PL6GGIKL.js"
|
|
4120
|
+
);
|
|
4121
|
+
const report2 = getEfficiency(graphs);
|
|
4122
|
+
return res.json(report2);
|
|
4123
|
+
} catch {
|
|
4124
|
+
}
|
|
4125
|
+
const somaVault = this.config.somaVault;
|
|
4126
|
+
if (!somaVault) return res.status(404).json({ error: "Soma vault not configured" });
|
|
4127
|
+
const reportPath = path4.join(somaVault, "..", "soma-report.json");
|
|
4128
|
+
if (!fs4.existsSync(reportPath)) {
|
|
4129
|
+
return res.status(404).json({ error: "No SOMA report found" });
|
|
4130
|
+
}
|
|
4131
|
+
const report = JSON.parse(fs4.readFileSync(reportPath, "utf-8"));
|
|
4132
|
+
const agents = report.agents ?? [];
|
|
4133
|
+
const runs = agents.map((a) => ({
|
|
4134
|
+
graphId: a.name,
|
|
4135
|
+
agentId: a.name,
|
|
4136
|
+
totalTokenCost: a.totalTokenCost ?? 0,
|
|
4137
|
+
completedNodes: a.totalRuns ?? 0,
|
|
4138
|
+
costPerNode: (a.totalTokenCost ?? 0) / Math.max(1, a.totalRuns ?? 1)
|
|
4139
|
+
}));
|
|
4140
|
+
const costs = runs.map((r) => r.costPerNode).filter((c) => c > 0).sort((a, b) => a - b);
|
|
4141
|
+
const mean = costs.length > 0 ? costs.reduce((a, b) => a + b, 0) / costs.length : 0;
|
|
4142
|
+
const median = costs.length > 0 ? costs[Math.floor(costs.length / 2)] : 0;
|
|
4143
|
+
const p95 = costs.length > 0 ? costs[Math.min(costs.length - 1, Math.ceil(costs.length * 0.95) - 1)] : 0;
|
|
4144
|
+
res.json({
|
|
4145
|
+
runs,
|
|
4146
|
+
aggregate: { mean, median, p95 },
|
|
4147
|
+
flags: [],
|
|
4148
|
+
nodeCosts: [],
|
|
4149
|
+
dataCoverage: agents.length > 0 ? 1 : 0
|
|
4150
|
+
});
|
|
4151
|
+
} catch {
|
|
4152
|
+
res.status(500).json({ error: "Failed to compute efficiency" });
|
|
4153
|
+
}
|
|
4154
|
+
});
|
|
4155
|
+
this.app.get("/api/soma/drift", async (req, res) => {
|
|
4156
|
+
const agentId = req.query.agentId;
|
|
4157
|
+
if (!agentId) return res.status(400).json({ error: "agentId query parameter required" });
|
|
4158
|
+
try {
|
|
4159
|
+
const somaVault = this.config.somaVault;
|
|
4160
|
+
if (!somaVault) return res.status(404).json({ error: "Soma vault not configured" });
|
|
4161
|
+
const historyPath = path4.join(somaVault, "..", "conformance-history.json");
|
|
4162
|
+
let history = [];
|
|
4163
|
+
if (fs4.existsSync(historyPath)) {
|
|
4164
|
+
history = JSON.parse(fs4.readFileSync(historyPath, "utf-8"));
|
|
4165
|
+
}
|
|
4166
|
+
const agentHistory = history.filter((e) => e.agentId === agentId);
|
|
4167
|
+
try {
|
|
4168
|
+
const { detectDrift } = await import(
|
|
4169
|
+
/* webpackIgnore: true */
|
|
4170
|
+
"./ops-intel-PL6GGIKL.js"
|
|
4171
|
+
);
|
|
4172
|
+
const driftReport = detectDrift(agentHistory);
|
|
4173
|
+
return res.json({ drift: driftReport, points: agentHistory });
|
|
4174
|
+
} catch {
|
|
4175
|
+
}
|
|
4176
|
+
const n = agentHistory.length;
|
|
4177
|
+
if (n < 10) {
|
|
4178
|
+
return res.json({
|
|
4179
|
+
drift: { status: "insufficient_data", slope: 0, r2: 0, windowSize: n, dataPoints: n },
|
|
4180
|
+
points: agentHistory
|
|
4181
|
+
});
|
|
4182
|
+
}
|
|
4183
|
+
let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
|
|
4184
|
+
for (let i = 0; i < n; i++) {
|
|
4185
|
+
const y = agentHistory[i].score;
|
|
4186
|
+
sumX += i;
|
|
4187
|
+
sumY += y;
|
|
4188
|
+
sumXY += i * y;
|
|
4189
|
+
sumX2 += i * i;
|
|
4190
|
+
}
|
|
4191
|
+
const denom = n * sumX2 - sumX * sumX;
|
|
4192
|
+
const slope = denom !== 0 ? (n * sumXY - sumX * sumY) / denom : 0;
|
|
4193
|
+
const intercept = (sumY - slope * sumX) / n;
|
|
4194
|
+
const meanY = sumY / n;
|
|
4195
|
+
let ssRes = 0, ssTot = 0;
|
|
4196
|
+
for (let i = 0; i < n; i++) {
|
|
4197
|
+
const y = agentHistory[i].score;
|
|
4198
|
+
ssRes += (y - (intercept + slope * i)) ** 2;
|
|
4199
|
+
ssTot += (y - meanY) ** 2;
|
|
4200
|
+
}
|
|
4201
|
+
const r2 = ssTot > 0 ? 1 - ssRes / ssTot : 0;
|
|
4202
|
+
const status = r2 > 0.3 ? slope < 0 ? "degrading" : "improving" : "stable";
|
|
4203
|
+
res.json({
|
|
4204
|
+
drift: { status, slope, r2, windowSize: n, dataPoints: n },
|
|
4205
|
+
points: agentHistory
|
|
4206
|
+
});
|
|
4207
|
+
} catch {
|
|
4208
|
+
res.status(404).json({ error: "Drift detection not available" });
|
|
4209
|
+
}
|
|
4210
|
+
});
|
|
4211
|
+
this.app.get("/api/soma/cross-agent", (_req, res) => {
|
|
4212
|
+
const somaVault = this.config.somaVault;
|
|
4213
|
+
if (!somaVault) return res.json({ insights: [], pairs: [] });
|
|
4214
|
+
try {
|
|
4215
|
+
const insightDir = path4.join(somaVault, "insight");
|
|
4216
|
+
if (!fs4.existsSync(insightDir)) return res.json({ insights: [], pairs: [] });
|
|
4217
|
+
const crossAgent = [];
|
|
4218
|
+
for (const file of fs4.readdirSync(insightDir)) {
|
|
4219
|
+
if (!file.endsWith(".md")) continue;
|
|
4220
|
+
try {
|
|
4221
|
+
const content = fs4.readFileSync(path4.join(insightDir, file), "utf-8");
|
|
4222
|
+
const parsed = parseVaultFrontmatter(content);
|
|
4223
|
+
if (!parsed) continue;
|
|
4224
|
+
const sa = parsed.source_agents;
|
|
4225
|
+
if (!sa || !Array.isArray(sa) || sa.length < 2) continue;
|
|
4226
|
+
crossAgent.push({
|
|
4227
|
+
name: String(parsed.name ?? file.replace(".md", "")),
|
|
4228
|
+
claim: String(parsed.claim ?? "").slice(0, 200),
|
|
4229
|
+
sourceAgents: sa,
|
|
4230
|
+
tags: parsed.tags ?? []
|
|
4231
|
+
});
|
|
4232
|
+
} catch {
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
const pairMap = /* @__PURE__ */ new Map();
|
|
4236
|
+
for (const insight of crossAgent) {
|
|
4237
|
+
const key = [...insight.sourceAgents].sort().join(" \u2194 ");
|
|
4238
|
+
if (!pairMap.has(key)) pairMap.set(key, []);
|
|
4239
|
+
pairMap.get(key).push(insight);
|
|
4240
|
+
}
|
|
4241
|
+
const pairs = [...pairMap.entries()].map(([agents, insights]) => ({
|
|
4242
|
+
agents,
|
|
4243
|
+
count: insights.length,
|
|
4244
|
+
insights: insights.slice(0, 5)
|
|
4245
|
+
}));
|
|
4246
|
+
res.json({ total: crossAgent.length, pairs });
|
|
4247
|
+
} catch {
|
|
4248
|
+
res.json({ insights: [], pairs: [] });
|
|
4249
|
+
}
|
|
4250
|
+
});
|
|
4251
|
+
this.app.get("/api/external/commands", (_req, res) => {
|
|
4252
|
+
try {
|
|
4253
|
+
const { commands, errors } = getValidatedExternalCommands(this.userConfig);
|
|
4254
|
+
const commandList = Object.entries(commands).map(([id, command]) => ({
|
|
4255
|
+
id,
|
|
4256
|
+
name: command.name,
|
|
4257
|
+
description: command.description,
|
|
4258
|
+
category: command.category,
|
|
4259
|
+
allowConcurrent: command.allowConcurrent,
|
|
4260
|
+
timeout: command.timeout
|
|
4261
|
+
}));
|
|
4262
|
+
res.json({
|
|
4263
|
+
commands: commandList,
|
|
4264
|
+
configErrors: errors,
|
|
4265
|
+
total: commandList.length
|
|
4266
|
+
});
|
|
4267
|
+
} catch (error) {
|
|
4268
|
+
res.status(500).json({
|
|
4269
|
+
error: "Failed to load external commands",
|
|
4270
|
+
message: error.message
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
});
|
|
4274
|
+
this.app.post("/api/external/commands/:commandId/execute", express.json(), async (req, res) => {
|
|
4275
|
+
try {
|
|
4276
|
+
const { commandId } = req.params;
|
|
4277
|
+
const { additionalArgs, timeout, context } = req.body;
|
|
4278
|
+
if (!isValidId(commandId)) {
|
|
4279
|
+
return res.status(400).json({ error: "Invalid command ID" });
|
|
4280
|
+
}
|
|
4281
|
+
const executionResult = await this.commandExecutor.executeCommand({
|
|
4282
|
+
commandId,
|
|
4283
|
+
additionalArgs: Array.isArray(additionalArgs) ? additionalArgs : void 0,
|
|
4284
|
+
timeout: typeof timeout === "number" ? timeout : void 0,
|
|
4285
|
+
context: context && typeof context === "object" ? context : void 0
|
|
4286
|
+
});
|
|
4287
|
+
res.json({
|
|
4288
|
+
executionId: executionResult.executionId,
|
|
4289
|
+
started: executionResult.started,
|
|
4290
|
+
status: executionResult.status,
|
|
4291
|
+
commandName: executionResult.command.name,
|
|
4292
|
+
pid: executionResult.pid,
|
|
4293
|
+
startedAt: executionResult.startedAt,
|
|
4294
|
+
error: executionResult.error
|
|
4295
|
+
});
|
|
4296
|
+
} catch (error) {
|
|
4297
|
+
res.status(500).json({
|
|
4298
|
+
error: "Command execution failed",
|
|
4299
|
+
message: error.message
|
|
4300
|
+
});
|
|
4301
|
+
}
|
|
4302
|
+
});
|
|
4303
|
+
this.app.get("/api/external/executions/:executionId", (req, res) => {
|
|
4304
|
+
try {
|
|
4305
|
+
const { executionId } = req.params;
|
|
4306
|
+
if (!isValidId(executionId)) {
|
|
4307
|
+
return res.status(400).json({ error: "Invalid execution ID" });
|
|
4308
|
+
}
|
|
4309
|
+
const execution = this.commandExecutor.getExecution(executionId);
|
|
4310
|
+
if (!execution) {
|
|
4311
|
+
return res.status(404).json({ error: "Execution not found" });
|
|
4312
|
+
}
|
|
4313
|
+
res.json({
|
|
4314
|
+
executionId: execution.executionId,
|
|
4315
|
+
started: execution.started,
|
|
4316
|
+
status: execution.status,
|
|
4317
|
+
commandName: execution.command.name,
|
|
4318
|
+
pid: execution.pid,
|
|
4319
|
+
startedAt: execution.startedAt,
|
|
4320
|
+
completedAt: execution.completedAt,
|
|
4321
|
+
duration: execution.duration,
|
|
4322
|
+
exitCode: execution.exitCode,
|
|
4323
|
+
hasOutput: execution.stdout.length > 0,
|
|
4324
|
+
hasError: execution.stderr.length > 0,
|
|
4325
|
+
// Limit output size for API response
|
|
4326
|
+
stdout: execution.stdout.slice(-2e3),
|
|
4327
|
+
// Last 2KB
|
|
4328
|
+
stderr: execution.stderr.slice(-1e3),
|
|
4329
|
+
// Last 1KB
|
|
4330
|
+
error: execution.error
|
|
4331
|
+
});
|
|
4332
|
+
} catch (error) {
|
|
4333
|
+
res.status(500).json({
|
|
4334
|
+
error: "Failed to get execution status",
|
|
4335
|
+
message: error.message
|
|
4336
|
+
});
|
|
4337
|
+
}
|
|
4338
|
+
});
|
|
4339
|
+
this.app.post("/api/external/executions/:executionId/kill", (req, res) => {
|
|
4340
|
+
try {
|
|
4341
|
+
const { executionId } = req.params;
|
|
4342
|
+
if (!isValidId(executionId)) {
|
|
4343
|
+
return res.status(400).json({ error: "Invalid execution ID" });
|
|
4344
|
+
}
|
|
4345
|
+
const killed = this.commandExecutor.killExecution(executionId, "manual");
|
|
4346
|
+
if (!killed) {
|
|
4347
|
+
return res.status(404).json({ error: "Execution not found or not running" });
|
|
4348
|
+
}
|
|
4349
|
+
res.json({ success: true, message: "Execution killed" });
|
|
4350
|
+
} catch (error) {
|
|
4351
|
+
res.status(500).json({
|
|
4352
|
+
error: "Failed to kill execution",
|
|
4353
|
+
message: error.message
|
|
4354
|
+
});
|
|
4355
|
+
}
|
|
4356
|
+
});
|
|
4357
|
+
this.app.get("/api/external/executions", (req, res) => {
|
|
4358
|
+
try {
|
|
4359
|
+
const limit = Math.min(parseInt(String(req.query.limit)) || 50, 100);
|
|
4360
|
+
const status = req.query.status;
|
|
4361
|
+
let executions = this.commandExecutor.getAllExecutions(limit);
|
|
4362
|
+
if (status && ["running", "completed", "failed", "timeout", "killed"].includes(status)) {
|
|
4363
|
+
executions = executions.filter((exec) => exec.status === status);
|
|
4364
|
+
}
|
|
4365
|
+
const executionSummaries = executions.map((exec) => ({
|
|
4366
|
+
executionId: exec.executionId,
|
|
4367
|
+
commandName: exec.command.name,
|
|
4368
|
+
status: exec.status,
|
|
4369
|
+
startedAt: exec.startedAt,
|
|
4370
|
+
completedAt: exec.completedAt,
|
|
4371
|
+
duration: exec.duration,
|
|
4372
|
+
exitCode: exec.exitCode,
|
|
4373
|
+
hasOutput: exec.stdout.length > 0,
|
|
4374
|
+
hasError: exec.stderr.length > 0,
|
|
4375
|
+
error: exec.error
|
|
4376
|
+
}));
|
|
4377
|
+
res.json({
|
|
4378
|
+
executions: executionSummaries,
|
|
4379
|
+
total: executionSummaries.length,
|
|
4380
|
+
running: this.commandExecutor.getRunningExecutions().length
|
|
4381
|
+
});
|
|
4382
|
+
} catch (error) {
|
|
4383
|
+
res.status(500).json({
|
|
4384
|
+
error: "Failed to get executions",
|
|
4385
|
+
message: error.message
|
|
4386
|
+
});
|
|
4387
|
+
}
|
|
4388
|
+
});
|
|
4389
|
+
this.app.get("/api/external/executions/:executionId/audit", (req, res) => {
|
|
4390
|
+
try {
|
|
4391
|
+
const { executionId } = req.params;
|
|
4392
|
+
if (!isValidId(executionId)) {
|
|
4393
|
+
return res.status(400).json({ error: "Invalid execution ID" });
|
|
4394
|
+
}
|
|
4395
|
+
const auditTrail = this.commandExecutor.getAuditTrail(executionId);
|
|
4396
|
+
res.json({
|
|
4397
|
+
executionId,
|
|
4398
|
+
auditEntries: auditTrail,
|
|
4399
|
+
total: auditTrail.length
|
|
4400
|
+
});
|
|
4401
|
+
} catch (error) {
|
|
4402
|
+
res.status(500).json({
|
|
4403
|
+
error: "Failed to get audit trail",
|
|
4404
|
+
message: error.message
|
|
4405
|
+
});
|
|
4406
|
+
}
|
|
4407
|
+
});
|
|
4408
|
+
this.app.get("/api/external/audit/stats", (req, res) => {
|
|
4409
|
+
try {
|
|
4410
|
+
const days = Math.min(parseInt(String(req.query.days)) || 7, 30);
|
|
4411
|
+
const stats = this.commandExecutor.getAuditStats(days);
|
|
4412
|
+
res.json({
|
|
4413
|
+
period: `${days} days`,
|
|
4414
|
+
...stats
|
|
4415
|
+
});
|
|
4416
|
+
} catch (error) {
|
|
4417
|
+
res.status(500).json({
|
|
4418
|
+
error: "Failed to get audit statistics",
|
|
4419
|
+
message: error.message
|
|
4420
|
+
});
|
|
4421
|
+
}
|
|
4422
|
+
});
|
|
2961
4423
|
this.app.get("/api/process-health", (_req, res) => {
|
|
2962
4424
|
var _a, _b;
|
|
2963
4425
|
try {
|
|
@@ -2967,7 +4429,7 @@ var DashboardServer = class {
|
|
|
2967
4429
|
}
|
|
2968
4430
|
const discoveryDirs = [
|
|
2969
4431
|
this.config.tracesDir,
|
|
2970
|
-
|
|
4432
|
+
path4.dirname(this.config.tracesDir),
|
|
2971
4433
|
...this.config.dataDirs || []
|
|
2972
4434
|
];
|
|
2973
4435
|
let configs = discoverAllProcessConfigs(discoveryDirs);
|
|
@@ -3004,9 +4466,7 @@ var DashboardServer = class {
|
|
|
3004
4466
|
const orphans = uniqueProcesses.filter(
|
|
3005
4467
|
(p) => !allKnownPids.has(p.pid) && p.pid !== process.pid && p.pid !== process.ppid
|
|
3006
4468
|
);
|
|
3007
|
-
const problems = services.flatMap(
|
|
3008
|
-
(s) => s.audit.problems.map((p) => `[${s.name}] ${p}`)
|
|
3009
|
-
);
|
|
4469
|
+
const problems = services.flatMap((s) => s.audit.problems.map((p) => `[${s.name}] ${p}`));
|
|
3010
4470
|
const result = {
|
|
3011
4471
|
// Backward-compatible fields from primary service
|
|
3012
4472
|
pidFile: (primary == null ? void 0 : primary.audit.pidFile) ?? null,
|
|
@@ -3032,7 +4492,7 @@ var DashboardServer = class {
|
|
|
3032
4492
|
// Topology edges: parent-child relationships from process ppid
|
|
3033
4493
|
topology: uniqueProcesses.map((p) => {
|
|
3034
4494
|
try {
|
|
3035
|
-
const statusContent =
|
|
4495
|
+
const statusContent = fs4.readFileSync(`/proc/${p.pid}/status`, "utf8");
|
|
3036
4496
|
const ppidMatch = statusContent.match(/^PPid:\s+(\d+)/m);
|
|
3037
4497
|
const ppid = ppidMatch ? parseInt(ppidMatch[1] ?? "0", 10) : 0;
|
|
3038
4498
|
if (ppid > 1 && allKnownPids.has(ppid)) {
|
|
@@ -3052,34 +4512,39 @@ var DashboardServer = class {
|
|
|
3052
4512
|
this.app.get("/api/directories", (_req, res) => {
|
|
3053
4513
|
try {
|
|
3054
4514
|
const home = process.env.HOME ?? "/home/trader";
|
|
3055
|
-
const configPath =
|
|
4515
|
+
const configPath = path4.join(home, ".agentflow/dashboard-config.json");
|
|
3056
4516
|
let extraDirs = [];
|
|
3057
4517
|
try {
|
|
3058
|
-
if (
|
|
3059
|
-
const cfg = JSON.parse(
|
|
4518
|
+
if (fs4.existsSync(configPath)) {
|
|
4519
|
+
const cfg = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
3060
4520
|
extraDirs = cfg.extraDirs ?? [];
|
|
3061
4521
|
}
|
|
3062
4522
|
} catch {
|
|
3063
4523
|
}
|
|
3064
|
-
const watched = [
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
4524
|
+
const watched = [
|
|
4525
|
+
...new Set(
|
|
4526
|
+
[this.config.tracesDir, ...this.config.dataDirs || [], ...extraDirs].map(
|
|
4527
|
+
(w) => path4.resolve(w)
|
|
4528
|
+
)
|
|
4529
|
+
)
|
|
4530
|
+
];
|
|
3069
4531
|
const discovered = [];
|
|
3070
4532
|
const svcNames = getSystemdServices(this.userConfig);
|
|
3071
4533
|
if (svcNames.length > 0) {
|
|
3072
4534
|
try {
|
|
3073
|
-
const
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
{
|
|
4535
|
+
const raw = execFileSync(
|
|
4536
|
+
"systemctl",
|
|
4537
|
+
["--user", "show", "--property=ExecStart", "--no-pager", ...svcNames],
|
|
4538
|
+
{
|
|
4539
|
+
encoding: "utf8",
|
|
4540
|
+
timeout: 5e3
|
|
4541
|
+
}
|
|
3077
4542
|
);
|
|
3078
4543
|
for (const line of raw.split("\n")) {
|
|
3079
4544
|
const match = line.match(/path=([^\s;]+)/);
|
|
3080
4545
|
if (match == null ? void 0 : match[1]) {
|
|
3081
|
-
const dir =
|
|
3082
|
-
if (
|
|
4546
|
+
const dir = path4.dirname(match[1]);
|
|
4547
|
+
if (fs4.existsSync(dir)) discovered.push(dir);
|
|
3083
4548
|
}
|
|
3084
4549
|
}
|
|
3085
4550
|
} catch {
|
|
@@ -3087,15 +4552,15 @@ var DashboardServer = class {
|
|
|
3087
4552
|
}
|
|
3088
4553
|
const commonPaths = [
|
|
3089
4554
|
...getDiscoveryPaths(this.userConfig),
|
|
3090
|
-
|
|
4555
|
+
path4.join(home, ".agentflow/traces")
|
|
3091
4556
|
];
|
|
3092
4557
|
for (const p of commonPaths) {
|
|
3093
|
-
if (
|
|
4558
|
+
if (fs4.existsSync(p) && !discovered.includes(p)) {
|
|
3094
4559
|
discovered.push(p);
|
|
3095
4560
|
}
|
|
3096
4561
|
}
|
|
3097
|
-
const watchedSet = new Set(watched.map((w) =>
|
|
3098
|
-
const suggested = discovered.filter((d) => !watchedSet.has(
|
|
4562
|
+
const watchedSet = new Set(watched.map((w) => path4.resolve(w)));
|
|
4563
|
+
const suggested = discovered.filter((d) => !watchedSet.has(path4.resolve(d)));
|
|
3099
4564
|
res.json({ watched, discovered, suggested });
|
|
3100
4565
|
} catch (error) {
|
|
3101
4566
|
console.error("Directory discovery error:", error);
|
|
@@ -3105,14 +4570,23 @@ var DashboardServer = class {
|
|
|
3105
4570
|
this.app.post("/api/directories", express.json(), (req, res) => {
|
|
3106
4571
|
try {
|
|
3107
4572
|
const { add, remove } = req.body;
|
|
3108
|
-
if (add
|
|
3109
|
-
|
|
4573
|
+
if (add) {
|
|
4574
|
+
const resolved = path4.resolve(add);
|
|
4575
|
+
if (resolved !== add || add.includes("..")) {
|
|
4576
|
+
return res.status(400).json({ error: "Invalid directory path" });
|
|
4577
|
+
}
|
|
4578
|
+
if (!fs4.existsSync(resolved)) {
|
|
4579
|
+
return res.status(400).json({ error: `Directory does not exist: ${add}` });
|
|
4580
|
+
}
|
|
3110
4581
|
}
|
|
3111
|
-
const configPath =
|
|
4582
|
+
const configPath = path4.join(
|
|
4583
|
+
process.env.HOME ?? "/home/trader",
|
|
4584
|
+
".agentflow/dashboard-config.json"
|
|
4585
|
+
);
|
|
3112
4586
|
let config = {};
|
|
3113
4587
|
try {
|
|
3114
|
-
if (
|
|
3115
|
-
config = JSON.parse(
|
|
4588
|
+
if (fs4.existsSync(configPath)) {
|
|
4589
|
+
config = JSON.parse(fs4.readFileSync(configPath, "utf-8"));
|
|
3116
4590
|
}
|
|
3117
4591
|
} catch {
|
|
3118
4592
|
}
|
|
@@ -3123,8 +4597,8 @@ var DashboardServer = class {
|
|
|
3123
4597
|
if (remove) {
|
|
3124
4598
|
config.extraDirs = config.extraDirs.filter((d) => d !== remove);
|
|
3125
4599
|
}
|
|
3126
|
-
|
|
3127
|
-
|
|
4600
|
+
fs4.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
4601
|
+
fs4.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
3128
4602
|
res.json({ ok: true, extraDirs: config.extraDirs });
|
|
3129
4603
|
} catch (error) {
|
|
3130
4604
|
console.error("Directory config error:", error);
|
|
@@ -3166,7 +4640,10 @@ var DashboardServer = class {
|
|
|
3166
4640
|
lastModified: Date.now(),
|
|
3167
4641
|
sourceDir: "http-collector"
|
|
3168
4642
|
};
|
|
3169
|
-
this.watcher.traces.set(
|
|
4643
|
+
this.watcher.traces.set(
|
|
4644
|
+
`otel:${trace.id}`,
|
|
4645
|
+
watched
|
|
4646
|
+
);
|
|
3170
4647
|
ingested++;
|
|
3171
4648
|
}
|
|
3172
4649
|
if (ingested > 0) {
|
|
@@ -3190,14 +4667,18 @@ var DashboardServer = class {
|
|
|
3190
4667
|
this.app.get("/ready", (_req, res) => {
|
|
3191
4668
|
res.json({ status: "ready" });
|
|
3192
4669
|
});
|
|
3193
|
-
this.app.get(
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
4670
|
+
this.app.get(
|
|
4671
|
+
"*",
|
|
4672
|
+
rateLimit({ windowMs: 60 * 1e3, max: 600, standardHeaders: true, legacyHeaders: false }),
|
|
4673
|
+
(_req, res) => {
|
|
4674
|
+
const clientIndex2 = path4.join(__dirname, "../dist/client/index.html");
|
|
4675
|
+
if (fs4.existsSync(clientIndex2)) {
|
|
4676
|
+
res.sendFile(clientIndex2);
|
|
4677
|
+
} else {
|
|
4678
|
+
res.status(404).send("Dashboard not found - public files may not be built");
|
|
4679
|
+
}
|
|
3199
4680
|
}
|
|
3200
|
-
|
|
4681
|
+
);
|
|
3201
4682
|
}
|
|
3202
4683
|
setupWebSocket() {
|
|
3203
4684
|
this.wss.on("connection", (ws) => {
|
|
@@ -3223,9 +4704,9 @@ var DashboardServer = class {
|
|
|
3223
4704
|
setupSomaReportWatcher() {
|
|
3224
4705
|
const somaVault = this.config.somaVault;
|
|
3225
4706
|
if (!somaVault) return;
|
|
3226
|
-
const reportPath =
|
|
3227
|
-
const reportDir =
|
|
3228
|
-
if (!
|
|
4707
|
+
const reportPath = path4.join(somaVault, "..", "soma-report.json");
|
|
4708
|
+
const reportDir = path4.dirname(reportPath);
|
|
4709
|
+
if (!fs4.existsSync(reportDir)) return;
|
|
3229
4710
|
let debounceTimer = null;
|
|
3230
4711
|
const watcher = chokidar2.watch(reportPath, {
|
|
3231
4712
|
ignoreInitial: true,
|
|
@@ -3237,7 +4718,7 @@ var DashboardServer = class {
|
|
|
3237
4718
|
debounceTimer = setTimeout(() => {
|
|
3238
4719
|
var _a, _b;
|
|
3239
4720
|
try {
|
|
3240
|
-
const report = JSON.parse(
|
|
4721
|
+
const report = JSON.parse(fs4.readFileSync(reportPath, "utf-8"));
|
|
3241
4722
|
this.broadcast({ type: "soma-report-updated", data: report });
|
|
3242
4723
|
if (report.generatedAt) {
|
|
3243
4724
|
this.broadcast({
|
|
@@ -3268,7 +4749,10 @@ var DashboardServer = class {
|
|
|
3268
4749
|
const nodes = trace.nodes;
|
|
3269
4750
|
if (!nodes || typeof nodes === "object" && Object.keys(nodes).length === 0) continue;
|
|
3270
4751
|
const nodeValues = Object.values(nodes);
|
|
3271
|
-
if (nodeValues.some(
|
|
4752
|
+
if (nodeValues.some(
|
|
4753
|
+
(n) => n.type === "log-file" || n.type === "log-entry"
|
|
4754
|
+
))
|
|
4755
|
+
continue;
|
|
3272
4756
|
graphs.push(loadGraph2(trace));
|
|
3273
4757
|
} catch {
|
|
3274
4758
|
}
|
|
@@ -3318,13 +4802,31 @@ var DashboardServer = class {
|
|
|
3318
4802
|
isVirtual: false
|
|
3319
4803
|
});
|
|
3320
4804
|
}
|
|
3321
|
-
const
|
|
3322
|
-
const
|
|
3323
|
-
const
|
|
3324
|
-
for (const
|
|
4805
|
+
const _rootSteps = new Set(model.steps);
|
|
4806
|
+
const _childSteps = new Set(model.transitions.map((t) => t.to));
|
|
4807
|
+
const _leafSteps = new Set(model.steps);
|
|
4808
|
+
for (const _t of model.transitions) {
|
|
3325
4809
|
}
|
|
3326
|
-
nodes.push({
|
|
3327
|
-
|
|
4810
|
+
nodes.push({
|
|
4811
|
+
id: "[START]",
|
|
4812
|
+
label: "[START]",
|
|
4813
|
+
count: model.totalGraphs,
|
|
4814
|
+
frequency: 1,
|
|
4815
|
+
avgDuration: 0,
|
|
4816
|
+
failRate: 0,
|
|
4817
|
+
p95Duration: 0,
|
|
4818
|
+
isVirtual: true
|
|
4819
|
+
});
|
|
4820
|
+
nodes.push({
|
|
4821
|
+
id: "[END]",
|
|
4822
|
+
label: "[END]",
|
|
4823
|
+
count: model.totalGraphs,
|
|
4824
|
+
frequency: 1,
|
|
4825
|
+
avgDuration: 0,
|
|
4826
|
+
failRate: 0,
|
|
4827
|
+
p95Duration: 0,
|
|
4828
|
+
isVirtual: true
|
|
4829
|
+
});
|
|
3328
4830
|
const edges = model.transitions.map((t) => ({
|
|
3329
4831
|
source: t.from,
|
|
3330
4832
|
target: t.to,
|
|
@@ -3388,7 +4890,7 @@ var DashboardServer = class {
|
|
|
3388
4890
|
}
|
|
3389
4891
|
const seq = ["[START]", ...activities.map((a) => a.name), "[END]"];
|
|
3390
4892
|
for (let i = 0; i < seq.length; i++) {
|
|
3391
|
-
const act = seq[i];
|
|
4893
|
+
const act = seq[i] ?? "";
|
|
3392
4894
|
activityCounts.set(act, (activityCounts.get(act) || 0) + 1);
|
|
3393
4895
|
if (i < seq.length - 1) {
|
|
3394
4896
|
const key = `${act} \u2192 ${seq[i + 1]}`;
|
|
@@ -3487,15 +4989,15 @@ var DashboardServer = class {
|
|
|
3487
4989
|
/** Check if any src/client file is newer than the built bundle. */
|
|
3488
4990
|
isClientStale(srcDir, distDir) {
|
|
3489
4991
|
try {
|
|
3490
|
-
const distIndex =
|
|
3491
|
-
if (!
|
|
3492
|
-
const distMtime =
|
|
4992
|
+
const distIndex = path4.join(distDir, "index.html");
|
|
4993
|
+
if (!fs4.existsSync(distIndex)) return true;
|
|
4994
|
+
const distMtime = fs4.statSync(distIndex).mtimeMs;
|
|
3493
4995
|
const check = (dir) => {
|
|
3494
|
-
for (const entry of
|
|
3495
|
-
const full =
|
|
4996
|
+
for (const entry of fs4.readdirSync(dir, { withFileTypes: true })) {
|
|
4997
|
+
const full = path4.join(dir, entry.name);
|
|
3496
4998
|
if (entry.isDirectory()) {
|
|
3497
4999
|
if (check(full)) return true;
|
|
3498
|
-
} else if (
|
|
5000
|
+
} else if (fs4.statSync(full).mtimeMs > distMtime) {
|
|
3499
5001
|
return true;
|
|
3500
5002
|
}
|
|
3501
5003
|
}
|
|
@@ -3521,6 +5023,9 @@ var DashboardServer = class {
|
|
|
3521
5023
|
getStats() {
|
|
3522
5024
|
return this.stats.getGlobalStats();
|
|
3523
5025
|
}
|
|
5026
|
+
getTrace(filename) {
|
|
5027
|
+
return this.watcher.getTrace(filename);
|
|
5028
|
+
}
|
|
3524
5029
|
getTraces() {
|
|
3525
5030
|
return this.watcher.getAllTraces();
|
|
3526
5031
|
}
|