fix: avoid whatsapp config resurrection

This commit is contained in:
Peter Steinberger
2026-01-22 04:49:49 +00:00
parent b60db040e2
commit f02960df26
3 changed files with 53 additions and 11 deletions

View File

@@ -19,6 +19,7 @@ Docs: https://docs.clawd.bot
### Fixes ### Fixes
- Config: avoid stack traces for invalid configs and log the config path. - 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. - 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) - 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) - macOS: include Textual syntax highlighting resources in packaged app to prevent chat crashes. (#1362)

View File

@@ -23,21 +23,33 @@ describe("legacy config detection", () => {
expect(res.issues[0]?.path).toBe("routing.groupChat.requireMention"); 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(); vi.resetModules();
const { migrateLegacyConfig } = await import("./config.js"); const { migrateLegacyConfig } = await import("./config.js");
const res = migrateLegacyConfig({ const res = migrateLegacyConfig({
routing: { allowFrom: ["+15555550123"] }, routing: { allowFrom: ["+15555550123"] },
channels: { whatsapp: {} },
}); });
expect(res.changes).toContain("Moved routing.allowFrom → channels.whatsapp.allowFrom."); expect(res.changes).toContain("Moved routing.allowFrom → channels.whatsapp.allowFrom.");
expect(res.config?.channels?.whatsapp?.allowFrom).toEqual(["+15555550123"]); expect(res.config?.channels?.whatsapp?.allowFrom).toEqual(["+15555550123"]);
expect(res.config?.routing?.allowFrom).toBeUndefined(); 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(); vi.resetModules();
const { migrateLegacyConfig } = await import("./config.js"); const { migrateLegacyConfig } = await import("./config.js");
const res = migrateLegacyConfig({ const res = migrateLegacyConfig({
routing: { groupChat: { requireMention: false } }, routing: { groupChat: { requireMention: false } },
channels: { whatsapp: {} },
}); });
expect(res.changes).toContain( expect(res.changes).toContain(
'Moved routing.groupChat.requireMention → channels.whatsapp.groups."*".requireMention.', '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?.channels?.imessage?.groups?.["*"]?.requireMention).toBe(false);
expect(res.config?.routing?.groupChat?.requireMention).toBeUndefined(); 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 () => { it("migrates routing.groupChat.mentionPatterns to messages.groupChat.mentionPatterns", async () => {
vi.resetModules(); vi.resetModules();
const { migrateLegacyConfig } = await import("./config.js"); const { migrateLegacyConfig } = await import("./config.js");

View File

@@ -156,11 +156,16 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [
const allowFrom = (routing as Record<string, unknown>).allowFrom; const allowFrom = (routing as Record<string, unknown>).allowFrom;
if (allowFrom === undefined) return; if (allowFrom === undefined) return;
const channels = ensureRecord(raw, "channels"); const channels = getRecord(raw.channels);
const whatsapp = const whatsapp = channels ? getRecord(channels.whatsapp) : null;
channels.whatsapp && typeof channels.whatsapp === "object" if (!whatsapp) {
? (channels.whatsapp as Record<string, unknown>) delete (routing as Record<string, unknown>).allowFrom;
: {}; if (Object.keys(routing as Record<string, unknown>).length === 0) {
delete raw.routing;
}
changes.push("Removed routing.allowFrom (channels.whatsapp not configured).");
return;
}
if (whatsapp.allowFrom === undefined) { if (whatsapp.allowFrom === undefined) {
whatsapp.allowFrom = allowFrom; whatsapp.allowFrom = allowFrom;
@@ -173,8 +178,8 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [
if (Object.keys(routing as Record<string, unknown>).length === 0) { if (Object.keys(routing as Record<string, unknown>).length === 0) {
delete raw.routing; delete raw.routing;
} }
channels.whatsapp = whatsapp; channels!.whatsapp = whatsapp;
raw.channels = channels; raw.channels = channels!;
}, },
}, },
{ {
@@ -193,7 +198,11 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [
if (requireMention === undefined) return; if (requireMention === undefined) return;
const channels = ensureRecord(raw, "channels"); 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 = const section =
channels[key] && typeof channels[key] === "object" channels[key] && typeof channels[key] === "object"
? (channels[key] as Record<string, unknown>) ? (channels[key] as Record<string, unknown>)
@@ -222,7 +231,7 @@ export const LEGACY_CONFIG_MIGRATIONS_PART_1: LegacyConfigMigration[] = [
} }
}; };
applyTo("whatsapp"); applyTo("whatsapp", { requireExisting: true });
applyTo("telegram"); applyTo("telegram");
applyTo("imessage"); applyTo("imessage");