/** * **动态 Agent 路由模块** * * 为每个用户/群组自动生成独立的 Agent ID,实现会话隔离。 * 参考: openclaw-plugin-wecom/dynamic-agent.js */ import type { OpenClawConfig } from "openclaw/plugin-sdk"; export interface DynamicAgentConfig { enabled: boolean; dmCreateAgent: boolean; groupEnabled: boolean; adminUsers: string[]; } /** * **getDynamicAgentConfig (读取动态 Agent 配置)** * * 从全局配置中读取动态 Agent 配置,提供默认值。 */ export function getDynamicAgentConfig(config: OpenClawConfig): DynamicAgentConfig { const dynamicAgents = (config as { channels?: { wecom?: { dynamicAgents?: Partial } } })?.channels?.wecom?.dynamicAgents; return { enabled: dynamicAgents?.enabled ?? false, dmCreateAgent: dynamicAgents?.dmCreateAgent ?? true, groupEnabled: dynamicAgents?.groupEnabled ?? true, adminUsers: dynamicAgents?.adminUsers ?? [], }; } function sanitizeDynamicIdPart(value: string): string { return String(value) .trim() .toLowerCase() .replace(/[^a-z0-9_-]/g, "_"); } /** * **generateAgentId (生成动态 Agent ID)** * * 根据账号 + 聊天类型 + 对端 ID 生成确定性的 Agent ID,避免多账号串会话。 * 格式: wecom-{accountId}-{type}-{sanitizedPeerId} */ export function generateAgentId(chatType: "dm" | "group", peerId: string, accountId?: string): string { const sanitizedPeer = sanitizeDynamicIdPart(peerId) || "unknown"; const sanitizedAccountId = sanitizeDynamicIdPart(accountId ?? "default") || "default"; return `wecom-${sanitizedAccountId}-${chatType}-${sanitizedPeer}`; } /** * **shouldUseDynamicAgent (检查是否使用动态 Agent)** * * 根据配置和发送者信息判断是否应使用动态 Agent。 * 管理员(adminUsers)始终绕过动态路由,使用主 Agent。 */ export function shouldUseDynamicAgent(params: { chatType: "dm" | "group"; senderId: string; config: OpenClawConfig; }): boolean { const { chatType, senderId, config } = params; const dynamicConfig = getDynamicAgentConfig(config); if (!dynamicConfig.enabled) { return false; } // 管理员绕过动态路由 const sender = String(senderId).trim().toLowerCase(); const isAdmin = dynamicConfig.adminUsers.some( (admin) => admin.trim().toLowerCase() === sender ); if (isAdmin) { return false; } if (chatType === "group") { return dynamicConfig.groupEnabled; } return dynamicConfig.dmCreateAgent; } /** * 内存中已确保的 Agent ID(避免重复写入) */ const ensuredDynamicAgentIds = new Set(); /** * 写入队列(避免并发冲突) */ let ensureDynamicAgentWriteQueue: Promise = Promise.resolve(); /** * 将 Agent ID 插入 agents.list(如果不存在) */ function upsertAgentIdOnlyEntry(cfg: Record, agentId: string): boolean { if (!cfg.agents || typeof cfg.agents !== "object") { cfg.agents = {}; } const agentsObj = cfg.agents as Record; const currentList: Array<{ id: string }> = Array.isArray(agentsObj.list) ? agentsObj.list as Array<{ id: string }> : []; const existingIds = new Set( currentList .map((entry) => entry?.id?.trim().toLowerCase()) .filter((id): id is string => Boolean(id)) ); let changed = false; const nextList = [...currentList]; // 首次创建时保留 main 作为默认 if (nextList.length === 0) { nextList.push({ id: "main" }); existingIds.add("main"); changed = true; } if (!existingIds.has(agentId.toLowerCase())) { nextList.push({ id: agentId }); changed = true; } if (changed) { agentsObj.list = nextList; } return changed; } /** * **ensureDynamicAgentListed (确保动态 Agent 已添加到 agents.list)** * * 将动态生成的 Agent ID 添加到 OpenClaw 配置中的 agents.list。 * 特性: * - 幂等:使用内存 Set 避免重复写入 * - 串行:使用 Promise 队列避免并发冲突 * - 异步:不阻塞消息处理流程 */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export async function ensureDynamicAgentListed(agentId: string, runtime: any): Promise { const normalizedId = String(agentId).trim().toLowerCase(); if (!normalizedId) return; if (ensuredDynamicAgentIds.has(normalizedId)) return; const configRuntime = runtime?.config; if (!configRuntime?.loadConfig || !configRuntime?.writeConfigFile) return; ensureDynamicAgentWriteQueue = ensureDynamicAgentWriteQueue .then(async () => { if (ensuredDynamicAgentIds.has(normalizedId)) return; const latestConfig = configRuntime.loadConfig!(); if (!latestConfig || typeof latestConfig !== "object") return; const changed = upsertAgentIdOnlyEntry(latestConfig as Record, normalizedId); if (changed) { await configRuntime.writeConfigFile!(latestConfig as unknown); } ensuredDynamicAgentIds.add(normalizedId); }) .catch((err) => { console.warn(`[wecom] 动态 Agent 添加失败: ${normalizedId}`, err); }); await ensureDynamicAgentWriteQueue; } /** * **resetEnsuredCache (重置已确保缓存)** * * 主要用于测试场景,重置内存中的缓存状态。 */ export function resetEnsuredCache(): void { ensuredDynamicAgentIds.clear(); }