From b0ac284dae4863a6daaad345032cbb27d7a2521f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 7 Mar 2026 19:53:31 +0000 Subject: [PATCH] refactor: share setup account config patch helper --- extensions/bluebubbles/src/channel.ts | 12 ---- extensions/googlechat/src/channel.ts | 38 ++-------- extensions/mattermost/src/channel.ts | 50 ++++--------- extensions/zalo/src/channel.ts | 55 ++++----------- extensions/zalouser/src/channel.ts | 36 ++-------- src/channels/plugins/setup-helpers.test.ts | 81 ++++++++++++++++++++++ src/channels/plugins/setup-helpers.ts | 50 +++++++++++++ src/plugin-sdk/googlechat.ts | 1 + src/plugin-sdk/mattermost.ts | 1 + src/plugin-sdk/zalo.ts | 1 + src/plugin-sdk/zalouser.ts | 1 + 11 files changed, 176 insertions(+), 150 deletions(-) create mode 100644 src/channels/plugins/setup-helpers.test.ts diff --git a/extensions/bluebubbles/src/channel.ts b/extensions/bluebubbles/src/channel.ts index 741f93d3ae0..8e3ae2ea6a5 100644 --- a/extensions/bluebubbles/src/channel.ts +++ b/extensions/bluebubbles/src/channel.ts @@ -256,18 +256,6 @@ export const bluebubblesPlugin: ChannelPlugin = { channelKey: "bluebubbles", }) : namedConfig; - if (accountId === DEFAULT_ACCOUNT_ID) { - return applyBlueBubblesConnectionConfig({ - cfg: next, - accountId, - patch: { - serverUrl: input.httpUrl, - password: input.password, - webhookPath: input.webhookPath, - }, - onlyDefinedFields: true, - }); - } return applyBlueBubblesConnectionConfig({ cfg: next, accountId, diff --git a/extensions/googlechat/src/channel.ts b/extensions/googlechat/src/channel.ts index 6dd896e9f00..333f5c74ac5 100644 --- a/extensions/googlechat/src/channel.ts +++ b/extensions/googlechat/src/channel.ts @@ -1,5 +1,6 @@ import { applyAccountNameToChannelSection, + applySetupAccountConfigPatch, buildChannelConfigSchema, DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, @@ -345,37 +346,12 @@ export const googlechatPlugin: ChannelPlugin = { ...(webhookPath ? { webhookPath } : {}), ...(webhookUrl ? { webhookUrl } : {}), }; - if (accountId === DEFAULT_ACCOUNT_ID) { - return { - ...next, - channels: { - ...next.channels, - googlechat: { - ...next.channels?.["googlechat"], - enabled: true, - ...configPatch, - }, - }, - } as OpenClawConfig; - } - return { - ...next, - channels: { - ...next.channels, - googlechat: { - ...next.channels?.["googlechat"], - enabled: true, - accounts: { - ...next.channels?.["googlechat"]?.accounts, - [accountId]: { - ...next.channels?.["googlechat"]?.accounts?.[accountId], - enabled: true, - ...configPatch, - }, - }, - }, - }, - } as OpenClawConfig; + return applySetupAccountConfigPatch({ + cfg: next, + channelKey: "googlechat", + accountId, + patch: configPatch, + }); }, }, outbound: { diff --git a/extensions/mattermost/src/channel.ts b/extensions/mattermost/src/channel.ts index b9f5a3bc85d..c413e1790f9 100644 --- a/extensions/mattermost/src/channel.ts +++ b/extensions/mattermost/src/channel.ts @@ -1,5 +1,6 @@ import { applyAccountNameToChannelSection, + applySetupAccountConfigPatch, buildChannelConfigSchema, DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, @@ -449,43 +450,18 @@ export const mattermostPlugin: ChannelPlugin = { channelKey: "mattermost", }) : namedConfig; - if (accountId === DEFAULT_ACCOUNT_ID) { - return { - ...next, - channels: { - ...next.channels, - mattermost: { - ...next.channels?.mattermost, - enabled: true, - ...(input.useEnv - ? {} - : { - ...(token ? { botToken: token } : {}), - ...(baseUrl ? { baseUrl } : {}), - }), - }, - }, - }; - } - return { - ...next, - channels: { - ...next.channels, - mattermost: { - ...next.channels?.mattermost, - enabled: true, - accounts: { - ...next.channels?.mattermost?.accounts, - [accountId]: { - ...next.channels?.mattermost?.accounts?.[accountId], - enabled: true, - ...(token ? { botToken: token } : {}), - ...(baseUrl ? { baseUrl } : {}), - }, - }, - }, - }, - }; + const patch = input.useEnv + ? {} + : { + ...(token ? { botToken: token } : {}), + ...(baseUrl ? { baseUrl } : {}), + }; + return applySetupAccountConfigPatch({ + cfg: next, + channelKey: "mattermost", + accountId, + patch, + }); }, }, gateway: { diff --git a/extensions/zalo/src/channel.ts b/extensions/zalo/src/channel.ts index b6a7f7d0486..54dce454693 100644 --- a/extensions/zalo/src/channel.ts +++ b/extensions/zalo/src/channel.ts @@ -6,6 +6,7 @@ import type { } from "openclaw/plugin-sdk/zalo"; import { applyAccountNameToChannelSection, + applySetupAccountConfigPatch, buildBaseAccountStatusSnapshot, buildChannelConfigSchema, buildTokenChannelStatusSummary, @@ -243,47 +244,19 @@ export const zaloPlugin: ChannelPlugin = { channelKey: "zalo", }) : namedConfig; - if (accountId === DEFAULT_ACCOUNT_ID) { - return { - ...next, - channels: { - ...next.channels, - zalo: { - ...next.channels?.zalo, - enabled: true, - ...(input.useEnv - ? {} - : input.tokenFile - ? { tokenFile: input.tokenFile } - : input.token - ? { botToken: input.token } - : {}), - }, - }, - } as OpenClawConfig; - } - return { - ...next, - channels: { - ...next.channels, - zalo: { - ...next.channels?.zalo, - enabled: true, - accounts: { - ...next.channels?.zalo?.accounts, - [accountId]: { - ...next.channels?.zalo?.accounts?.[accountId], - enabled: true, - ...(input.tokenFile - ? { tokenFile: input.tokenFile } - : input.token - ? { botToken: input.token } - : {}), - }, - }, - }, - }, - } as OpenClawConfig; + const patch = input.useEnv + ? {} + : input.tokenFile + ? { tokenFile: input.tokenFile } + : input.token + ? { botToken: input.token } + : {}; + return applySetupAccountConfigPatch({ + cfg: next, + channelKey: "zalo", + accountId, + patch, + }); }, }, pairing: { diff --git a/extensions/zalouser/src/channel.ts b/extensions/zalouser/src/channel.ts index 41327f1fe7e..2de31e9aa3e 100644 --- a/extensions/zalouser/src/channel.ts +++ b/extensions/zalouser/src/channel.ts @@ -10,6 +10,7 @@ import type { } from "openclaw/plugin-sdk/zalouser"; import { applyAccountNameToChannelSection, + applySetupAccountConfigPatch, buildChannelSendResult, buildBaseAccountStatusSnapshot, buildChannelConfigSchema, @@ -329,35 +330,12 @@ export const zalouserPlugin: ChannelPlugin = { channelKey: "zalouser", }) : namedConfig; - if (accountId === DEFAULT_ACCOUNT_ID) { - return { - ...next, - channels: { - ...next.channels, - zalouser: { - ...next.channels?.zalouser, - enabled: true, - }, - }, - } as OpenClawConfig; - } - return { - ...next, - channels: { - ...next.channels, - zalouser: { - ...next.channels?.zalouser, - enabled: true, - accounts: { - ...next.channels?.zalouser?.accounts, - [accountId]: { - ...next.channels?.zalouser?.accounts?.[accountId], - enabled: true, - }, - }, - }, - }, - } as OpenClawConfig; + return applySetupAccountConfigPatch({ + cfg: next, + channelKey: "zalouser", + accountId, + patch: {}, + }); }, }, messaging: { diff --git a/src/channels/plugins/setup-helpers.test.ts b/src/channels/plugins/setup-helpers.test.ts new file mode 100644 index 00000000000..df4609fc76f --- /dev/null +++ b/src/channels/plugins/setup-helpers.test.ts @@ -0,0 +1,81 @@ +import { describe, expect, it } from "vitest"; +import type { OpenClawConfig } from "../../config/config.js"; +import { DEFAULT_ACCOUNT_ID } from "../../routing/session-key.js"; +import { applySetupAccountConfigPatch } from "./setup-helpers.js"; + +function asConfig(value: unknown): OpenClawConfig { + return value as OpenClawConfig; +} + +describe("applySetupAccountConfigPatch", () => { + it("patches top-level config for default account and enables channel", () => { + const next = applySetupAccountConfigPatch({ + cfg: asConfig({ + channels: { + zalo: { + webhookPath: "/old", + enabled: false, + }, + }, + }), + channelKey: "zalo", + accountId: DEFAULT_ACCOUNT_ID, + patch: { webhookPath: "/new", botToken: "tok" }, + }); + + expect(next.channels?.zalo).toMatchObject({ + enabled: true, + webhookPath: "/new", + botToken: "tok", + }); + }); + + it("patches named account config and enables both channel and account", () => { + const next = applySetupAccountConfigPatch({ + cfg: asConfig({ + channels: { + zalo: { + enabled: false, + accounts: { + work: { botToken: "old", enabled: false }, + }, + }, + }, + }), + channelKey: "zalo", + accountId: "work", + patch: { botToken: "new" }, + }); + + expect(next.channels?.zalo).toMatchObject({ + enabled: true, + accounts: { + work: { enabled: true, botToken: "new" }, + }, + }); + }); + + it("normalizes account id and preserves other accounts", () => { + const next = applySetupAccountConfigPatch({ + cfg: asConfig({ + channels: { + zalo: { + accounts: { + personal: { botToken: "personal-token" }, + }, + }, + }, + }), + channelKey: "zalo", + accountId: "Work Team", + patch: { botToken: "work-token" }, + }); + + expect(next.channels?.zalo).toMatchObject({ + accounts: { + personal: { botToken: "personal-token" }, + "work-team": { enabled: true, botToken: "work-token" }, + }, + }); + }); +}); diff --git a/src/channels/plugins/setup-helpers.ts b/src/channels/plugins/setup-helpers.ts index 72b3163a62e..5045c431d60 100644 --- a/src/channels/plugins/setup-helpers.ts +++ b/src/channels/plugins/setup-helpers.ts @@ -120,6 +120,56 @@ export function migrateBaseNameToDefaultAccount(params: { } as OpenClawConfig; } +export function applySetupAccountConfigPatch(params: { + cfg: OpenClawConfig; + channelKey: string; + accountId: string; + patch: Record; +}): OpenClawConfig { + const accountId = normalizeAccountId(params.accountId); + const channels = params.cfg.channels as Record | undefined; + const channelConfig = channels?.[params.channelKey]; + const base = + typeof channelConfig === "object" && channelConfig + ? (channelConfig as Record & { + accounts?: Record>; + }) + : undefined; + if (accountId === DEFAULT_ACCOUNT_ID) { + return { + ...params.cfg, + channels: { + ...params.cfg.channels, + [params.channelKey]: { + ...base, + enabled: true, + ...params.patch, + }, + }, + } as OpenClawConfig; + } + + const accounts = base?.accounts ?? {}; + return { + ...params.cfg, + channels: { + ...params.cfg.channels, + [params.channelKey]: { + ...base, + enabled: true, + accounts: { + ...accounts, + [accountId]: { + ...accounts[accountId], + enabled: true, + ...params.patch, + }, + }, + }, + }, + } as OpenClawConfig; +} + type ChannelSectionRecord = Record & { accounts?: Record>; }; diff --git a/src/plugin-sdk/googlechat.ts b/src/plugin-sdk/googlechat.ts index 40ebe2db47b..0b84d803d5f 100644 --- a/src/plugin-sdk/googlechat.ts +++ b/src/plugin-sdk/googlechat.ts @@ -30,6 +30,7 @@ export { export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; export { applyAccountNameToChannelSection, + applySetupAccountConfigPatch, migrateBaseNameToDefaultAccount, } from "../channels/plugins/setup-helpers.js"; export { createAccountListHelpers } from "../channels/plugins/account-helpers.js"; diff --git a/src/plugin-sdk/mattermost.ts b/src/plugin-sdk/mattermost.ts index ca4af538a78..4df37da8dd8 100644 --- a/src/plugin-sdk/mattermost.ts +++ b/src/plugin-sdk/mattermost.ts @@ -35,6 +35,7 @@ export { } from "../channels/plugins/onboarding/helpers.js"; export { applyAccountNameToChannelSection, + applySetupAccountConfigPatch, migrateBaseNameToDefaultAccount, } from "../channels/plugins/setup-helpers.js"; export { createAccountListHelpers } from "../channels/plugins/account-helpers.js"; diff --git a/src/plugin-sdk/zalo.ts b/src/plugin-sdk/zalo.ts index c11739dfd4a..011b95e43cb 100644 --- a/src/plugin-sdk/zalo.ts +++ b/src/plugin-sdk/zalo.ts @@ -23,6 +23,7 @@ export { export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; export { applyAccountNameToChannelSection, + applySetupAccountConfigPatch, migrateBaseNameToDefaultAccount, } from "../channels/plugins/setup-helpers.js"; export { createAccountListHelpers } from "../channels/plugins/account-helpers.js"; diff --git a/src/plugin-sdk/zalouser.ts b/src/plugin-sdk/zalouser.ts index a9d32b3010d..e5c076f8ca2 100644 --- a/src/plugin-sdk/zalouser.ts +++ b/src/plugin-sdk/zalouser.ts @@ -23,6 +23,7 @@ export { } from "../channels/plugins/onboarding/helpers.js"; export { applyAccountNameToChannelSection, + applySetupAccountConfigPatch, migrateBaseNameToDefaultAccount, } from "../channels/plugins/setup-helpers.js"; export { createAccountListHelpers } from "../channels/plugins/account-helpers.js";