feat(client): persist digital employee snapshots

This commit is contained in:
baiyanyun
2026-06-05 16:27:15 +08:00
parent 15a3285ed5
commit 40151c3902
5 changed files with 251 additions and 63 deletions

View File

@@ -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(";");
}