refactor: share scoped account config patching

This commit is contained in:
Peter Steinberger
2026-03-10 20:22:06 +00:00
parent b517dc089a
commit 00170f8e1a
14 changed files with 118 additions and 253 deletions

View File

@@ -6,10 +6,10 @@ import type {
WizardPrompter, WizardPrompter,
} from "openclaw/plugin-sdk/bluebubbles"; } from "openclaw/plugin-sdk/bluebubbles";
import { import {
DEFAULT_ACCOUNT_ID,
formatDocsLink, formatDocsLink,
mergeAllowFromEntries, mergeAllowFromEntries,
normalizeAccountId, normalizeAccountId,
patchScopedAccountConfig,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom,
} from "openclaw/plugin-sdk/bluebubbles"; } from "openclaw/plugin-sdk/bluebubbles";
@@ -38,34 +38,14 @@ function setBlueBubblesAllowFrom(
accountId: string, accountId: string,
allowFrom: string[], allowFrom: string[],
): OpenClawConfig { ): OpenClawConfig {
if (accountId === DEFAULT_ACCOUNT_ID) { return patchScopedAccountConfig({
return { cfg,
...cfg, channelKey: channel,
channels: { accountId,
...cfg.channels, patch: { allowFrom },
bluebubbles: { ensureChannelEnabled: false,
...cfg.channels?.bluebubbles, ensureAccountEnabled: false,
allowFrom, });
},
},
};
}
return {
...cfg,
channels: {
...cfg.channels,
bluebubbles: {
...cfg.channels?.bluebubbles,
accounts: {
...cfg.channels?.bluebubbles?.accounts,
[accountId]: {
...cfg.channels?.bluebubbles?.accounts?.[accountId],
allowFrom,
},
},
},
},
};
} }
function parseBlueBubblesAllowFromInput(raw: string): string[] { function parseBlueBubblesAllowFromInput(raw: string): string[] {

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig, DmPolicy } from "openclaw/plugin-sdk/googlechat"; import type { OpenClawConfig, DmPolicy } from "openclaw/plugin-sdk/googlechat";
import { import {
applySetupAccountConfigPatch,
addWildcardAllowFrom, addWildcardAllowFrom,
formatDocsLink, formatDocsLink,
mergeAllowFromEntries, mergeAllowFromEntries,
@@ -8,7 +9,6 @@ import {
type ChannelOnboardingAdapter, type ChannelOnboardingAdapter,
type ChannelOnboardingDmPolicy, type ChannelOnboardingDmPolicy,
type WizardPrompter, type WizardPrompter,
DEFAULT_ACCOUNT_ID,
migrateBaseNameToDefaultAccount, migrateBaseNameToDefaultAccount,
} from "openclaw/plugin-sdk/googlechat"; } from "openclaw/plugin-sdk/googlechat";
import { import {
@@ -83,45 +83,6 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
promptAllowFrom, promptAllowFrom,
}; };
function applyAccountConfig(params: {
cfg: OpenClawConfig;
accountId: string;
patch: Record<string, unknown>;
}): OpenClawConfig {
const { cfg, accountId, patch } = params;
if (accountId === DEFAULT_ACCOUNT_ID) {
return {
...cfg,
channels: {
...cfg.channels,
googlechat: {
...cfg.channels?.["googlechat"],
enabled: true,
...patch,
},
},
};
}
return {
...cfg,
channels: {
...cfg.channels,
googlechat: {
...cfg.channels?.["googlechat"],
enabled: true,
accounts: {
...cfg.channels?.["googlechat"]?.accounts,
[accountId]: {
...cfg.channels?.["googlechat"]?.accounts?.[accountId],
enabled: true,
...patch,
},
},
},
},
};
}
async function promptCredentials(params: { async function promptCredentials(params: {
cfg: OpenClawConfig; cfg: OpenClawConfig;
prompter: WizardPrompter; prompter: WizardPrompter;
@@ -137,7 +98,7 @@ async function promptCredentials(params: {
initialValue: true, initialValue: true,
}); });
if (useEnv) { if (useEnv) {
return applyAccountConfig({ cfg, accountId, patch: {} }); return applySetupAccountConfigPatch({ cfg, channelKey: channel, accountId, patch: {} });
} }
} }
@@ -156,8 +117,9 @@ async function promptCredentials(params: {
placeholder: "/path/to/service-account.json", placeholder: "/path/to/service-account.json",
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
}); });
return applyAccountConfig({ return applySetupAccountConfigPatch({
cfg, cfg,
channelKey: channel,
accountId, accountId,
patch: { serviceAccountFile: String(path).trim() }, patch: { serviceAccountFile: String(path).trim() },
}); });
@@ -168,8 +130,9 @@ async function promptCredentials(params: {
placeholder: '{"type":"service_account", ... }', placeholder: '{"type":"service_account", ... }',
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
}); });
return applyAccountConfig({ return applySetupAccountConfigPatch({
cfg, cfg,
channelKey: channel,
accountId, accountId,
patch: { serviceAccount: String(json).trim() }, patch: { serviceAccount: String(json).trim() },
}); });
@@ -200,8 +163,9 @@ async function promptAudience(params: {
initialValue: currentAudience || undefined, initialValue: currentAudience || undefined,
validate: (value) => (String(value ?? "").trim() ? undefined : "Required"), validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
}); });
return applyAccountConfig({ return applySetupAccountConfigPatch({
cfg: params.cfg, cfg: params.cfg,
channelKey: channel,
accountId: params.accountId, accountId: params.accountId,
patch: { audienceType, audience: String(audience).trim() }, patch: { audienceType, audience: String(audience).trim() },
}); });

View File

@@ -1,6 +1,7 @@
import { import {
DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID,
formatDocsLink, formatDocsLink,
patchScopedAccountConfig,
promptChannelAccessConfig, promptChannelAccessConfig,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
setTopLevelChannelAllowFrom, setTopLevelChannelAllowFrom,
@@ -59,35 +60,14 @@ function updateIrcAccountConfig(
accountId: string, accountId: string,
patch: Partial<IrcAccountConfig>, patch: Partial<IrcAccountConfig>,
): CoreConfig { ): CoreConfig {
const current = cfg.channels?.irc ?? {}; return patchScopedAccountConfig({
if (accountId === DEFAULT_ACCOUNT_ID) { cfg,
return { channelKey: channel,
...cfg, accountId,
channels: { patch,
...cfg.channels, ensureChannelEnabled: false,
irc: { ensureAccountEnabled: false,
...current, }) as CoreConfig;
...patch,
},
},
};
}
return {
...cfg,
channels: {
...cfg.channels,
irc: {
...current,
accounts: {
...current.accounts,
[accountId]: {
...current.accounts?.[accountId],
...patch,
},
},
},
},
};
} }
function setIrcDmPolicy(cfg: CoreConfig, dmPolicy: DmPolicy): CoreConfig { function setIrcDmPolicy(cfg: CoreConfig, dmPolicy: DmPolicy): CoreConfig {

View File

@@ -4,6 +4,7 @@ import {
hasConfiguredSecretInput, hasConfiguredSecretInput,
mapAllowFromEntries, mapAllowFromEntries,
mergeAllowFromEntries, mergeAllowFromEntries,
patchScopedAccountConfig,
promptSingleChannelSecretInput, promptSingleChannelSecretInput,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID,
@@ -39,38 +40,12 @@ function setNextcloudTalkAccountConfig(
accountId: string, accountId: string,
updates: Record<string, unknown>, updates: Record<string, unknown>,
): CoreConfig { ): CoreConfig {
if (accountId === DEFAULT_ACCOUNT_ID) { return patchScopedAccountConfig({
return { cfg,
...cfg, channelKey: channel,
channels: { accountId,
...cfg.channels, patch: updates,
"nextcloud-talk": { }) as CoreConfig;
...cfg.channels?.["nextcloud-talk"],
enabled: true,
...updates,
},
},
};
}
return {
...cfg,
channels: {
...cfg.channels,
"nextcloud-talk": {
...cfg.channels?.["nextcloud-talk"],
enabled: true,
accounts: {
...cfg.channels?.["nextcloud-talk"]?.accounts,
[accountId]: {
...cfg.channels?.["nextcloud-talk"]?.accounts?.[accountId],
enabled: cfg.channels?.["nextcloud-talk"]?.accounts?.[accountId]?.enabled ?? true,
...updates,
},
},
},
},
};
} }
async function noteNextcloudTalkSecretHelp(prompter: WizardPrompter): Promise<void> { async function noteNextcloudTalkSecretHelp(prompter: WizardPrompter): Promise<void> {

View File

@@ -1,6 +1,7 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/tlon"; import type { OpenClawConfig } from "openclaw/plugin-sdk/tlon";
import { import {
formatDocsLink, formatDocsLink,
patchScopedAccountConfig,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
DEFAULT_ACCOUNT_ID, DEFAULT_ACCOUNT_ID,
type ChannelOnboardingAdapter, type ChannelOnboardingAdapter,
@@ -32,46 +33,30 @@ function applyAccountConfig(params: {
}; };
}): OpenClawConfig { }): OpenClawConfig {
const { cfg, accountId, input } = params; const { cfg, accountId, input } = params;
const useDefault = accountId === DEFAULT_ACCOUNT_ID;
const base = cfg.channels?.tlon ?? {};
const nextValues = { const nextValues = {
enabled: true, enabled: true,
...(input.name ? { name: input.name } : {}), ...(input.name ? { name: input.name } : {}),
...buildTlonAccountFields(input), ...buildTlonAccountFields(input),
}; };
if (accountId === DEFAULT_ACCOUNT_ID) {
if (useDefault) { return patchScopedAccountConfig({
return { cfg,
...cfg, channelKey: channel,
channels: { accountId,
...cfg.channels, patch: nextValues,
tlon: { ensureChannelEnabled: false,
...base, ensureAccountEnabled: false,
...nextValues, });
},
},
};
} }
return patchScopedAccountConfig({
return { cfg,
...cfg, channelKey: channel,
channels: { accountId,
...cfg.channels, patch: { enabled: cfg.channels?.tlon?.enabled ?? true },
tlon: { accountPatch: nextValues,
...base, ensureChannelEnabled: false,
enabled: base.enabled ?? true, ensureAccountEnabled: false,
accounts: { });
...(base as { accounts?: Record<string, unknown> }).accounts,
[accountId]: {
...(base as { accounts?: Record<string, Record<string, unknown>> }).accounts?.[
accountId
],
...nextValues,
},
},
},
},
};
} }
async function noteTlonHelp(prompter: WizardPrompter): Promise<void> { async function noteTlonHelp(prompter: WizardPrompter): Promise<void> {

View File

@@ -5,10 +5,10 @@ import type {
WizardPrompter, WizardPrompter,
} from "openclaw/plugin-sdk/zalouser"; } from "openclaw/plugin-sdk/zalouser";
import { import {
DEFAULT_ACCOUNT_ID,
formatResolvedUnresolvedNote, formatResolvedUnresolvedNote,
mergeAllowFromEntries, mergeAllowFromEntries,
normalizeAccountId, normalizeAccountId,
patchScopedAccountConfig,
promptChannelAccessConfig, promptChannelAccessConfig,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom,
@@ -36,37 +36,13 @@ function setZalouserAccountScopedConfig(
defaultPatch: Record<string, unknown>, defaultPatch: Record<string, unknown>,
accountPatch: Record<string, unknown> = defaultPatch, accountPatch: Record<string, unknown> = defaultPatch,
): OpenClawConfig { ): OpenClawConfig {
if (accountId === DEFAULT_ACCOUNT_ID) { return patchScopedAccountConfig({
return { cfg,
...cfg, channelKey: channel,
channels: { accountId,
...cfg.channels, patch: defaultPatch,
zalouser: { accountPatch,
...cfg.channels?.zalouser, }) as OpenClawConfig;
enabled: true,
...defaultPatch,
},
},
} as OpenClawConfig;
}
return {
...cfg,
channels: {
...cfg.channels,
zalouser: {
...cfg.channels?.zalouser,
enabled: true,
accounts: {
...cfg.channels?.zalouser?.accounts,
[accountId]: {
...cfg.channels?.zalouser?.accounts?.[accountId],
enabled: cfg.channels?.zalouser?.accounts?.[accountId]?.enabled ?? true,
...accountPatch,
},
},
},
},
} as OpenClawConfig;
} }
function setZalouserDmPolicy( function setZalouserDmPolicy(

View File

@@ -9,7 +9,10 @@ import { promptAccountId as promptAccountIdSdk } from "../../../plugin-sdk/onboa
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";
import type { WizardPrompter } from "../../../wizard/prompts.js"; import type { WizardPrompter } from "../../../wizard/prompts.js";
import type { PromptAccountId, PromptAccountIdParams } from "../onboarding-types.js"; import type { PromptAccountId, PromptAccountIdParams } from "../onboarding-types.js";
import { moveSingleAccountChannelSectionToDefaultAccount } from "../setup-helpers.js"; import {
moveSingleAccountChannelSectionToDefaultAccount,
patchScopedAccountConfig,
} from "../setup-helpers.js";
export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => { export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => {
return await promptAccountIdSdk(params); return await promptAccountIdSdk(params);
@@ -364,50 +367,14 @@ function patchConfigForScopedAccount(params: {
cfg, cfg,
channelKey: channel, channelKey: channel,
}); });
const channelConfig = return patchScopedAccountConfig({
(seededCfg.channels?.[channel] as Record<string, unknown> | undefined) ?? {}; cfg: seededCfg,
channelKey: channel,
if (accountId === DEFAULT_ACCOUNT_ID) { accountId,
return { patch,
...seededCfg, ensureChannelEnabled: ensureEnabled,
channels: { ensureAccountEnabled: ensureEnabled,
...seededCfg.channels, });
[channel]: {
...channelConfig,
...(ensureEnabled ? { enabled: true } : {}),
...patch,
},
},
};
}
const accounts =
(channelConfig.accounts as Record<string, Record<string, unknown>> | undefined) ?? {};
const existingAccount = accounts[accountId] ?? {};
return {
...seededCfg,
channels: {
...seededCfg.channels,
[channel]: {
...channelConfig,
...(ensureEnabled ? { enabled: true } : {}),
accounts: {
...accounts,
[accountId]: {
...existingAccount,
...(ensureEnabled
? {
enabled:
typeof existingAccount.enabled === "boolean" ? existingAccount.enabled : true,
}
: {}),
...patch,
},
},
},
},
};
} }
export function patchChannelConfigForAccount(params: { export function patchChannelConfigForAccount(params: {

View File

@@ -125,6 +125,23 @@ export function applySetupAccountConfigPatch(params: {
channelKey: string; channelKey: string;
accountId: string; accountId: string;
patch: Record<string, unknown>; patch: Record<string, unknown>;
}): OpenClawConfig {
return patchScopedAccountConfig({
cfg: params.cfg,
channelKey: params.channelKey,
accountId: params.accountId,
patch: params.patch,
});
}
export function patchScopedAccountConfig(params: {
cfg: OpenClawConfig;
channelKey: string;
accountId: string;
patch: Record<string, unknown>;
accountPatch?: Record<string, unknown>;
ensureChannelEnabled?: boolean;
ensureAccountEnabled?: boolean;
}): OpenClawConfig { }): OpenClawConfig {
const accountId = normalizeAccountId(params.accountId); const accountId = normalizeAccountId(params.accountId);
const channels = params.cfg.channels as Record<string, unknown> | undefined; const channels = params.cfg.channels as Record<string, unknown> | undefined;
@@ -135,6 +152,10 @@ export function applySetupAccountConfigPatch(params: {
accounts?: Record<string, Record<string, unknown>>; accounts?: Record<string, Record<string, unknown>>;
}) })
: undefined; : undefined;
const ensureChannelEnabled = params.ensureChannelEnabled ?? true;
const ensureAccountEnabled = params.ensureAccountEnabled ?? ensureChannelEnabled;
const patch = params.patch;
const accountPatch = params.accountPatch ?? patch;
if (accountId === DEFAULT_ACCOUNT_ID) { if (accountId === DEFAULT_ACCOUNT_ID) {
return { return {
...params.cfg, ...params.cfg,
@@ -142,27 +163,33 @@ export function applySetupAccountConfigPatch(params: {
...params.cfg.channels, ...params.cfg.channels,
[params.channelKey]: { [params.channelKey]: {
...base, ...base,
enabled: true, ...(ensureChannelEnabled ? { enabled: true } : {}),
...params.patch, ...patch,
}, },
}, },
} as OpenClawConfig; } as OpenClawConfig;
} }
const accounts = base?.accounts ?? {}; const accounts = base?.accounts ?? {};
const existingAccount = accounts[accountId] ?? {};
return { return {
...params.cfg, ...params.cfg,
channels: { channels: {
...params.cfg.channels, ...params.cfg.channels,
[params.channelKey]: { [params.channelKey]: {
...base, ...base,
enabled: true, ...(ensureChannelEnabled ? { enabled: true } : {}),
accounts: { accounts: {
...accounts, ...accounts,
[accountId]: { [accountId]: {
...accounts[accountId], ...existingAccount,
enabled: true, ...(ensureAccountEnabled
...params.patch, ? {
enabled:
typeof existingAccount.enabled === "boolean" ? existingAccount.enabled : true,
}
: {}),
...accountPatch,
}, },
}, },
}, },

View File

@@ -46,6 +46,7 @@ export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js
export { export {
applyAccountNameToChannelSection, applyAccountNameToChannelSection,
migrateBaseNameToDefaultAccount, migrateBaseNameToDefaultAccount,
patchScopedAccountConfig,
} from "../channels/plugins/setup-helpers.js"; } from "../channels/plugins/setup-helpers.js";
export { createAccountListHelpers } from "../channels/plugins/account-helpers.js"; export { createAccountListHelpers } from "../channels/plugins/account-helpers.js";
export { collectBlueBubblesStatusIssues } from "../channels/plugins/status-issues/bluebubbles.js"; export { collectBlueBubblesStatusIssues } from "../channels/plugins/status-issues/bluebubbles.js";

View File

@@ -546,7 +546,9 @@ export {
} from "../channels/plugins/config-helpers.js"; } from "../channels/plugins/config-helpers.js";
export { export {
applyAccountNameToChannelSection, applyAccountNameToChannelSection,
applySetupAccountConfigPatch,
migrateBaseNameToDefaultAccount, migrateBaseNameToDefaultAccount,
patchScopedAccountConfig,
} from "../channels/plugins/setup-helpers.js"; } from "../channels/plugins/setup-helpers.js";
export { export {
buildOpenGroupPolicyConfigureRouteAllowlistWarning, buildOpenGroupPolicyConfigureRouteAllowlistWarning,

View File

@@ -23,6 +23,7 @@ export {
setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom,
} from "../channels/plugins/onboarding/helpers.js"; } from "../channels/plugins/onboarding/helpers.js";
export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js";
export { patchScopedAccountConfig } from "../channels/plugins/setup-helpers.js";
export type { BaseProbeResult } from "../channels/plugins/types.js"; export type { BaseProbeResult } from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";
export { getChatChannelMeta } from "../channels/registry.js"; export { getChatChannelMeta } from "../channels/registry.js";

View File

@@ -30,7 +30,10 @@ export {
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
setTopLevelChannelDmPolicyWithAllowFrom, setTopLevelChannelDmPolicyWithAllowFrom,
} from "../channels/plugins/onboarding/helpers.js"; } from "../channels/plugins/onboarding/helpers.js";
export { applyAccountNameToChannelSection } from "../channels/plugins/setup-helpers.js"; export {
applyAccountNameToChannelSection,
patchScopedAccountConfig,
} from "../channels/plugins/setup-helpers.js";
export { createAccountListHelpers } from "../channels/plugins/account-helpers.js"; export { createAccountListHelpers } from "../channels/plugins/account-helpers.js";
export type { ChannelGroupContext, ChannelSetupInput } from "../channels/plugins/types.js"; export type { ChannelGroupContext, ChannelSetupInput } from "../channels/plugins/types.js";
export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; export type { ChannelPlugin } from "../channels/plugins/types.plugin.js";

View File

@@ -8,7 +8,10 @@ export {
promptAccountId, promptAccountId,
resolveAccountIdForConfigure, resolveAccountIdForConfigure,
} from "../channels/plugins/onboarding/helpers.js"; } from "../channels/plugins/onboarding/helpers.js";
export { applyAccountNameToChannelSection } from "../channels/plugins/setup-helpers.js"; export {
applyAccountNameToChannelSection,
patchScopedAccountConfig,
} from "../channels/plugins/setup-helpers.js";
export type { export type {
ChannelAccountSnapshot, ChannelAccountSnapshot,
ChannelOutboundAdapter, ChannelOutboundAdapter,

View File

@@ -27,6 +27,7 @@ export {
applyAccountNameToChannelSection, applyAccountNameToChannelSection,
applySetupAccountConfigPatch, applySetupAccountConfigPatch,
migrateBaseNameToDefaultAccount, migrateBaseNameToDefaultAccount,
patchScopedAccountConfig,
} from "../channels/plugins/setup-helpers.js"; } from "../channels/plugins/setup-helpers.js";
export { createAccountListHelpers } from "../channels/plugins/account-helpers.js"; export { createAccountListHelpers } from "../channels/plugins/account-helpers.js";
export type { export type {