225 lines
7.1 KiB
TypeScript
225 lines
7.1 KiB
TypeScript
import { ipcMain } from "electron";
|
|
import type { HandlerContext } from "@shared/types/ipc";
|
|
import { FEATURES } from "@shared/featureFlags";
|
|
import { agentService } from "../services/engines/unifiedAgent";
|
|
import { mcpProxyManager } from "../services/packages/mcp";
|
|
import { getComputerServerStatus } from "../services/computerServer";
|
|
import { getGuiAgentServerStatus } from "../services/packages/guiAgentServer";
|
|
import { getWindowsMcpStatus } from "../services/packages/windowsMcp";
|
|
import { isWindows } from "../services/system/shellEnv";
|
|
import {
|
|
recordDigitalEmployeeSnapshot,
|
|
recordDigitalEmployeeGovernanceCommand,
|
|
readDigitalEmployeeRuntimeRecords,
|
|
readDigitalEmployeeState,
|
|
readDigitalEmployeeUiState,
|
|
saveDigitalEmployeeUiState,
|
|
type DigitalEmployeeManagedService,
|
|
type DigitalEmployeeGovernanceCommandRecord,
|
|
type DigitalEmployeeServiceStatus,
|
|
type DigitalEmployeeSnapshot,
|
|
type DigitalEmployeeUiStateUpdate,
|
|
} from "../services/digitalEmployee/stateService";
|
|
import {
|
|
flushDigitalEmployeeSyncOutbox,
|
|
getDigitalEmployeeSyncStatus,
|
|
} from "../services/digitalEmployee/syncService";
|
|
import { listDigitalEmployeePlanTemplates } from "../services/digitalEmployee/planTemplateService";
|
|
import { listDigitalEmployeeSkillCapabilities } from "../services/digitalEmployee/skillCatalogService";
|
|
|
|
export function registerDigitalEmployeeHandlers(ctx: HandlerContext): void {
|
|
ipcMain.handle("digitalEmployee:getSnapshot", async () => {
|
|
const snapshot = buildSnapshot(ctx);
|
|
const state = recordDigitalEmployeeSnapshot(snapshot);
|
|
return {
|
|
...snapshot,
|
|
state: {
|
|
updatedAt: state.updatedAt,
|
|
events: state.events,
|
|
},
|
|
};
|
|
});
|
|
|
|
ipcMain.handle("digitalEmployee:getState", async () => {
|
|
return readDigitalEmployeeState();
|
|
});
|
|
|
|
ipcMain.handle("digitalEmployee:getRuntimeRecords", async () => {
|
|
return readDigitalEmployeeRuntimeRecords();
|
|
});
|
|
|
|
ipcMain.handle("digitalEmployee:getUiState", async () => {
|
|
return readDigitalEmployeeUiState();
|
|
});
|
|
|
|
ipcMain.handle("digitalEmployee:getSkillCapabilities", async () => {
|
|
return listDigitalEmployeeSkillCapabilities();
|
|
});
|
|
|
|
ipcMain.handle("digitalEmployee:getPlanTemplates", async () => {
|
|
return listDigitalEmployeePlanTemplates();
|
|
});
|
|
|
|
ipcMain.handle(
|
|
"digitalEmployee:saveUiState",
|
|
async (_, update: DigitalEmployeeUiStateUpdate) => {
|
|
return saveDigitalEmployeeUiState(update);
|
|
},
|
|
);
|
|
|
|
ipcMain.handle("digitalEmployee:getSyncStatus", async () => {
|
|
return getDigitalEmployeeSyncStatus();
|
|
});
|
|
|
|
ipcMain.handle("digitalEmployee:flushSync", async () => {
|
|
return flushDigitalEmployeeSyncOutbox({ force: true });
|
|
});
|
|
|
|
ipcMain.handle(
|
|
"digitalEmployee:recordGovernanceCommand",
|
|
async (_, command: DigitalEmployeeGovernanceCommandRecord) => {
|
|
return recordDigitalEmployeeGovernanceCommand(command);
|
|
},
|
|
);
|
|
}
|
|
|
|
function buildSnapshot(ctx: HandlerContext): DigitalEmployeeSnapshot {
|
|
const sessions = agentService.listAllSessionsDetailed();
|
|
const generatedAt = new Date().toISOString();
|
|
const services = {
|
|
agent: {
|
|
running: agentService.isReady,
|
|
engineType: agentService.getEngineType(),
|
|
},
|
|
mcp: normalizeServiceStatus(mcpProxyManager.getStatus()),
|
|
computerServer: normalizeServiceStatus(getComputerServerStatus()),
|
|
lanproxy: normalizeServiceStatus(ctx.lanproxy.status()),
|
|
fileServer: normalizeServiceStatus(ctx.fileServer.status()),
|
|
guiServer: normalizeServiceStatus(getGuiStatus()),
|
|
} satisfies Record<string, DigitalEmployeeServiceStatus | null>;
|
|
return {
|
|
generatedAt,
|
|
services,
|
|
managedServices: buildManagedServices(services, generatedAt),
|
|
sessions: {
|
|
total: sessions.length,
|
|
active: sessions.filter((session) =>
|
|
session.status === "active" || session.status === "pending",
|
|
).length,
|
|
items: sessions.slice(0, 10),
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildManagedServices(
|
|
services: Record<string, DigitalEmployeeServiceStatus | null>,
|
|
generatedAt: string,
|
|
): DigitalEmployeeManagedService[] {
|
|
return MANAGED_SERVICE_DEFINITIONS.map((definition) => {
|
|
const status = services[definition.serviceId] ?? { running: false };
|
|
const error = status.error ?? null;
|
|
const running = status.running === true;
|
|
const state = error ? "degraded" : running ? "running" : "stopped";
|
|
return {
|
|
serviceId: definition.serviceId,
|
|
name: definition.name,
|
|
kind: definition.kind,
|
|
status: state,
|
|
statusLabel: state === "running" ? "运行中" : state === "degraded" ? "异常" : "未运行",
|
|
running,
|
|
error,
|
|
requiresHumanAction: Boolean(error || (!running && definition.required)),
|
|
dependentTasks: definition.dependentTasks,
|
|
restartCount: 0,
|
|
lastSeenAt: generatedAt,
|
|
instance: {
|
|
pid: status.pid,
|
|
port: status.port,
|
|
engineType: status.engineType,
|
|
serverCount: status.serverCount,
|
|
serverNames: status.serverNames,
|
|
},
|
|
lease: {
|
|
state: state === "running" ? "active" : state,
|
|
updatedAt: generatedAt,
|
|
},
|
|
};
|
|
});
|
|
}
|
|
|
|
const MANAGED_SERVICE_DEFINITIONS: Array<{
|
|
serviceId: string;
|
|
name: string;
|
|
kind: DigitalEmployeeManagedService["kind"];
|
|
required: boolean;
|
|
dependentTasks: string[];
|
|
}> = [
|
|
{
|
|
serviceId: "agent",
|
|
name: "Agent",
|
|
kind: "agent",
|
|
required: true,
|
|
dependentTasks: ["ACP 会话执行", "Plan Agent", "Task Agent"],
|
|
},
|
|
{
|
|
serviceId: "mcp",
|
|
name: "MCP Proxy",
|
|
kind: "mcp",
|
|
required: true,
|
|
dependentTasks: ["Skill Catalog", "MCP 工具调用"],
|
|
},
|
|
{
|
|
serviceId: "computerServer",
|
|
name: "Computer Server",
|
|
kind: "desktop",
|
|
required: true,
|
|
dependentTasks: ["/computer/chat", "远程任务接入"],
|
|
},
|
|
{
|
|
serviceId: "fileServer",
|
|
name: "File Server",
|
|
kind: "file",
|
|
required: false,
|
|
dependentTasks: ["Artifact 上传", "文件交付"],
|
|
},
|
|
{
|
|
serviceId: "lanproxy",
|
|
name: "Lanproxy",
|
|
kind: "network",
|
|
required: false,
|
|
dependentTasks: ["远程访问", "管理端回连"],
|
|
},
|
|
{
|
|
serviceId: "guiServer",
|
|
name: "GUI Agent Server",
|
|
kind: "gui",
|
|
required: false,
|
|
dependentTasks: ["GUI 自动化", "桌面操作"],
|
|
},
|
|
];
|
|
|
|
function normalizeServiceStatus(
|
|
value: object | null | undefined,
|
|
): DigitalEmployeeServiceStatus {
|
|
if (!value) return { running: false };
|
|
const record = value as Record<string, unknown>;
|
|
return {
|
|
running: record.running === true,
|
|
error: typeof record.error === "string" ? record.error : undefined,
|
|
engineType: typeof record.engineType === "string" ? record.engineType : undefined,
|
|
port: typeof record.port === "number" ? record.port : undefined,
|
|
serverCount: typeof record.serverCount === "number" ? record.serverCount : undefined,
|
|
serverNames: Array.isArray(record.serverNames)
|
|
? record.serverNames.filter((item): item is string => typeof item === "string")
|
|
: undefined,
|
|
pid: typeof record.pid === "number" ? record.pid : undefined,
|
|
};
|
|
}
|
|
|
|
function getGuiStatus(): object {
|
|
if (!FEATURES.ENABLE_GUI_AGENT_SERVER) {
|
|
return { running: false, error: "GUI Agent Server is not available" };
|
|
}
|
|
return isWindows() ? getWindowsMcpStatus() : getGuiAgentServerStatus();
|
|
}
|