import fs from "node:fs/promises"; import path from "node:path"; import { demoAgents } from "@/data/demo-agents"; import { AgentFeed, AgentProfile, AgentStatus } from "@/types/agent"; const defaultFeedPath = path.join(process.cwd(), "storage", "agents", "openclaw-agents.json"); function isAgentStatus(value: unknown): value is AgentStatus { return value === "working" || value === "idle" || value === "warning" || value === "offline"; } function normalizeAgent(agent: Record): AgentProfile | null { if (typeof agent.id !== "string" || typeof agent.name !== "string" || typeof agent.role !== "string") { return null; } const status = isAgentStatus(agent.status) ? agent.status : "offline"; const statusLabelMap: Record = { working: "工作中", idle: "待命", warning: "待确认", offline: "离线" }; return { id: agent.id, name: agent.name, role: agent.role, avatarKind: (agent.avatarKind as AgentProfile["avatarKind"]) ?? "male-dispatcher", status, statusLabel: typeof agent.statusLabel === "string" ? agent.statusLabel : statusLabelMap[status], host: typeof agent.host === "string" ? agent.host : "unknown-host", owner: typeof agent.owner === "string" ? agent.owner : "OpenClaw", currentTask: typeof agent.currentTask === "string" ? agent.currentTask : "未上报当前任务", taskId: typeof agent.taskId === "string" ? agent.taskId : "unknown", taskStage: typeof agent.taskStage === "string" ? agent.taskStage : "unknown", queueDepth: typeof agent.queueDepth === "number" ? agent.queueDepth : 0, todayCompleted: typeof agent.todayCompleted === "number" ? agent.todayCompleted : 0, uptime: typeof agent.uptime === "string" ? agent.uptime : "未知", lastHeartbeat: typeof agent.lastHeartbeat === "string" ? agent.lastHeartbeat : "未知", updatedAt: typeof agent.updatedAt === "string" ? agent.updatedAt : "刚刚", lastOutput: typeof agent.lastOutput === "string" ? agent.lastOutput : "暂无最近输出。", lastError: typeof agent.lastError === "string" ? agent.lastError : undefined, tags: Array.isArray(agent.tags) ? agent.tags.filter((tag): tag is string => typeof tag === "string") : [] }; } async function loadFileFeed(feedPath: string): Promise { try { const raw = await fs.readFile(feedPath, "utf8"); const parsed = JSON.parse(raw) as { fetchedAt?: string; agents?: Array>; }; if (!Array.isArray(parsed.agents)) { return null; } const agents = parsed.agents.map(normalizeAgent).filter((agent): agent is AgentProfile => agent !== null); return { source: "file", sourceLabel: path.basename(feedPath), fetchedAt: typeof parsed.fetchedAt === "string" ? parsed.fetchedAt : new Date().toISOString(), agents }; } catch { return null; } } export async function loadAgentFeed(status?: "all" | AgentStatus): Promise { const feedPath = process.env.OPENCLAW_AGENT_FEED_PATH ?? defaultFeedPath; const fileFeed = await loadFileFeed(feedPath); const fallbackFeed: AgentFeed = { source: "demo", sourceLabel: "demo-agents.ts", fetchedAt: new Date().toISOString(), agents: demoAgents }; const chosenFeed = fileFeed ?? fallbackFeed; const filteredAgents = status && status !== "all" ? chosenFeed.agents.filter((agent) => agent.status === status) : chosenFeed.agents; return { ...chosenFeed, agents: filteredAgents }; }