feat(ui): add operator UI server, stores, and insights

This commit is contained in:
2026-02-23 18:49:53 -05:00
parent 8100f4d1c6
commit cf386e1aaa
18 changed files with 3252 additions and 17 deletions

View File

@@ -0,0 +1,207 @@
import test from "node:test";
import assert from "node:assert/strict";
import { mkdir, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { resolve } from "node:path";
import { mkdtemp } from "node:fs/promises";
import { buildSessionGraphInsight, buildSessionSummaries } from "../src/ui/session-insights.js";
import { parseAgentManifest } from "../src/agents/manifest.js";
function createManifest() {
return parseAgentManifest({
schemaVersion: "1",
topologies: ["sequential", "retry-unrolled"],
personas: [
{
id: "planner",
displayName: "Planner",
systemPromptTemplate: "Plan",
toolClearance: {
allowlist: ["read_file"],
banlist: [],
},
},
],
relationships: [],
topologyConstraints: {
maxDepth: 3,
maxRetries: 2,
},
pipeline: {
entryNodeId: "n1",
nodes: [
{
id: "n1",
actorId: "a1",
personaId: "planner",
topology: { kind: "sequential" },
},
{
id: "n2",
actorId: "a2",
personaId: "planner",
topology: { kind: "retry-unrolled" },
},
],
edges: [
{
from: "n1",
to: "n2",
event: "validation_failed",
},
],
},
});
}
test("buildSessionGraphInsight maps attempts, edge visits, and sandbox payload", async () => {
const root = await mkdtemp(resolve(tmpdir(), "ai-ops-session-insights-"));
const stateRoot = resolve(root, "state");
const sessionId = "session-1";
const handoffDir = resolve(stateRoot, sessionId, "handoffs");
const runtimeLogPath = resolve(root, "runtime-events.ndjson");
await mkdir(handoffDir, { recursive: true });
await writeFile(
resolve(handoffDir, "n2.json"),
`${JSON.stringify({
nodeId: "n2",
fromNodeId: "n1",
payload: {},
createdAt: new Date().toISOString(),
})}\n`,
"utf8",
);
const lines = [
{
id: "1",
timestamp: "2026-01-01T00:00:00.000Z",
type: "session.started",
severity: "info",
message: "started",
sessionId,
},
{
id: "2",
timestamp: "2026-01-01T00:00:01.000Z",
type: "node.attempt.completed",
severity: "info",
message: "n1 success",
sessionId,
nodeId: "n1",
attempt: 1,
usage: { durationMs: 100, costUsd: 0.001 },
metadata: {
status: "success",
executionContext: { phase: "n1", allowedTools: ["read_file"] },
},
},
{
id: "3",
timestamp: "2026-01-01T00:00:02.000Z",
type: "node.attempt.completed",
severity: "warning",
message: "n2 validation",
sessionId,
nodeId: "n2",
attempt: 1,
usage: { durationMs: 140, costUsd: 0.002 },
metadata: {
status: "validation_fail",
retrySpawned: true,
subtasks: ["fix tests"],
executionContext: { phase: "n2", allowedTools: ["read_file"] },
},
},
{
id: "4",
timestamp: "2026-01-01T00:00:03.000Z",
type: "node.attempt.completed",
severity: "info",
message: "n2 success",
sessionId,
nodeId: "n2",
attempt: 2,
usage: { durationMs: 120, costUsd: 0.0025 },
metadata: {
status: "success",
executionContext: { phase: "n2", allowedTools: ["read_file"] },
},
},
{
id: "5",
timestamp: "2026-01-01T00:00:04.000Z",
type: "session.completed",
severity: "info",
message: "completed",
sessionId,
metadata: {
status: "success",
},
},
];
await writeFile(runtimeLogPath, `${lines.map((line) => JSON.stringify(line)).join("\n")}\n`, "utf8");
const manifest = createManifest();
const graph = await buildSessionGraphInsight({
stateRoot,
runtimeEventLogPath: runtimeLogPath,
sessionId,
manifest,
});
assert.equal(graph.status, "success");
assert.equal(graph.nodes.length, 2);
const node2 = graph.nodes.find((node) => node.nodeId === "n2");
assert.ok(node2);
assert.equal(node2.attemptCount, 2);
assert.equal(node2.subtaskCount, 1);
assert.equal(node2.sandboxPayload?.phase, "n2");
const edge = graph.edges.find((entry) => entry.from === "n1" && entry.to === "n2");
assert.ok(edge);
assert.equal(edge.visited, true);
assert.equal(edge.trigger, "event:validation_failed");
});
test("buildSessionSummaries reflects aborted failed session", async () => {
const root = await mkdtemp(resolve(tmpdir(), "ai-ops-session-insights-"));
const stateRoot = resolve(root, "state");
const sessionId = "session-abort";
const runtimeLogPath = resolve(root, "runtime-events.ndjson");
await mkdir(resolve(stateRoot, sessionId), { recursive: true });
const lines = [
{
id: "1",
timestamp: "2026-01-01T00:00:00.000Z",
type: "session.started",
severity: "info",
message: "started",
sessionId,
},
{
id: "2",
timestamp: "2026-01-01T00:00:01.000Z",
type: "session.failed",
severity: "critical",
message: "Pipeline aborted after hard failures.",
sessionId,
},
];
await writeFile(runtimeLogPath, `${lines.map((line) => JSON.stringify(line)).join("\n")}\n`, "utf8");
const sessions = await buildSessionSummaries({
stateRoot,
runtimeEventLogPath: runtimeLogPath,
});
assert.equal(sessions.length, 1);
assert.equal(sessions[0]?.status, "failure");
assert.equal(sessions[0]?.aborted, true);
});