diff --git a/electron/utils/channel-config.ts b/electron/utils/channel-config.ts index 4815a24bf..0682135f5 100644 --- a/electron/utils/channel-config.ts +++ b/electron/utils/channel-config.ts @@ -371,6 +371,17 @@ export async function saveChannelConfig( enabled: transformedConfig.enabled ?? true, }; + // Most OpenClaw channel plugins read the default account's credentials + // from the top level of `channels.` (e.g. channels.feishu.appId), + // not from `accounts.default`. Mirror them there so plugins can discover + // the credentials correctly. We use the final account entry (not + // transformedConfig) because `enabled` is only added at the account level. + if (resolvedAccountId === DEFAULT_ACCOUNT_ID) { + for (const [key, value] of Object.entries(accounts[resolvedAccountId])) { + channelSection[key] = value; + } + } + await writeOpenClawConfig(currentConfig); logger.info('Channel config saved', { channelType, diff --git a/electron/utils/openclaw-auth.ts b/electron/utils/openclaw-auth.ts index 0e90759cf..994c85f74 100644 --- a/electron/utils/openclaw-auth.ts +++ b/electron/utils/openclaw-auth.ts @@ -1016,24 +1016,31 @@ export async function sanitizeOpenClawConfig(): Promise { } } - // ── channels.feishu migration ────────────────────────────────── - // The official feishu plugin reads the default account's credentials from - // the top level of `channels.feishu` (appId, appSecret), but ClawX - // historically stored them only under `channels.feishu.accounts.default`. - // Mirror the default account credentials at the top level so the plugin - // can discover them. - const feishuSection = (config.channels as Record> | undefined)?.feishu; - if (feishuSection) { - const feishuAccounts = feishuSection.accounts as Record> | undefined; - const defaultAccount = feishuAccounts?.default; - if (defaultAccount?.appId && defaultAccount?.appSecret && !feishuSection.appId) { + // ── channels default-account migration ───────────────────────── + // Most OpenClaw channel plugins read the default account's credentials + // from the top level of `channels.` (e.g. channels.feishu.appId), + // but ClawX historically stored them only under `channels..accounts.default`. + // Mirror the default account credentials at the top level so plugins can + // discover them. + const channelsObj = config.channels as Record> | undefined; + if (channelsObj && typeof channelsObj === 'object') { + for (const [channelType, section] of Object.entries(channelsObj)) { + if (!section || typeof section !== 'object') continue; + const accounts = section.accounts as Record> | undefined; + const defaultAccount = accounts?.default; + if (!defaultAccount || typeof defaultAccount !== 'object') continue; + // Mirror each missing key from accounts.default to the top level + let mirrored = false; for (const [key, value] of Object.entries(defaultAccount)) { - if (key !== 'enabled' && !(key in feishuSection)) { - feishuSection[key] = value; + if (!(key in section)) { + section[key] = value; + mirrored = true; } } - modified = true; - console.log('[sanitize] Mirrored feishu default account credentials to top-level channels.feishu'); + if (mirrored) { + modified = true; + console.log(`[sanitize] Mirrored ${channelType} default account credentials to top-level channels.${channelType}`); + } } }