diff --git a/CHANGELOG.md b/CHANGELOG.md index 355cee7ad..86c881fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Docs: https://docs.clawd.bot ### Fixes - Config: avoid stack traces for invalid configs and log the config path. +- Doctor: avoid recreating WhatsApp config when only legacy routing keys remain. (#900) - Doctor: warn when gateway.mode is unset with configure/config guidance. - OpenCode Zen: route models to the Zen API shape per family so proxy endpoints are used. (#1416) - macOS: include Textual syntax highlighting resources in packaged app to prevent chat crashes. (#1362) diff --git a/src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts b/src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts index fd0f2f296..dbc08339d 100644 --- a/src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts +++ b/src/config/config.legacy-config-detection.rejects-routing-allowfrom.test.ts @@ -23,21 +23,33 @@ describe("legacy config detection", () => { expect(res.issues[0]?.path).toBe("routing.groupChat.requireMention"); } }); - it("migrates routing.allowFrom to channels.whatsapp.allowFrom", async () => { + it("migrates routing.allowFrom to channels.whatsapp.allowFrom when whatsapp configured", async () => { vi.resetModules(); const { migrateLegacyConfig } = await import("./config.js"); const res = migrateLegacyConfig({ routing: { allowFrom: ["+15555550123"] }, + channels: { whatsapp: {} }, }); expect(res.changes).toContain("Moved routing.allowFrom → channels.whatsapp.allowFrom."); expect(res.config?.channels?.whatsapp?.allowFrom).toEqual(["+15555550123"]); expect(res.config?.routing?.allowFrom).toBeUndefined(); }); - it("migrates routing.groupChat.requireMention to channels whatsapp/telegram/imessage groups", async () => { + it("drops routing.allowFrom when whatsapp missing", async () => { + vi.resetModules(); + const { migrateLegacyConfig } = await import("./config.js"); + const res = migrateLegacyConfig({ + routing: { allowFrom: ["+15555550123"] }, + }); + expect(res.changes).toContain("Removed routing.allowFrom (channels.whatsapp not configured)."); + expect(res.config?.channels?.whatsapp).toBeUndefined(); + expect(res.config?.routing?.allowFrom).toBeUndefined(); + }); + it("migrates routing.groupChat.requireMention to channels whatsapp/telegram/imessage groups when whatsapp configured", async () => { vi.resetModules(); const { migrateLegacyConfig } = await import("./config.js"); const res = migrateLegacyConfig({ routing: { groupChat: { requireMention: false } }, + channels: { whatsapp: {} }, }); expect(res.changes).toContain( 'Moved routing.groupChat.requireMention → channels.whatsapp.groups."*".requireMention.', @@ -53,6 +65,26 @@ describe("legacy config detection", () => { expect(res.config?.channels?.imessage?.groups?.["*"]?.requireMention).toBe(false); expect(res.config?.routing?.groupChat?.requireMention).toBeUndefined(); }); + it("migrates routing.groupChat.requireMention to telegram/imessage when whatsapp missing", async () => { + vi.resetModules(); + const { migrateLegacyConfig } = await import("./config.js"); + const res = migrateLegacyConfig({ + routing: { groupChat: { requireMention: false } }, + }); + expect(res.changes).toContain( + 'Moved routing.groupChat.requireMention → channels.telegram.groups."*".requireMention.', + ); + expect(res.changes).toContain( + 'Moved routing.groupChat.requireMention → channels.imessage.groups."*".requireMention.', + ); + expect(res.changes).not.toContain( + 'Moved routing.groupChat.requireMention → channels.whatsapp.groups."*".requireMention.', + ); + expect(res.config?.channels?.whatsapp).toBeUndefined(); + expect(res.config?.channels?.telegram?.groups?.["*"]?.requireMention).toBe(false); + expect(res.config?.channels?.imessage?.groups?.["*"]?.requireMention).toBe(false); + expect(res.config?.routing?.groupChat?.requireMention).toBeUndefined(); + }); it("migrates routing.groupChat.mentionPatterns to messages.groupChat.mentionPatterns", async () => { vi.resetModules(); const { migrateLegacyConfig } = await import("./config.js"); diff --git a/src/config/legacy.migrations.part-1.ts b/src/config/legacy.migrations.part-1.ts index d1d0a57e7..f537c3ce8 100644 --- a/src/config/legacy.migrations.part-1.ts +++ b/src/config/legacy.migrations.part-1.ts @@ -156,11 +156,16 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ const allowFrom = (routing as Record).allowFrom; if (allowFrom === undefined) return; - const channels = ensureRecord(raw, "channels"); - const whatsapp = - channels.whatsapp && typeof channels.whatsapp === "object" - ? (channels.whatsapp as Record) - : {}; + const channels = getRecord(raw.channels); + const whatsapp = channels ? getRecord(channels.whatsapp) : null; + if (!whatsapp) { + delete (routing as Record).allowFrom; + if (Object.keys(routing as Record).length === 0) { + delete raw.routing; + } + changes.push("Removed routing.allowFrom (channels.whatsapp not configured)."); + return; + } if (whatsapp.allowFrom === undefined) { whatsapp.allowFrom = allowFrom; @@ -173,8 +178,8 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ if (Object.keys(routing as Record).length === 0) { delete raw.routing; } - channels.whatsapp = whatsapp; - raw.channels = channels; + channels!.whatsapp = whatsapp; + raw.channels = channels!; }, }, { @@ -193,7 +198,11 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ if (requireMention === undefined) return; const channels = ensureRecord(raw, "channels"); - const applyTo = (key: "whatsapp" | "telegram" | "imessage") => { + const applyTo = ( + key: "whatsapp" | "telegram" | "imessage", + options?: { requireExisting?: boolean }, + ) => { + if (options?.requireExisting && !isRecord(channels[key])) return; const section = channels[key] && typeof channels[key] === "object" ? (channels[key] as Record) @@ -222,7 +231,7 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [ } }; - applyTo("whatsapp"); + applyTo("whatsapp", { requireExisting: true }); applyTo("telegram"); applyTo("imessage");