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,94 @@
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";
import type { RuntimeEvent } from "../telemetry/runtime-events.js";
type RuntimeEventFilter = {
sessionId?: string;
types?: string[];
severities?: Array<RuntimeEvent["severity"]>;
limit?: number;
};
function safeParseLine(line: string): RuntimeEvent | undefined {
const trimmed = line.trim();
if (!trimmed) {
return undefined;
}
try {
const parsed = JSON.parse(trimmed) as unknown;
if (!parsed || typeof parsed !== "object") {
return undefined;
}
const record = parsed as Partial<RuntimeEvent>;
if (
typeof record.id !== "string" ||
typeof record.timestamp !== "string" ||
typeof record.type !== "string" ||
typeof record.severity !== "string" ||
typeof record.message !== "string"
) {
return undefined;
}
return record as RuntimeEvent;
} catch {
return undefined;
}
}
export async function readRuntimeEvents(logPath: string): Promise<RuntimeEvent[]> {
const absolutePath = resolve(logPath);
let content = "";
try {
content = await readFile(absolutePath, "utf8");
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
return [];
}
throw error;
}
const parsed: RuntimeEvent[] = [];
const lines = content.split(/\r?\n/);
for (const line of lines) {
const event = safeParseLine(line);
if (event) {
parsed.push(event);
}
}
parsed.sort((left, right) => left.timestamp.localeCompare(right.timestamp));
return parsed;
}
export function filterRuntimeEvents(
events: readonly RuntimeEvent[],
filter: RuntimeEventFilter,
): RuntimeEvent[] {
const filtered: RuntimeEvent[] = [];
const types = filter.types ? new Set(filter.types) : undefined;
const severities = filter.severities ? new Set(filter.severities) : undefined;
for (const event of events) {
if (filter.sessionId && event.sessionId !== filter.sessionId) {
continue;
}
if (types && !types.has(event.type)) {
continue;
}
if (severities && !severities.has(event.severity)) {
continue;
}
filtered.push(event);
}
if (!filter.limit || filter.limit < 1 || filtered.length <= filter.limit) {
return filtered;
}
return filtered.slice(-filter.limit);
}