diff --git a/src/commands/doctor/shared/legacy-config-migrate.test.ts b/src/commands/doctor/shared/legacy-config-migrate.test.ts index 09cf1881230..fe2cca6a891 100644 --- a/src/commands/doctor/shared/legacy-config-migrate.test.ts +++ b/src/commands/doctor/shared/legacy-config-migrate.test.ts @@ -333,6 +333,57 @@ describe("legacy thread binding spawn migrate", () => { }); }); +describe("legacy Feishu account bot name migrate", () => { + it("moves legacy account botName to name", () => { + const res = migrateLegacyConfigForTest({ + channels: { + feishu: { + accounts: { + main: { + appId: "cli_xxx", + appSecret: "redacted", + botName: "Legacy Feishu Bot", + domain: "feishu", + }, + }, + }, + }, + }); + + expect(res.config?.channels?.feishu?.accounts?.main).toEqual({ + appId: "cli_xxx", + appSecret: "redacted", + name: "Legacy Feishu Bot", + domain: "feishu", + }); + expect(res.changes).toStrictEqual([ + "Moved channels.feishu.accounts.main.botName → channels.feishu.accounts.main.name.", + ]); + }); + + it("removes legacy account botName when name is already set", () => { + const res = migrateLegacyConfigForTest({ + channels: { + feishu: { + accounts: { + main: { + name: "Current Feishu Bot", + botName: "Legacy Feishu Bot", + }, + }, + }, + }, + }); + + expect(res.config?.channels?.feishu?.accounts?.main).toEqual({ + name: "Current Feishu Bot", + }); + expect(res.changes).toStrictEqual([ + "Removed channels.feishu.accounts.main.botName (channels.feishu.accounts.main.name already set).", + ]); + }); +}); + describe("legacy message queue mode migrate", () => { it("moves retired queue steering modes to followup mode", () => { const res = migrateLegacyConfigForTest({ diff --git a/src/commands/doctor/shared/legacy-config-migrations.channels.ts b/src/commands/doctor/shared/legacy-config-migrations.channels.ts index 2eee9cd6e71..f924eb882ed 100644 --- a/src/commands/doctor/shared/legacy-config-migrations.channels.ts +++ b/src/commands/doctor/shared/legacy-config-migrations.channels.ts @@ -200,6 +200,48 @@ function migrateTelegramRequireMention(raw: Record, changes: st raw.channels = channels; } +function hasLegacyFeishuAccountBotName(value: unknown): boolean { + const accounts = getRecord(value); + if (!accounts) { + return false; + } + return Object.values(accounts).some((entry) => { + const account = getRecord(entry); + return Boolean(account && hasOwnKey(account, "botName")); + }); +} + +function migrateFeishuAccountBotName(raw: Record, changes: string[]): void { + const channels = getRecord(raw.channels); + const feishu = getRecord(channels?.feishu); + const accounts = getRecord(feishu?.accounts); + if (!channels || !feishu || !accounts) { + return; + } + + for (const [accountId, accountRaw] of Object.entries(accounts)) { + const account = getRecord(accountRaw); + if (!account || !hasOwnKey(account, "botName")) { + continue; + } + + const legacyPath = `channels.feishu.accounts.${accountId}.botName`; + const currentPath = `channels.feishu.accounts.${accountId}.name`; + if (account.name === undefined) { + account.name = account.botName; + changes.push(`Moved ${legacyPath} → ${currentPath}.`); + } else { + changes.push(`Removed ${legacyPath} (${currentPath} already set).`); + } + delete account.botName; + accounts[accountId] = account; + } + + feishu.accounts = accounts; + channels.feishu = feishu; + raw.channels = channels; +} + function hasLegacyThreadBindingTtl(value: unknown): boolean { const threadBindings = getRecord(value); return Boolean(threadBindings && hasOwnKey(threadBindings, "ttlHours")); @@ -409,6 +451,15 @@ const GROUP_ROUTING_RULES: LegacyConfigRule[] = [ }, ]; +const FEISHU_ACCOUNT_RULES: LegacyConfigRule[] = [ + { + path: ["channels", "feishu", "accounts"], + message: + 'channels.feishu.accounts..botName was renamed to channels.feishu.accounts..name. Run "openclaw doctor --fix".', + match: (value) => hasLegacyFeishuAccountBotName(value), + }, +]; + export const LEGACY_CONFIG_MIGRATIONS_CHANNELS: LegacyConfigMigrationSpec[] = [ defineLegacyConfigMigration({ id: "legacy-group-routing->channel-groups", @@ -421,6 +472,14 @@ export const LEGACY_CONFIG_MIGRATIONS_CHANNELS: LegacyConfigMigrationSpec[] = [ migrateTelegramRequireMention(raw, changes); }, }), + defineLegacyConfigMigration({ + id: "feishu.accounts.botName->name", + describe: "Move legacy Feishu account botName config to account name", + legacyRules: FEISHU_ACCOUNT_RULES, + apply: (raw, changes) => { + migrateFeishuAccountBotName(raw, changes); + }, + }), defineLegacyConfigMigration({ id: "thread-bindings.ttlHours->idleHours", describe: