From 7fc134d74e5c8528babf87cb115eb4cfce7c9122 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 17 Mar 2026 03:43:13 +0000 Subject: [PATCH] refactor(setup): share patched account adapters --- extensions/googlechat/src/setup-core.ts | 57 +++++----------------- extensions/zalo/src/setup-core.ts | 49 +++---------------- src/channels/plugins/setup-helpers.test.ts | 54 +++++++++++++++++++- src/channels/plugins/setup-helpers.ts | 45 +++++++++++++++++ 4 files changed, 119 insertions(+), 86 deletions(-) diff --git a/extensions/googlechat/src/setup-core.ts b/extensions/googlechat/src/setup-core.ts index b12d2704b2d..09980bad5cd 100644 --- a/extensions/googlechat/src/setup-core.ts +++ b/extensions/googlechat/src/setup-core.ts @@ -1,23 +1,10 @@ -import { - applyAccountNameToChannelSection, - applySetupAccountConfigPatch, - DEFAULT_ACCOUNT_ID, - migrateBaseNameToDefaultAccount, - normalizeAccountId, - type ChannelSetupAdapter, -} from "openclaw/plugin-sdk/setup"; +import { createPatchedAccountSetupAdapter } from "../../../src/channels/plugins/setup-helpers.js"; +import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; const channel = "googlechat" as const; -export const googlechatSetupAdapter: ChannelSetupAdapter = { - resolveAccountId: ({ accountId }) => normalizeAccountId(accountId), - applyAccountName: ({ cfg, accountId, name }) => - applyAccountNameToChannelSection({ - cfg, - channelKey: channel, - accountId, - name, - }), +export const googlechatSetupAdapter = createPatchedAccountSetupAdapter({ + channelKey: channel, validateInput: ({ accountId, input }) => { if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) { return "GOOGLE_CHAT_SERVICE_ACCOUNT env vars can only be used for the default account."; @@ -27,20 +14,7 @@ export const googlechatSetupAdapter: ChannelSetupAdapter = { } return null; }, - applyAccountConfig: ({ cfg, accountId, input }) => { - const namedConfig = applyAccountNameToChannelSection({ - cfg, - channelKey: channel, - accountId, - name: input.name, - }); - const next = - accountId !== DEFAULT_ACCOUNT_ID - ? migrateBaseNameToDefaultAccount({ - cfg: namedConfig, - channelKey: channel, - }) - : namedConfig; + buildPatch: (input) => { const patch = input.useEnv ? {} : input.tokenFile @@ -52,17 +26,12 @@ export const googlechatSetupAdapter: ChannelSetupAdapter = { const audience = input.audience?.trim(); const webhookPath = input.webhookPath?.trim(); const webhookUrl = input.webhookUrl?.trim(); - return applySetupAccountConfigPatch({ - cfg: next, - channelKey: channel, - accountId, - patch: { - ...patch, - ...(audienceType ? { audienceType } : {}), - ...(audience ? { audience } : {}), - ...(webhookPath ? { webhookPath } : {}), - ...(webhookUrl ? { webhookUrl } : {}), - }, - }); + return { + ...patch, + ...(audienceType ? { audienceType } : {}), + ...(audience ? { audience } : {}), + ...(webhookPath ? { webhookPath } : {}), + ...(webhookUrl ? { webhookUrl } : {}), + }; }, -}; +}); diff --git a/extensions/zalo/src/setup-core.ts b/extensions/zalo/src/setup-core.ts index fd6d09449ad..3e54c5a86dc 100644 --- a/extensions/zalo/src/setup-core.ts +++ b/extensions/zalo/src/setup-core.ts @@ -1,23 +1,10 @@ -import { - applyAccountNameToChannelSection, - applySetupAccountConfigPatch, - DEFAULT_ACCOUNT_ID, - migrateBaseNameToDefaultAccount, - normalizeAccountId, - type ChannelSetupAdapter, -} from "openclaw/plugin-sdk/setup"; +import { createPatchedAccountSetupAdapter } from "../../../src/channels/plugins/setup-helpers.js"; +import { DEFAULT_ACCOUNT_ID } from "../../../src/routing/session-key.js"; const channel = "zalo" as const; -export const zaloSetupAdapter: ChannelSetupAdapter = { - resolveAccountId: ({ accountId }) => normalizeAccountId(accountId), - applyAccountName: ({ cfg, accountId, name }) => - applyAccountNameToChannelSection({ - cfg, - channelKey: channel, - accountId, - name, - }), +export const zaloSetupAdapter = createPatchedAccountSetupAdapter({ + channelKey: channel, validateInput: ({ accountId, input }) => { if (input.useEnv && accountId !== DEFAULT_ACCOUNT_ID) { return "ZALO_BOT_TOKEN can only be used for the default account."; @@ -27,32 +14,12 @@ export const zaloSetupAdapter: ChannelSetupAdapter = { } return null; }, - applyAccountConfig: ({ cfg, accountId, input }) => { - const namedConfig = applyAccountNameToChannelSection({ - cfg, - channelKey: channel, - accountId, - name: input.name, - }); - const next = - accountId !== DEFAULT_ACCOUNT_ID - ? migrateBaseNameToDefaultAccount({ - cfg: namedConfig, - channelKey: channel, - }) - : namedConfig; - const patch = input.useEnv + buildPatch: (input) => + input.useEnv ? {} : input.tokenFile ? { tokenFile: input.tokenFile } : input.token ? { botToken: input.token } - : {}; - return applySetupAccountConfigPatch({ - cfg: next, - channelKey: channel, - accountId, - patch, - }); - }, -}; + : {}, +}); diff --git a/src/channels/plugins/setup-helpers.test.ts b/src/channels/plugins/setup-helpers.test.ts index 10069c0b9f4..d0c6b1c8a74 100644 --- a/src/channels/plugins/setup-helpers.test.ts +++ b/src/channels/plugins/setup-helpers.test.ts @@ -1,7 +1,7 @@ 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"; +import { applySetupAccountConfigPatch, createPatchedAccountSetupAdapter } from "./setup-helpers.js"; function asConfig(value: unknown): OpenClawConfig { return value as OpenClawConfig; @@ -79,3 +79,55 @@ describe("applySetupAccountConfigPatch", () => { }); }); }); + +describe("createPatchedAccountSetupAdapter", () => { + it("stores default-account patch at channel root", () => { + const adapter = createPatchedAccountSetupAdapter({ + channelKey: "zalo", + buildPatch: (input) => ({ botToken: input.token }), + }); + + const next = adapter.applyAccountConfig({ + cfg: asConfig({ channels: { zalo: { enabled: false } } }), + accountId: DEFAULT_ACCOUNT_ID, + input: { name: "Personal", token: "tok" }, + }); + + expect(next.channels?.zalo).toMatchObject({ + enabled: true, + name: "Personal", + botToken: "tok", + }); + }); + + it("migrates base name into the default account before patching a named account", () => { + const adapter = createPatchedAccountSetupAdapter({ + channelKey: "zalo", + buildPatch: (input) => ({ botToken: input.token }), + }); + + const next = adapter.applyAccountConfig({ + cfg: asConfig({ + channels: { + zalo: { + name: "Personal", + accounts: { + work: { botToken: "old" }, + }, + }, + }, + }), + accountId: "Work Team", + input: { name: "Work", token: "new" }, + }); + + expect(next.channels?.zalo).toMatchObject({ + accounts: { + default: { name: "Personal" }, + work: { botToken: "old" }, + "work-team": { enabled: true, name: "Work", botToken: "new" }, + }, + }); + expect(next.channels?.zalo).not.toHaveProperty("name"); + }); +}); diff --git a/src/channels/plugins/setup-helpers.ts b/src/channels/plugins/setup-helpers.ts index d592a56e475..31ba2c7d9c6 100644 --- a/src/channels/plugins/setup-helpers.ts +++ b/src/channels/plugins/setup-helpers.ts @@ -1,5 +1,7 @@ import type { OpenClawConfig } from "../../config/config.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js"; +import type { ChannelSetupAdapter } from "./types.adapters.js"; +import type { ChannelSetupInput } from "./types.core.js"; type ChannelSectionBase = { name?: string; @@ -134,6 +136,49 @@ export function applySetupAccountConfigPatch(params: { }); } +export function createPatchedAccountSetupAdapter(params: { + channelKey: string; + alwaysUseAccounts?: boolean; + validateInput?: ChannelSetupAdapter["validateInput"]; + buildPatch: (input: ChannelSetupInput) => Record; +}): ChannelSetupAdapter { + return { + resolveAccountId: ({ accountId }) => normalizeAccountId(accountId), + applyAccountName: ({ cfg, accountId, name }) => + applyAccountNameToChannelSection({ + cfg, + channelKey: params.channelKey, + accountId, + name, + alwaysUseAccounts: params.alwaysUseAccounts, + }), + validateInput: params.validateInput, + applyAccountConfig: ({ cfg, accountId, input }) => { + const namedConfig = applyAccountNameToChannelSection({ + cfg, + channelKey: params.channelKey, + accountId, + name: input.name, + alwaysUseAccounts: params.alwaysUseAccounts, + }); + const next = + accountId !== DEFAULT_ACCOUNT_ID + ? migrateBaseNameToDefaultAccount({ + cfg: namedConfig, + channelKey: params.channelKey, + alwaysUseAccounts: params.alwaysUseAccounts, + }) + : namedConfig; + return applySetupAccountConfigPatch({ + cfg: next, + channelKey: params.channelKey, + accountId, + patch: params.buildPatch(input), + }); + }, + }; +} + export function patchScopedAccountConfig(params: { cfg: OpenClawConfig; channelKey: string;