diff --git a/src/telegram/accounts.test.ts b/src/telegram/accounts.test.ts index 33112386d7d..94b4b89a28c 100644 --- a/src/telegram/accounts.test.ts +++ b/src/telegram/accounts.test.ts @@ -1,8 +1,9 @@ -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { withEnv } from "../test-utils/env.js"; import { listTelegramAccountIds, + resetMissingDefaultWarnFlag, resolveDefaultTelegramAccountId, resolveTelegramAccount, } from "./accounts.js"; @@ -24,6 +25,7 @@ vi.mock("../logging/subsystem.js", () => ({ describe("resolveTelegramAccount", () => { afterEach(() => { warnMock.mockClear(); + resetMissingDefaultWarnFlag(); }); it("falls back to the first configured account when accountId is omitted", () => { @@ -105,6 +107,81 @@ describe("resolveTelegramAccount", () => { }); describe("resolveDefaultTelegramAccountId", () => { + beforeEach(() => { + resetMissingDefaultWarnFlag(); + }); + + afterEach(() => { + warnMock.mockClear(); + resetMissingDefaultWarnFlag(); + }); + + it("warns when accounts.default is missing in multi-account setup (#32137)", () => { + const cfg: OpenClawConfig = { + channels: { + telegram: { + accounts: { work: { botToken: "tok-work" }, alerts: { botToken: "tok-alerts" } }, + }, + }, + }; + + const result = resolveDefaultTelegramAccountId(cfg); + expect(result).toBe("alerts"); + expect(warnMock).toHaveBeenCalledWith(expect.stringContaining("accounts.default is missing")); + }); + + it("does not warn when accounts.default exists", () => { + const cfg: OpenClawConfig = { + channels: { + telegram: { + accounts: { default: { botToken: "tok-default" }, work: { botToken: "tok-work" } }, + }, + }, + }; + + resolveDefaultTelegramAccountId(cfg); + const warnLines = warnMock.mock.calls.map(([line]: [string]) => line); + expect(warnLines.every((line: string) => !line.includes("accounts.default is missing"))).toBe( + true, + ); + }); + + it("does not warn when defaultAccount is explicitly set", () => { + const cfg: OpenClawConfig = { + channels: { + telegram: { + defaultAccount: "work", + accounts: { work: { botToken: "tok-work" } }, + }, + }, + }; + + resolveDefaultTelegramAccountId(cfg); + const warnLines = warnMock.mock.calls.map(([line]: [string]) => line); + expect(warnLines.every((line: string) => !line.includes("accounts.default is missing"))).toBe( + true, + ); + }); + + it("warns only once per process lifetime", () => { + const cfg: OpenClawConfig = { + channels: { + telegram: { + accounts: { work: { botToken: "tok-work" } }, + }, + }, + }; + + resolveDefaultTelegramAccountId(cfg); + resolveDefaultTelegramAccountId(cfg); + resolveDefaultTelegramAccountId(cfg); + + const missingDefaultWarns = warnMock.mock.calls + .map(([line]: [string]) => line) + .filter((line: string) => line.includes("accounts.default is missing")); + expect(missingDefaultWarns).toHaveLength(1); + }); + it("prefers channels.telegram.defaultAccount when it matches a configured account", () => { const cfg: OpenClawConfig = { channels: { diff --git a/src/telegram/accounts.ts b/src/telegram/accounts.ts index 54af9ba2adf..bdc7fcfe5bd 100644 --- a/src/telegram/accounts.ts +++ b/src/telegram/accounts.ts @@ -63,6 +63,13 @@ export function listTelegramAccountIds(cfg: OpenClawConfig): string[] { return ids.toSorted((a, b) => a.localeCompare(b)); } +let emittedMissingDefaultWarn = false; + +/** @internal Reset the once-per-process warning flag. Exported for tests only. */ +export function resetMissingDefaultWarnFlag(): void { + emittedMissingDefaultWarn = false; +} + export function resolveDefaultTelegramAccountId(cfg: OpenClawConfig): string { const boundDefault = resolveDefaultAgentBoundAccountId(cfg, "telegram"); if (boundDefault) { @@ -79,6 +86,14 @@ export function resolveDefaultTelegramAccountId(cfg: OpenClawConfig): string { if (ids.includes(DEFAULT_ACCOUNT_ID)) { return DEFAULT_ACCOUNT_ID; } + if (ids.length > 0 && !emittedMissingDefaultWarn) { + emittedMissingDefaultWarn = true; + log.warn( + `channels.telegram: accounts.default is missing; falling back to "${ids[0]}". ` + + "Set channels.telegram.defaultAccount or add an accounts.default entry " + + "to avoid routing surprises in multi-account setups.", + ); + } return ids[0] ?? DEFAULT_ACCOUNT_ID; }