sentinelayer-cli 0.1.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +998 -996
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +63 -54
- package/src/agents/jules/config/definition.js +209 -209
- package/src/agents/jules/config/system-prompt.js +175 -175
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +377 -377
- package/src/agents/jules/loop.js +367 -367
- package/src/agents/jules/pulse.js +327 -319
- package/src/agents/jules/stream.js +186 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +308 -308
- package/src/agents/jules/tools/auth-audit.js +557 -222
- package/src/agents/jules/tools/dispatch.js +327 -327
- package/src/agents/jules/tools/file-edit.js +180 -180
- package/src/agents/jules/tools/file-read.js +100 -100
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +168 -168
- package/src/agents/jules/tools/grep.js +228 -228
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +161 -161
- package/src/agents/jules/tools/runtime-audit.js +503 -493
- package/src/agents/jules/tools/shell.js +383 -383
- package/src/agents/jules/tools/url-policy.js +100 -0
- package/src/ai/aidenid.js +972 -945
- package/src/ai/client.js +508 -508
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/gate.js +45 -11
- package/src/auth/http.js +270 -113
- package/src/auth/service.js +891 -848
- package/src/auth/session-store.js +359 -345
- package/src/cli.js +252 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1337
- package/src/commands/ai/provision-governance.js +1272 -1246
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1166 -1166
- package/src/commands/auth.js +375 -366
- package/src/commands/chat.js +191 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +10 -10
- package/src/commands/mcp.js +461 -404
- package/src/commands/omargate.js +15 -15
- package/src/commands/persona.js +20 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +866 -788
- package/src/commands/spec.js +716 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +510 -510
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tracker.js +171 -171
- package/src/daemon/artifact-lineage.js +534 -534
- package/src/daemon/assignment-ledger.js +770 -770
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +626 -626
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/jira-lifecycle.js +632 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/watchdog.js +971 -971
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/legacy-cli.js +2592 -2435
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +118 -106
- package/src/review/ai-review.js +669 -669
- package/src/review/local-review.js +1295 -1284
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -0
- package/src/scaffold/templates.js +150 -0
- package/src/scan/generator.js +418 -351
- package/src/scan/gh-secrets.js +107 -0
- package/src/spec/generator.js +519 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +576 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/sync.js +107 -61
- package/src/ui/markdown.js +220 -220
package/src/swarm/dashboard.js
CHANGED
|
@@ -1,247 +1,247 @@
|
|
|
1
|
-
import fsp from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
import { resolveOutputRoot } from "../config/service.js";
|
|
5
|
-
|
|
6
|
-
function normalizeString(value) {
|
|
7
|
-
return String(value || "").trim();
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function sleep(ms) {
|
|
11
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function pathExists(targetPath) {
|
|
15
|
-
try {
|
|
16
|
-
await fsp.access(targetPath);
|
|
17
|
-
return true;
|
|
18
|
-
} catch {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function toAgentRows(summary = {}, events = []) {
|
|
24
|
-
const byAgent = new Map();
|
|
25
|
-
for (const agentId of summary.selectedAgents || []) {
|
|
26
|
-
byAgent.set(agentId, {
|
|
27
|
-
agentId,
|
|
28
|
-
eventCount: 0,
|
|
29
|
-
lastEventType: "",
|
|
30
|
-
lastTimestamp: "",
|
|
31
|
-
lastMessage: "",
|
|
32
|
-
status: summary.completed ? "completed" : "running",
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
for (const event of events) {
|
|
37
|
-
const agentId = normalizeString(event.agentId || "unknown").toLowerCase() || "unknown";
|
|
38
|
-
const current = byAgent.get(agentId) || {
|
|
39
|
-
agentId,
|
|
40
|
-
eventCount: 0,
|
|
41
|
-
lastEventType: "",
|
|
42
|
-
lastTimestamp: "",
|
|
43
|
-
lastMessage: "",
|
|
44
|
-
status: summary.completed ? "completed" : "running",
|
|
45
|
-
};
|
|
46
|
-
current.eventCount += 1;
|
|
47
|
-
current.lastEventType = normalizeString(event.eventType);
|
|
48
|
-
current.lastTimestamp = normalizeString(event.timestamp);
|
|
49
|
-
current.lastMessage = normalizeString(event.message);
|
|
50
|
-
byAgent.set(agentId, current);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (!summary.completed && summary.stop?.blocking) {
|
|
54
|
-
const omar = byAgent.get("omar") || {
|
|
55
|
-
agentId: "omar",
|
|
56
|
-
eventCount: 0,
|
|
57
|
-
lastEventType: "",
|
|
58
|
-
lastTimestamp: "",
|
|
59
|
-
lastMessage: "",
|
|
60
|
-
status: "stopped",
|
|
61
|
-
};
|
|
62
|
-
omar.status = "stopped";
|
|
63
|
-
byAgent.set("omar", omar);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return [...byAgent.values()].sort((left, right) => left.agentId.localeCompare(right.agentId));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export async function resolveSwarmRuntimeFiles({
|
|
70
|
-
targetPath = ".",
|
|
71
|
-
outputDir = "",
|
|
72
|
-
runId = "",
|
|
73
|
-
env,
|
|
74
|
-
homeDir,
|
|
75
|
-
} = {}) {
|
|
76
|
-
const normalizedTargetPath = path.resolve(String(targetPath || "."));
|
|
77
|
-
const outputRoot = await resolveOutputRoot({
|
|
78
|
-
cwd: normalizedTargetPath,
|
|
79
|
-
outputDirOverride: outputDir,
|
|
80
|
-
env,
|
|
81
|
-
homeDir,
|
|
82
|
-
});
|
|
83
|
-
const swarmsDirectory = path.join(outputRoot, "swarms");
|
|
84
|
-
const requestedRunId = normalizeString(runId);
|
|
85
|
-
if (requestedRunId) {
|
|
86
|
-
const runtimeDirectory = path.join(swarmsDirectory, requestedRunId, "runtime");
|
|
87
|
-
return {
|
|
88
|
-
outputRoot,
|
|
89
|
-
runId: requestedRunId,
|
|
90
|
-
runtimeDirectory,
|
|
91
|
-
runtimeJsonPath: path.join(runtimeDirectory, "SWARM_RUNTIME.json"),
|
|
92
|
-
runtimeEventsPath: path.join(runtimeDirectory, "events.ndjson"),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const entries = await fsp.readdir(swarmsDirectory, { withFileTypes: true });
|
|
97
|
-
const runtimeCandidates = [];
|
|
98
|
-
for (const entry of entries) {
|
|
99
|
-
if (!entry.isDirectory()) {
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (!entry.name.startsWith("swarm-runtime-")) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
const runtimeDirectory = path.join(swarmsDirectory, entry.name, "runtime");
|
|
106
|
-
const runtimeJsonPath = path.join(runtimeDirectory, "SWARM_RUNTIME.json");
|
|
107
|
-
if (!(await pathExists(runtimeJsonPath))) {
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
const stats = await fsp.stat(runtimeJsonPath);
|
|
111
|
-
runtimeCandidates.push({
|
|
112
|
-
runId: entry.name,
|
|
113
|
-
runtimeDirectory,
|
|
114
|
-
runtimeJsonPath,
|
|
115
|
-
runtimeEventsPath: path.join(runtimeDirectory, "events.ndjson"),
|
|
116
|
-
mtimeMs: stats.mtimeMs,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (runtimeCandidates.length === 0) {
|
|
121
|
-
throw new Error("No swarm runtime artifacts found.");
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
runtimeCandidates.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
125
|
-
const latest = runtimeCandidates[0];
|
|
126
|
-
return {
|
|
127
|
-
outputRoot,
|
|
128
|
-
runId: latest.runId,
|
|
129
|
-
runtimeDirectory: latest.runtimeDirectory,
|
|
130
|
-
runtimeJsonPath: latest.runtimeJsonPath,
|
|
131
|
-
runtimeEventsPath: latest.runtimeEventsPath,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export async function loadSwarmDashboardSnapshot({
|
|
136
|
-
targetPath = ".",
|
|
137
|
-
outputDir = "",
|
|
138
|
-
runId = "",
|
|
139
|
-
env,
|
|
140
|
-
homeDir,
|
|
141
|
-
} = {}) {
|
|
142
|
-
const files = await resolveSwarmRuntimeFiles({
|
|
143
|
-
targetPath,
|
|
144
|
-
outputDir,
|
|
145
|
-
runId,
|
|
146
|
-
env,
|
|
147
|
-
homeDir,
|
|
148
|
-
});
|
|
149
|
-
const summary = JSON.parse(await fsp.readFile(files.runtimeJsonPath, "utf-8"));
|
|
150
|
-
const eventsRaw = await fsp.readFile(files.runtimeEventsPath, "utf-8");
|
|
151
|
-
const events = String(eventsRaw || "")
|
|
152
|
-
.split(/\r?\n/)
|
|
153
|
-
.map((line) => line.trim())
|
|
154
|
-
.filter(Boolean)
|
|
155
|
-
.map((line) => JSON.parse(line));
|
|
156
|
-
|
|
157
|
-
const snapshot = {
|
|
158
|
-
runId: summary.runId,
|
|
159
|
-
planRunId: summary.planRunId,
|
|
160
|
-
scenario: summary.scenario,
|
|
161
|
-
engine: summary.engine,
|
|
162
|
-
execute: Boolean(summary.execute),
|
|
163
|
-
completed: Boolean(summary.completed),
|
|
164
|
-
stop: summary.stop || { stopClass: "NONE", reason: "", blocking: false },
|
|
165
|
-
usage: summary.usage || {},
|
|
166
|
-
eventCount: events.length,
|
|
167
|
-
runtimeJsonPath: files.runtimeJsonPath,
|
|
168
|
-
runtimeEventsPath: files.runtimeEventsPath,
|
|
169
|
-
runtimeDirectory: files.runtimeDirectory,
|
|
170
|
-
generatedAt: new Date().toISOString(),
|
|
171
|
-
agentRows: toAgentRows(summary, events),
|
|
172
|
-
recentEvents: events.slice(-10),
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
return snapshot;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function renderSwarmDashboard(snapshot = {}) {
|
|
179
|
-
const header = [
|
|
180
|
-
`Swarm run: ${snapshot.runId}`,
|
|
181
|
-
`Scenario: ${snapshot.scenario}`,
|
|
182
|
-
`Status: ${snapshot.completed ? "completed" : "running"}${snapshot.stop?.blocking ? ` (${snapshot.stop.stopClass})` : ""}`,
|
|
183
|
-
`Usage: tokens=${snapshot.usage.outputTokens || 0} tools=${snapshot.usage.toolCalls || 0} runtime_ms=${snapshot.usage.durationMs || 0} cost_usd=${snapshot.usage.costUsd || 0}`,
|
|
184
|
-
].join("\n");
|
|
185
|
-
|
|
186
|
-
const rows = (snapshot.agentRows || [])
|
|
187
|
-
.map(
|
|
188
|
-
(row) =>
|
|
189
|
-
`- ${row.agentId} status=${row.status} events=${row.eventCount} last=${row.lastEventType || "n/a"}`
|
|
190
|
-
)
|
|
191
|
-
.join("\n");
|
|
192
|
-
|
|
193
|
-
return `${header}\nAgents:\n${rows || "- none"}`;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export async function watchSwarmDashboard({
|
|
197
|
-
targetPath = ".",
|
|
198
|
-
outputDir = "",
|
|
199
|
-
runId = "",
|
|
200
|
-
pollSeconds = 2,
|
|
201
|
-
maxIdleSeconds = 20,
|
|
202
|
-
env,
|
|
203
|
-
homeDir,
|
|
204
|
-
onSnapshot,
|
|
205
|
-
} = {}) {
|
|
206
|
-
const pollMs = Math.max(250, Math.floor(Number(pollSeconds || 2) * 1000));
|
|
207
|
-
const maxIdleMs = Math.max(1000, Math.floor(Number(maxIdleSeconds || 20) * 1000));
|
|
208
|
-
let lastSignature = "";
|
|
209
|
-
let lastUpdate = Date.now();
|
|
210
|
-
const snapshots = [];
|
|
211
|
-
|
|
212
|
-
while (true) {
|
|
213
|
-
const snapshot = await loadSwarmDashboardSnapshot({
|
|
214
|
-
targetPath,
|
|
215
|
-
outputDir,
|
|
216
|
-
runId,
|
|
217
|
-
env,
|
|
218
|
-
homeDir,
|
|
219
|
-
});
|
|
220
|
-
const signature = `${snapshot.eventCount}:${snapshot.usage.toolCalls || 0}:${snapshot.completed}:${snapshot.stop?.stopClass || "NONE"}`;
|
|
221
|
-
if (signature !== lastSignature) {
|
|
222
|
-
lastSignature = signature;
|
|
223
|
-
lastUpdate = Date.now();
|
|
224
|
-
snapshots.push(snapshot);
|
|
225
|
-
if (onSnapshot) {
|
|
226
|
-
await onSnapshot(snapshot);
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (snapshot.completed || snapshot.stop?.blocking) {
|
|
231
|
-
return {
|
|
232
|
-
finalSnapshot: snapshot,
|
|
233
|
-
snapshots,
|
|
234
|
-
stopReason: snapshot.completed ? "COMPLETED" : snapshot.stop?.stopClass || "STOPPED",
|
|
235
|
-
};
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (Date.now() - lastUpdate >= maxIdleMs) {
|
|
239
|
-
return {
|
|
240
|
-
finalSnapshot: snapshot,
|
|
241
|
-
snapshots,
|
|
242
|
-
stopReason: "IDLE_TIMEOUT",
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
await sleep(pollMs);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
1
|
+
import fsp from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { resolveOutputRoot } from "../config/service.js";
|
|
5
|
+
|
|
6
|
+
function normalizeString(value) {
|
|
7
|
+
return String(value || "").trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function sleep(ms) {
|
|
11
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function pathExists(targetPath) {
|
|
15
|
+
try {
|
|
16
|
+
await fsp.access(targetPath);
|
|
17
|
+
return true;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function toAgentRows(summary = {}, events = []) {
|
|
24
|
+
const byAgent = new Map();
|
|
25
|
+
for (const agentId of summary.selectedAgents || []) {
|
|
26
|
+
byAgent.set(agentId, {
|
|
27
|
+
agentId,
|
|
28
|
+
eventCount: 0,
|
|
29
|
+
lastEventType: "",
|
|
30
|
+
lastTimestamp: "",
|
|
31
|
+
lastMessage: "",
|
|
32
|
+
status: summary.completed ? "completed" : "running",
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
for (const event of events) {
|
|
37
|
+
const agentId = normalizeString(event.agentId || "unknown").toLowerCase() || "unknown";
|
|
38
|
+
const current = byAgent.get(agentId) || {
|
|
39
|
+
agentId,
|
|
40
|
+
eventCount: 0,
|
|
41
|
+
lastEventType: "",
|
|
42
|
+
lastTimestamp: "",
|
|
43
|
+
lastMessage: "",
|
|
44
|
+
status: summary.completed ? "completed" : "running",
|
|
45
|
+
};
|
|
46
|
+
current.eventCount += 1;
|
|
47
|
+
current.lastEventType = normalizeString(event.eventType);
|
|
48
|
+
current.lastTimestamp = normalizeString(event.timestamp);
|
|
49
|
+
current.lastMessage = normalizeString(event.message);
|
|
50
|
+
byAgent.set(agentId, current);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!summary.completed && summary.stop?.blocking) {
|
|
54
|
+
const omar = byAgent.get("omar") || {
|
|
55
|
+
agentId: "omar",
|
|
56
|
+
eventCount: 0,
|
|
57
|
+
lastEventType: "",
|
|
58
|
+
lastTimestamp: "",
|
|
59
|
+
lastMessage: "",
|
|
60
|
+
status: "stopped",
|
|
61
|
+
};
|
|
62
|
+
omar.status = "stopped";
|
|
63
|
+
byAgent.set("omar", omar);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return [...byAgent.values()].sort((left, right) => left.agentId.localeCompare(right.agentId));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function resolveSwarmRuntimeFiles({
|
|
70
|
+
targetPath = ".",
|
|
71
|
+
outputDir = "",
|
|
72
|
+
runId = "",
|
|
73
|
+
env,
|
|
74
|
+
homeDir,
|
|
75
|
+
} = {}) {
|
|
76
|
+
const normalizedTargetPath = path.resolve(String(targetPath || "."));
|
|
77
|
+
const outputRoot = await resolveOutputRoot({
|
|
78
|
+
cwd: normalizedTargetPath,
|
|
79
|
+
outputDirOverride: outputDir,
|
|
80
|
+
env,
|
|
81
|
+
homeDir,
|
|
82
|
+
});
|
|
83
|
+
const swarmsDirectory = path.join(outputRoot, "swarms");
|
|
84
|
+
const requestedRunId = normalizeString(runId);
|
|
85
|
+
if (requestedRunId) {
|
|
86
|
+
const runtimeDirectory = path.join(swarmsDirectory, requestedRunId, "runtime");
|
|
87
|
+
return {
|
|
88
|
+
outputRoot,
|
|
89
|
+
runId: requestedRunId,
|
|
90
|
+
runtimeDirectory,
|
|
91
|
+
runtimeJsonPath: path.join(runtimeDirectory, "SWARM_RUNTIME.json"),
|
|
92
|
+
runtimeEventsPath: path.join(runtimeDirectory, "events.ndjson"),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const entries = await fsp.readdir(swarmsDirectory, { withFileTypes: true });
|
|
97
|
+
const runtimeCandidates = [];
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (!entry.isDirectory()) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!entry.name.startsWith("swarm-runtime-")) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const runtimeDirectory = path.join(swarmsDirectory, entry.name, "runtime");
|
|
106
|
+
const runtimeJsonPath = path.join(runtimeDirectory, "SWARM_RUNTIME.json");
|
|
107
|
+
if (!(await pathExists(runtimeJsonPath))) {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const stats = await fsp.stat(runtimeJsonPath);
|
|
111
|
+
runtimeCandidates.push({
|
|
112
|
+
runId: entry.name,
|
|
113
|
+
runtimeDirectory,
|
|
114
|
+
runtimeJsonPath,
|
|
115
|
+
runtimeEventsPath: path.join(runtimeDirectory, "events.ndjson"),
|
|
116
|
+
mtimeMs: stats.mtimeMs,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (runtimeCandidates.length === 0) {
|
|
121
|
+
throw new Error("No swarm runtime artifacts found.");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
runtimeCandidates.sort((left, right) => right.mtimeMs - left.mtimeMs);
|
|
125
|
+
const latest = runtimeCandidates[0];
|
|
126
|
+
return {
|
|
127
|
+
outputRoot,
|
|
128
|
+
runId: latest.runId,
|
|
129
|
+
runtimeDirectory: latest.runtimeDirectory,
|
|
130
|
+
runtimeJsonPath: latest.runtimeJsonPath,
|
|
131
|
+
runtimeEventsPath: latest.runtimeEventsPath,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function loadSwarmDashboardSnapshot({
|
|
136
|
+
targetPath = ".",
|
|
137
|
+
outputDir = "",
|
|
138
|
+
runId = "",
|
|
139
|
+
env,
|
|
140
|
+
homeDir,
|
|
141
|
+
} = {}) {
|
|
142
|
+
const files = await resolveSwarmRuntimeFiles({
|
|
143
|
+
targetPath,
|
|
144
|
+
outputDir,
|
|
145
|
+
runId,
|
|
146
|
+
env,
|
|
147
|
+
homeDir,
|
|
148
|
+
});
|
|
149
|
+
const summary = JSON.parse(await fsp.readFile(files.runtimeJsonPath, "utf-8"));
|
|
150
|
+
const eventsRaw = await fsp.readFile(files.runtimeEventsPath, "utf-8");
|
|
151
|
+
const events = String(eventsRaw || "")
|
|
152
|
+
.split(/\r?\n/)
|
|
153
|
+
.map((line) => line.trim())
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.map((line) => JSON.parse(line));
|
|
156
|
+
|
|
157
|
+
const snapshot = {
|
|
158
|
+
runId: summary.runId,
|
|
159
|
+
planRunId: summary.planRunId,
|
|
160
|
+
scenario: summary.scenario,
|
|
161
|
+
engine: summary.engine,
|
|
162
|
+
execute: Boolean(summary.execute),
|
|
163
|
+
completed: Boolean(summary.completed),
|
|
164
|
+
stop: summary.stop || { stopClass: "NONE", reason: "", blocking: false },
|
|
165
|
+
usage: summary.usage || {},
|
|
166
|
+
eventCount: events.length,
|
|
167
|
+
runtimeJsonPath: files.runtimeJsonPath,
|
|
168
|
+
runtimeEventsPath: files.runtimeEventsPath,
|
|
169
|
+
runtimeDirectory: files.runtimeDirectory,
|
|
170
|
+
generatedAt: new Date().toISOString(),
|
|
171
|
+
agentRows: toAgentRows(summary, events),
|
|
172
|
+
recentEvents: events.slice(-10),
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
return snapshot;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function renderSwarmDashboard(snapshot = {}) {
|
|
179
|
+
const header = [
|
|
180
|
+
`Swarm run: ${snapshot.runId}`,
|
|
181
|
+
`Scenario: ${snapshot.scenario}`,
|
|
182
|
+
`Status: ${snapshot.completed ? "completed" : "running"}${snapshot.stop?.blocking ? ` (${snapshot.stop.stopClass})` : ""}`,
|
|
183
|
+
`Usage: tokens=${snapshot.usage.outputTokens || 0} tools=${snapshot.usage.toolCalls || 0} runtime_ms=${snapshot.usage.durationMs || 0} cost_usd=${snapshot.usage.costUsd || 0}`,
|
|
184
|
+
].join("\n");
|
|
185
|
+
|
|
186
|
+
const rows = (snapshot.agentRows || [])
|
|
187
|
+
.map(
|
|
188
|
+
(row) =>
|
|
189
|
+
`- ${row.agentId} status=${row.status} events=${row.eventCount} last=${row.lastEventType || "n/a"}`
|
|
190
|
+
)
|
|
191
|
+
.join("\n");
|
|
192
|
+
|
|
193
|
+
return `${header}\nAgents:\n${rows || "- none"}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function watchSwarmDashboard({
|
|
197
|
+
targetPath = ".",
|
|
198
|
+
outputDir = "",
|
|
199
|
+
runId = "",
|
|
200
|
+
pollSeconds = 2,
|
|
201
|
+
maxIdleSeconds = 20,
|
|
202
|
+
env,
|
|
203
|
+
homeDir,
|
|
204
|
+
onSnapshot,
|
|
205
|
+
} = {}) {
|
|
206
|
+
const pollMs = Math.max(250, Math.floor(Number(pollSeconds || 2) * 1000));
|
|
207
|
+
const maxIdleMs = Math.max(1000, Math.floor(Number(maxIdleSeconds || 20) * 1000));
|
|
208
|
+
let lastSignature = "";
|
|
209
|
+
let lastUpdate = Date.now();
|
|
210
|
+
const snapshots = [];
|
|
211
|
+
|
|
212
|
+
while (true) {
|
|
213
|
+
const snapshot = await loadSwarmDashboardSnapshot({
|
|
214
|
+
targetPath,
|
|
215
|
+
outputDir,
|
|
216
|
+
runId,
|
|
217
|
+
env,
|
|
218
|
+
homeDir,
|
|
219
|
+
});
|
|
220
|
+
const signature = `${snapshot.eventCount}:${snapshot.usage.toolCalls || 0}:${snapshot.completed}:${snapshot.stop?.stopClass || "NONE"}`;
|
|
221
|
+
if (signature !== lastSignature) {
|
|
222
|
+
lastSignature = signature;
|
|
223
|
+
lastUpdate = Date.now();
|
|
224
|
+
snapshots.push(snapshot);
|
|
225
|
+
if (onSnapshot) {
|
|
226
|
+
await onSnapshot(snapshot);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (snapshot.completed || snapshot.stop?.blocking) {
|
|
231
|
+
return {
|
|
232
|
+
finalSnapshot: snapshot,
|
|
233
|
+
snapshots,
|
|
234
|
+
stopReason: snapshot.completed ? "COMPLETED" : snapshot.stop?.stopClass || "STOPPED",
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (Date.now() - lastUpdate >= maxIdleMs) {
|
|
239
|
+
return {
|
|
240
|
+
finalSnapshot: snapshot,
|
|
241
|
+
snapshots,
|
|
242
|
+
stopReason: "IDLE_TIMEOUT",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
await sleep(pollMs);
|
|
246
|
+
}
|
|
247
|
+
}
|