agent-monitor.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import fs from "node:fs/promises";
  2. import path from "node:path";
  3. import { demoAgents } from "@/data/demo-agents";
  4. import { AgentFeed, AgentProfile, AgentStatus } from "@/types/agent";
  5. const defaultFeedPath = path.join(process.cwd(), "storage", "agents", "openclaw-agents.json");
  6. function isAgentStatus(value: unknown): value is AgentStatus {
  7. return value === "working" || value === "idle" || value === "warning" || value === "offline";
  8. }
  9. function normalizeAgent(agent: Record<string, unknown>): AgentProfile | null {
  10. if (typeof agent.id !== "string" || typeof agent.name !== "string" || typeof agent.role !== "string") {
  11. return null;
  12. }
  13. const status = isAgentStatus(agent.status) ? agent.status : "offline";
  14. const statusLabelMap: Record<AgentStatus, string> = {
  15. working: "工作中",
  16. idle: "待命",
  17. warning: "待确认",
  18. offline: "离线"
  19. };
  20. return {
  21. id: agent.id,
  22. name: agent.name,
  23. role: agent.role,
  24. avatarKind: (agent.avatarKind as AgentProfile["avatarKind"]) ?? "male-dispatcher",
  25. status,
  26. statusLabel: typeof agent.statusLabel === "string" ? agent.statusLabel : statusLabelMap[status],
  27. host: typeof agent.host === "string" ? agent.host : "unknown-host",
  28. owner: typeof agent.owner === "string" ? agent.owner : "OpenClaw",
  29. currentTask: typeof agent.currentTask === "string" ? agent.currentTask : "未上报当前任务",
  30. taskId: typeof agent.taskId === "string" ? agent.taskId : "unknown",
  31. taskStage: typeof agent.taskStage === "string" ? agent.taskStage : "unknown",
  32. queueDepth: typeof agent.queueDepth === "number" ? agent.queueDepth : 0,
  33. todayCompleted: typeof agent.todayCompleted === "number" ? agent.todayCompleted : 0,
  34. uptime: typeof agent.uptime === "string" ? agent.uptime : "未知",
  35. lastHeartbeat: typeof agent.lastHeartbeat === "string" ? agent.lastHeartbeat : "未知",
  36. updatedAt: typeof agent.updatedAt === "string" ? agent.updatedAt : "刚刚",
  37. lastOutput: typeof agent.lastOutput === "string" ? agent.lastOutput : "暂无最近输出。",
  38. lastError: typeof agent.lastError === "string" ? agent.lastError : undefined,
  39. tags: Array.isArray(agent.tags) ? agent.tags.filter((tag): tag is string => typeof tag === "string") : []
  40. };
  41. }
  42. async function loadFileFeed(feedPath: string): Promise<AgentFeed | null> {
  43. try {
  44. const raw = await fs.readFile(feedPath, "utf8");
  45. const parsed = JSON.parse(raw) as {
  46. fetchedAt?: string;
  47. agents?: Array<Record<string, unknown>>;
  48. };
  49. if (!Array.isArray(parsed.agents)) {
  50. return null;
  51. }
  52. const agents = parsed.agents.map(normalizeAgent).filter((agent): agent is AgentProfile => agent !== null);
  53. return {
  54. source: "file",
  55. sourceLabel: path.basename(feedPath),
  56. fetchedAt: typeof parsed.fetchedAt === "string" ? parsed.fetchedAt : new Date().toISOString(),
  57. agents
  58. };
  59. } catch {
  60. return null;
  61. }
  62. }
  63. export async function loadAgentFeed(status?: "all" | AgentStatus): Promise<AgentFeed> {
  64. const feedPath = process.env.OPENCLAW_AGENT_FEED_PATH ?? defaultFeedPath;
  65. const fileFeed = await loadFileFeed(feedPath);
  66. const fallbackFeed: AgentFeed = {
  67. source: "demo",
  68. sourceLabel: "demo-agents.ts",
  69. fetchedAt: new Date().toISOString(),
  70. agents: demoAgents
  71. };
  72. const chosenFeed = fileFeed ?? fallbackFeed;
  73. const filteredAgents =
  74. status && status !== "all" ? chosenFeed.agents.filter((agent) => agent.status === status) : chosenFeed.agents;
  75. return {
  76. ...chosenFeed,
  77. agents: filteredAgents
  78. };
  79. }