/** * 配置处理模块 * @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"; interface PITChannelConfig extends PITBotAccountConfig { accounts?: Record; } /** * 默认账户 ID */ export const DEFAULT_ACCOUNT_ID = "default"; /** * 获取 PIT Bot 账户 ID 列表 * @returns 账户 ID 列表 */ export function listPITBotAccountIds(): string[] { return [DEFAULT_ACCOUNT_ID]; } /** * 解析 PIT Bot 账户 * @param cfg OpenClaw 配置 * @param accountId 账户 ID * @returns 解析后的账户配置 * @throws 如果配置无效 */ export function resolvePITBotAccount( cfg: OpenClawConfig, accountId: string ): ResolvedPITBotAccount { const log = createLogger(MODULE); const pit = cfg.channels?.["zhidui-channel"] as PITChannelConfig | undefined; // 如果没有配置,返回默认值 if (!pit) { log.warn(`No zhidui-channel configuration found, using defaults`); return { accountId, name: undefined, enabled: false, routerUrl: "", authToken: "", secretSource: "none", config: { enabled: false, reconnectInterval: 5000, heartbeatInterval: 30000, heartbeatTimeout: 10000, ackTimeout: 30000, maxQueueSize: 100, }, }; } // 获取全局默认配置 const defaults: PITBotAccountConfig = { enabled: pit.enabled ?? true, routerUrl: pit.routerUrl, authToken: pit.authToken, name: pit.name, reconnectInterval: pit.reconnectInterval ?? 5000, heartbeatInterval: pit.heartbeatInterval ?? 30000, heartbeatTimeout: pit.heartbeatTimeout ?? 10000, ackTimeout: pit.ackTimeout ?? 30000, maxQueueSize: pit.maxQueueSize ?? 100, }; // 获取特定账户配置 let accountConfig: PITBotAccountConfig = {}; if (pit.accounts && typeof pit.accounts === "object") { const specificConfig = pit.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") { log.warn(`No routerUrl configured for account ${accountId}`); } return { accountId, name: mergedConfig.name, enabled: mergedConfig.enabled ?? true, routerUrl: routerUrl ?? "", authToken: authToken ?? "", secretSource, config: mergedConfig, }; } /** * 应用 PIT Bot 账户配置 * @param cfg OpenClaw 配置 * @param accountId 账户 ID * @param config 新配置 */ export function applyPITBotAccountConfig( cfg: OpenClawConfig, accountId: string, config: Partial ): void { // No-op for now - configuration is managed by OpenClaw console.log(`[zhidui-channel] applyAccountConfig called for ${accountId}:`, 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 | undefined): { valid: boolean; errors: string[] } { const errors: string[] = []; if (!config) { errors.push("Configuration is required"); return { valid: false, errors }; } 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; } }