feat(client): persist digital employee snapshots
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { readSetting, writeSetting } from "../../db";
|
||||
|
||||
const STATE_KEY = "digital_employee_state_v1";
|
||||
const MAX_EVENTS = 200;
|
||||
|
||||
export interface DigitalEmployeeServiceStatus {
|
||||
running?: boolean;
|
||||
error?: string;
|
||||
engineType?: string | null;
|
||||
port?: number;
|
||||
serverCount?: number;
|
||||
serverNames?: string[];
|
||||
pid?: number;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeSessionSummary {
|
||||
total: number;
|
||||
active: number;
|
||||
items: Array<{
|
||||
id: string;
|
||||
title?: string;
|
||||
status?: string;
|
||||
engineType?: string;
|
||||
projectId?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeSnapshot {
|
||||
generatedAt: string;
|
||||
services: Record<string, DigitalEmployeeServiceStatus | null>;
|
||||
sessions: DigitalEmployeeSessionSummary;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeStateEvent {
|
||||
event_id: string;
|
||||
kind: string;
|
||||
occurred_at: string;
|
||||
message: string;
|
||||
plan_id: string | null;
|
||||
task_id: string | null;
|
||||
}
|
||||
|
||||
export interface DigitalEmployeeState {
|
||||
version: 1;
|
||||
updatedAt: string | null;
|
||||
lastSnapshot: DigitalEmployeeSnapshot | null;
|
||||
events: DigitalEmployeeStateEvent[];
|
||||
}
|
||||
|
||||
function defaultState(): DigitalEmployeeState {
|
||||
return {
|
||||
version: 1,
|
||||
updatedAt: null,
|
||||
lastSnapshot: null,
|
||||
events: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function readDigitalEmployeeState(): DigitalEmployeeState {
|
||||
const state = readSetting(STATE_KEY);
|
||||
if (!state || typeof state !== "object") {
|
||||
return defaultState();
|
||||
}
|
||||
return {
|
||||
...defaultState(),
|
||||
...(state as Partial<DigitalEmployeeState>),
|
||||
version: 1,
|
||||
};
|
||||
}
|
||||
|
||||
export function writeDigitalEmployeeState(state: DigitalEmployeeState): void {
|
||||
writeSetting(STATE_KEY, state);
|
||||
}
|
||||
|
||||
export function recordDigitalEmployeeSnapshot(
|
||||
snapshot: DigitalEmployeeSnapshot,
|
||||
): DigitalEmployeeState {
|
||||
const previous = readDigitalEmployeeState();
|
||||
const events = [...previous.events];
|
||||
const event = eventFromSnapshot(snapshot, previous.lastSnapshot);
|
||||
if (event) {
|
||||
events.unshift(event);
|
||||
}
|
||||
|
||||
const next: DigitalEmployeeState = {
|
||||
version: 1,
|
||||
updatedAt: snapshot.generatedAt,
|
||||
lastSnapshot: snapshot,
|
||||
events: events.slice(0, MAX_EVENTS),
|
||||
};
|
||||
writeDigitalEmployeeState(next);
|
||||
return next;
|
||||
}
|
||||
|
||||
function eventFromSnapshot(
|
||||
snapshot: DigitalEmployeeSnapshot,
|
||||
previous: DigitalEmployeeSnapshot | null,
|
||||
): DigitalEmployeeStateEvent | null {
|
||||
const currentSummary = summarizeSnapshot(snapshot);
|
||||
const previousSummary = previous ? summarizeSnapshot(previous) : "";
|
||||
if (currentSummary === previousSummary) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
event_id: `qimingclaw-snapshot-${Date.now()}`,
|
||||
kind: "service_snapshot",
|
||||
occurred_at: snapshot.generatedAt,
|
||||
message: currentSummary,
|
||||
plan_id: "qimingclaw-client-readiness",
|
||||
task_id: "task-agent-status",
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeSnapshot(snapshot: DigitalEmployeeSnapshot): string {
|
||||
const serviceNames = [
|
||||
["agent", "Agent"],
|
||||
["mcp", "MCP"],
|
||||
["computerServer", "Computer Server"],
|
||||
["lanproxy", "Lanproxy"],
|
||||
["fileServer", "File Server"],
|
||||
] as const;
|
||||
const services = serviceNames.map(([key, label]) => {
|
||||
const status = snapshot.services[key];
|
||||
return `${label}:${status?.running ? "running" : "stopped"}`;
|
||||
});
|
||||
services.push(`sessions:${snapshot.sessions.active}/${snapshot.sessions.total}`);
|
||||
return services.join(";");
|
||||
}
|
||||
Reference in New Issue
Block a user