diff --git a/src/channels/read-only-account-inspect.telegram.ts b/src/channels/read-only-account-inspect.telegram.ts index dda404a48ab..0bbccd805da 100644 --- a/src/channels/read-only-account-inspect.telegram.ts +++ b/src/channels/read-only-account-inspect.telegram.ts @@ -59,7 +59,7 @@ export function listTelegramAccountIds(cfg: OpenClawConfig): string[] { }); } -function resolveDefaultTelegramAccountId(cfg: OpenClawConfig): string { +export function resolveDefaultTelegramAccountId(cfg: OpenClawConfig): string { const boundDefault = resolveDefaultAgentBoundAccountId(cfg, "telegram"); if (boundDefault) { return boundDefault; diff --git a/src/commands/doctor-state-migrations.test.ts b/src/commands/doctor-state-migrations.test.ts index ec465632cfa..4d2e38766a1 100644 --- a/src/commands/doctor-state-migrations.test.ts +++ b/src/commands/doctor-state-migrations.test.ts @@ -318,11 +318,12 @@ describe("doctor legacy state migrations", () => { }); }); - it("fans out legacy Telegram pairing allowFrom store to configured named accounts", async () => { + it("does not fan out legacy Telegram pairing allowFrom store to configured named accounts", async () => { const root = await makeTempRoot(); const cfg: OpenClawConfig = { channels: { telegram: { + defaultAccount: "bot2", accounts: { bot1: {}, bot2: {}, @@ -333,20 +334,53 @@ describe("doctor legacy state migrations", () => { const { oauthDir, detected, result } = await runTelegramAllowFromMigration({ root, cfg }); expect(detected.pairingAllowFrom.hasLegacyTelegram).toBe(true); expect( - detected.pairingAllowFrom.copyPlans.map((plan) => path.basename(plan.targetPath)).toSorted(), - ).toEqual(["telegram-bot1-allowFrom.json", "telegram-bot2-allowFrom.json"]); + detected.pairingAllowFrom.copyPlans.map((plan) => path.basename(plan.targetPath)), + ).toEqual(["telegram-bot2-allowFrom.json"]); expect(result.warnings).toEqual([]); const bot1Target = path.join(oauthDir, "telegram-bot1-allowFrom.json"); const bot2Target = path.join(oauthDir, "telegram-bot2-allowFrom.json"); - expect(fs.existsSync(bot1Target)).toBe(true); + const defaultTarget = path.join(oauthDir, "telegram-default-allowFrom.json"); + expect(fs.existsSync(bot1Target)).toBe(false); expect(fs.existsSync(bot2Target)).toBe(true); - expect(fs.existsSync(path.join(oauthDir, "telegram-default-allowFrom.json"))).toBe(false); - expect(JSON.parse(fs.readFileSync(bot1Target, "utf-8"))).toEqual({ + expect(fs.existsSync(defaultTarget)).toBe(false); + expect(JSON.parse(fs.readFileSync(bot2Target, "utf-8"))).toEqual({ version: 1, allowFrom: ["123456"], }); - expect(JSON.parse(fs.readFileSync(bot2Target, "utf-8"))).toEqual({ + }); + + it("migrates legacy Telegram pairing allowFrom store to the default agent bound account", async () => { + const root = await makeTempRoot(); + const cfg: OpenClawConfig = { + agents: { + list: [{ id: "ops", default: true }], + }, + bindings: [{ agentId: "ops", match: { channel: "telegram", accountId: "alerts" } }], + channels: { + telegram: { + accounts: { + alerts: {}, + backup: {}, + }, + }, + }, + }; + + const { oauthDir, detected, result } = await runTelegramAllowFromMigration({ root, cfg }); + expect(detected.pairingAllowFrom.hasLegacyTelegram).toBe(true); + expect( + detected.pairingAllowFrom.copyPlans.map((plan) => path.basename(plan.targetPath)), + ).toEqual(["telegram-alerts-allowFrom.json"]); + expect(result.warnings).toEqual([]); + + const alertsTarget = path.join(oauthDir, "telegram-alerts-allowFrom.json"); + const backupTarget = path.join(oauthDir, "telegram-backup-allowFrom.json"); + const defaultTarget = path.join(oauthDir, "telegram-default-allowFrom.json"); + expect(fs.existsSync(alertsTarget)).toBe(true); + expect(fs.existsSync(backupTarget)).toBe(false); + expect(fs.existsSync(defaultTarget)).toBe(false); + expect(JSON.parse(fs.readFileSync(alertsTarget, "utf-8"))).toEqual({ version: 1, allowFrom: ["123456"], }); diff --git a/src/infra/state-migrations.test.ts b/src/infra/state-migrations.test.ts index 5462463a9a4..5f0ae657154 100644 --- a/src/infra/state-migrations.test.ts +++ b/src/infra/state-migrations.test.ts @@ -19,6 +19,7 @@ function createConfig(): OpenClawConfig { }, channels: { telegram: { + defaultAccount: "alpha", accounts: { beta: {}, alpha: {}, @@ -100,7 +101,6 @@ describe("state migrations", () => { expect(detected.pairingAllowFrom.hasLegacyTelegram).toBe(true); expect(detected.pairingAllowFrom.copyPlans.map((plan) => plan.targetPath)).toEqual([ resolveChannelAllowFromPath("telegram", env, "alpha"), - resolveChannelAllowFromPath("telegram", env, "beta"), ]); expect(detected.preview).toEqual([ `- Sessions: ${path.join(stateDir, "sessions")} → ${path.join(stateDir, "agents", "worker-1", "sessions")}`, @@ -108,7 +108,6 @@ describe("state migrations", () => { `- Agent dir: ${path.join(stateDir, "agent")} → ${path.join(stateDir, "agents", "worker-1", "agent")}`, `- WhatsApp auth: ${path.join(stateDir, "credentials")} → ${path.join(stateDir, "credentials", "whatsapp", "default")} (keep oauth.json)`, `- Telegram pairing allowFrom: ${resolveChannelAllowFromPath("telegram", env)} → ${resolveChannelAllowFromPath("telegram", env, "alpha")}`, - `- Telegram pairing allowFrom: ${resolveChannelAllowFromPath("telegram", env)} → ${resolveChannelAllowFromPath("telegram", env, "beta")}`, ]); }); @@ -135,7 +134,6 @@ describe("state migrations", () => { "Moved WhatsApp auth creds.json → whatsapp/default", "Moved WhatsApp auth pre-key-1.json → whatsapp/default", `Copied Telegram pairing allowFrom → ${resolveChannelAllowFromPath("telegram", env, "alpha")}`, - `Copied Telegram pairing allowFrom → ${resolveChannelAllowFromPath("telegram", env, "beta")}`, ]); const mergedStore = JSON.parse( @@ -176,7 +174,10 @@ describe("state migrations", () => { fs.readFile(resolveChannelAllowFromPath("telegram", env, "alpha"), "utf8"), ).resolves.toBe('["123","456"]\n'); await expect( - fs.readFile(resolveChannelAllowFromPath("telegram", env, "beta"), "utf8"), - ).resolves.toBe('["123","456"]\n'); + fs.stat(resolveChannelAllowFromPath("telegram", env, "default")), + ).rejects.toMatchObject({ code: "ENOENT" }); + await expect( + fs.stat(resolveChannelAllowFromPath("telegram", env, "beta")), + ).rejects.toMatchObject({ code: "ENOENT" }); }); }); diff --git a/src/infra/state-migrations.ts b/src/infra/state-migrations.ts index ad1f5a440bf..e888fa0ffbd 100644 --- a/src/infra/state-migrations.ts +++ b/src/infra/state-migrations.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { resolveDefaultAgentId } from "../agents/agent-scope.js"; -import { listTelegramAccountIds } from "../channels/read-only-account-inspect.telegram.js"; +import { resolveDefaultTelegramAccountId } from "../channels/read-only-account-inspect.telegram.js"; import type { OpenClawConfig } from "../config/config.js"; import { resolveLegacyStateDirs, @@ -700,14 +700,14 @@ export async function detectLegacyStateMigrations(params: { fileExists(path.join(oauthDir, "creds.json")) && !fileExists(path.join(targetWhatsAppAuthDir, "creds.json")); const legacyTelegramAllowFromPath = resolveChannelAllowFromPath("telegram", env); + const targetTelegramAccountId = resolveDefaultTelegramAccountId(params.cfg); + const targetTelegramAllowFromPath = resolveChannelAllowFromPath( + "telegram", + env, + targetTelegramAccountId, + ); const telegramPairingAllowFromPlans = fileExists(legacyTelegramAllowFromPath) - ? Array.from( - new Set( - listTelegramAccountIds(params.cfg).map((accountId) => - resolveChannelAllowFromPath("telegram", env, accountId), - ), - ), - ) + ? [targetTelegramAllowFromPath] .filter((targetPath) => !fileExists(targetPath)) .map( (targetPath): FileCopyPlan => ({