/** * 配置处理模块 * @module config */ import type { PITBotAccountConfig, ResolvedPITBotAccount } from "./types.js"; import type { OpenClawConfig } from "openclaw/plugin-sdk"; import { createLogger } from "./utils/logger.js"; const MODULE = "config"; /** * 默认账户 ID */ export const DEFAULT_ACCOUNT_ID = "default"; /** * 获取 PIT Bot 账户 ID 列表 * @param cfg OpenClaw 配置 * @returns 账户 ID 列表 */ export function listPITBotAccountIds(cfg: OpenClawConfig): string[] { const accounts = cfg.getSection<{ accounts?: Record }>("channels.pit-bot"); const ids: string[] = []; if (accounts && typeof accounts.accounts === "object") { for (const [id, account] of Object.entries(accounts.accounts)) { if (account && typeof account === "object" && !Array.isArray(account)) { ids.push(id); } } } // 如果没有配置账户,返回默认账户 if (ids.length === 0) { ids.push(DEFAULT_ACCOUNT_ID); } return ids; } /** * 解析 PIT Bot 账户 * @param cfg OpenClaw 配置 * @param accountId 账户 ID * @returns 解析后的账户配置 * @throws 如果配置无效 */ export function resolvePITBotAccount( cfg: OpenClawConfig, accountId: string ): ResolvedPITBotAccount { const log = createLogger(MODULE); const section = cfg.getSection<{ enabled?: boolean; routerUrl?: string; authToken?: string; name?: string; reconnectInterval?: number; heartbeatInterval?: number; heartbeatTimeout?: number; ackTimeout?: number; maxQueueSize?: number; accounts?: Record; }>("channels.pit-bot"); if (!section) { throw new Error(`[pit-bot] Configuration section 'channels.pit-bot' not found`); } // 获取全局默认配置 const defaults: PITBotAccountConfig = { enabled: section.enabled ?? true, routerUrl: section.routerUrl, authToken: section.authToken, name: section.name, reconnectInterval: section.reconnectInterval ?? 5000, heartbeatInterval: section.heartbeatInterval ?? 30000, heartbeatTimeout: section.heartbeatTimeout ?? 10000, ackTimeout: section.ackTimeout ?? 30000, maxQueueSize: section.maxQueueSize ?? 100, }; // 获取特定账户配置 let accountConfig: PITBotAccountConfig = {}; if (section.accounts && typeof section.accounts === "object") { const specificConfig = section.accounts[accountId]; if (specificConfig && typeof specificConfig === "object") { accountConfig = specificConfig; } } // 合并配置(账户配置覆盖默认配置) const mergedConfig: PITBotAccountConfig = { ...defaults, ...accountConfig, }; // 解析认证 Token const { authToken, secretSource } = resolveAuthToken(mergedConfig.authToken, accountId, log); // 验证必需字段 const routerUrl = mergedConfig.routerUrl; if (!routerUrl || typeof routerUrl !== "string") { throw new Error( `[pit-bot:${accountId}] Missing required config: 'routerUrl'. ` + `Please set channels.pit-bot.routerUrl or channels.pit-bot.accounts.${accountId}.routerUrl` ); } if (!authToken) { log.warn(`No auth token configured for account ${accountId}. Connection may fail.`); } return { accountId, name: mergedConfig.name, enabled: mergedConfig.enabled ?? true, routerUrl, authToken: authToken ?? "", secretSource, config: mergedConfig, }; } /** * 应用 PIT Bot 账户配置 * @param cfg OpenClaw 配置 * @param accountId 账户 ID * @param config 新配置 */ export function applyPITBotAccountConfig( cfg: OpenClawConfig, accountId: string, config: PITBotAccountConfig ): void { const key = `channels.pit-bot.accounts.${accountId}`; cfg.set(key, config); } /** * 解析认证 Token * 支持环境变量引用和环境变量名 */ function resolveAuthToken( configToken: string | undefined, accountId: string, log: { warn: (msg: string) => void } ): { authToken: string | null; secretSource: "config" | "env" | "none" } { // 1. 如果配置中直接设置了 Token if (configToken && typeof configToken === "string") { // 检查是否是环境变量引用 ${VAR_NAME} const envMatch = configToken.match(/^\$\{(.+)\}$/); if (envMatch) { const envVar = envMatch[1]; const token = process.env[envVar]; if (!token) { log.warn(`Environment variable ${envVar} is not set for account ${accountId}`); return { authToken: null, secretSource: "env" }; } return { authToken: token, secretSource: "env" }; } return { authToken: configToken, secretSource: "config" }; } // 2. 尝试从标准环境变量名获取 const envVarName = accountId === DEFAULT_ACCOUNT_ID ? "PIT_ROUTER_TOKEN" : `PIT_ROUTER_TOKEN_${accountId.toUpperCase()}`; const envToken = process.env[envVarName]; if (envToken) { return { authToken: envToken, secretSource: "env" }; } // 3. 无 Token return { authToken: null, secretSource: "none" }; } /** * 验证配置 */ export function validateConfig(config: PITBotAccountConfig): { valid: boolean; errors: string[] } { const errors: string[] = []; if (!config.routerUrl) { errors.push("routerUrl is required"); } else if (!isValidUrl(config.routerUrl)) { errors.push(`routerUrl is invalid: ${config.routerUrl}`); } if (config.reconnectInterval !== undefined && config.reconnectInterval < 1000) { errors.push("reconnectInterval must be at least 1000ms"); } if (config.heartbeatInterval !== undefined && config.heartbeatInterval < 1000) { errors.push("heartbeatInterval must be at least 1000ms"); } return { valid: errors.length === 0, errors, }; } /** * 验证 URL */ function isValidUrl(url: string): boolean { try { new URL(url); return true; } catch { return false; } }