diff --git a/src/commands/doctor-config-flow.missing-explicit-default-account.test.ts b/src/commands/doctor-config-flow.missing-explicit-default-account.test.ts index faf14653961..5ef4f7a6cae 100644 --- a/src/commands/doctor-config-flow.missing-explicit-default-account.test.ts +++ b/src/commands/doctor-config-flow.missing-explicit-default-account.test.ts @@ -66,6 +66,22 @@ describe("collectMissingExplicitDefaultAccountWarnings", () => { expect(collectMissingExplicitDefaultAccountWarnings(cfg)).toEqual([]); }); + it("normalizes defaultAccount before validating configured account ids", () => { + const cfg: OpenClawConfig = { + channels: { + telegram: { + defaultAccount: "Router D", + accounts: { + "router-d": { botToken: "r" }, + work: { botToken: "w" }, + }, + }, + }, + }; + + expect(collectMissingExplicitDefaultAccountWarnings(cfg)).toEqual([]); + }); + it("warns when defaultAccount is invalid for configured accounts", () => { const cfg: OpenClawConfig = { channels: { diff --git a/src/commands/doctor-config-flow.ts b/src/commands/doctor-config-flow.ts index bab0806bbaf..69cbc764397 100644 --- a/src/commands/doctor-config-flow.ts +++ b/src/commands/doctor-config-flow.ts @@ -219,15 +219,21 @@ function normalizeBindingChannelKey(raw?: string | null): string { return (raw ?? "").trim().toLowerCase(); } -export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig): string[] { +type ChannelMissingDefaultAccountContext = { + channelKey: string; + channel: Record; + normalizedAccountIds: string[]; +}; + +function collectChannelsMissingDefaultAccount( + cfg: OpenClawConfig, +): ChannelMissingDefaultAccountContext[] { const channels = asObjectRecord(cfg.channels); if (!channels) { return []; } - const bindings = Array.isArray(cfg.bindings) ? cfg.bindings : []; - const warnings: string[] = []; - + const contexts: ChannelMissingDefaultAccountContext[] = []; for (const [channelKey, rawChannel] of Object.entries(channels)) { const channel = asObjectRecord(rawChannel); if (!channel) { @@ -244,10 +250,20 @@ export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig) .map((accountId) => normalizeAccountId(accountId)) .filter(Boolean), ), - ); + ).toSorted((a, b) => a.localeCompare(b)); if (normalizedAccountIds.length === 0 || normalizedAccountIds.includes(DEFAULT_ACCOUNT_ID)) { continue; } + contexts.push({ channelKey, channel, normalizedAccountIds }); + } + return contexts; +} + +export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig): string[] { + const bindings = Array.isArray(cfg.bindings) ? cfg.bindings : []; + const warnings: string[] = []; + + for (const { channelKey, normalizedAccountIds } of collectChannelsMissingDefaultAccount(cfg)) { const accountIdSet = new Set(normalizedAccountIds); const channelPattern = normalizeBindingChannelKey(channelKey); @@ -309,31 +325,11 @@ export function collectMissingDefaultAccountBindingWarnings(cfg: OpenClawConfig) } export function collectMissingExplicitDefaultAccountWarnings(cfg: OpenClawConfig): string[] { - const channels = asObjectRecord(cfg.channels); - if (!channels) { - return []; - } - const warnings: string[] = []; - for (const [channelKey, rawChannel] of Object.entries(channels)) { - const channel = asObjectRecord(rawChannel); - if (!channel) { - continue; - } - - const accounts = asObjectRecord(channel.accounts); - if (!accounts) { - continue; - } - - const normalizedAccountIds = Array.from( - new Set( - Object.keys(accounts) - .map((accountId) => normalizeAccountId(accountId)) - .filter(Boolean), - ), - ).toSorted((a, b) => a.localeCompare(b)); - if (normalizedAccountIds.length < 2 || normalizedAccountIds.includes(DEFAULT_ACCOUNT_ID)) { + for (const { channelKey, channel, normalizedAccountIds } of collectChannelsMissingDefaultAccount( + cfg, + )) { + if (normalizedAccountIds.length < 2) { continue; } diff --git a/src/telegram/accounts.test.ts b/src/telegram/accounts.test.ts index 3d0b84f8fcf..1c0807aaa1a 100644 --- a/src/telegram/accounts.test.ts +++ b/src/telegram/accounts.test.ts @@ -12,6 +12,10 @@ const { warnMock } = vi.hoisted(() => ({ warnMock: vi.fn(), })); +function warningLines(): string[] { + return warnMock.mock.calls.map(([line]) => String(line)); +} + vi.mock("../logging/subsystem.js", () => ({ createSubsystemLogger: () => { const logger = { @@ -140,8 +144,7 @@ describe("resolveDefaultTelegramAccountId", () => { }; resolveDefaultTelegramAccountId(cfg); - const warnLines = warnMock.mock.calls.map(([line]) => String(line)); - expect(warnLines.every((line: string) => !line.includes("accounts.default is missing"))).toBe( + expect(warningLines().every((line) => !line.includes("accounts.default is missing"))).toBe( true, ); }); @@ -157,8 +160,7 @@ describe("resolveDefaultTelegramAccountId", () => { }; resolveDefaultTelegramAccountId(cfg); - const warnLines = warnMock.mock.calls.map(([line]) => String(line)); - expect(warnLines.every((line: string) => !line.includes("accounts.default is missing"))).toBe( + expect(warningLines().every((line) => !line.includes("accounts.default is missing"))).toBe( true, ); }); @@ -173,8 +175,7 @@ describe("resolveDefaultTelegramAccountId", () => { }; resolveDefaultTelegramAccountId(cfg); - const warnLines = warnMock.mock.calls.map(([line]) => String(line)); - expect(warnLines.every((line: string) => !line.includes("accounts.default is missing"))).toBe( + expect(warningLines().every((line) => !line.includes("accounts.default is missing"))).toBe( true, ); }); @@ -192,9 +193,9 @@ describe("resolveDefaultTelegramAccountId", () => { resolveDefaultTelegramAccountId(cfg); resolveDefaultTelegramAccountId(cfg); - const missingDefaultWarns = warnMock.mock.calls - .map(([line]) => String(line)) - .filter((line) => line.includes("accounts.default is missing")); + const missingDefaultWarns = warningLines().filter((line) => + line.includes("accounts.default is missing"), + ); expect(missingDefaultWarns).toHaveLength(1); });