From 625201bddcf089847d4a1f247be5df316828f623 Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Fri, 3 Apr 2026 10:39:27 -0500 Subject: [PATCH] fix: honor bluebubbles setup dm policy accounts --- extensions/bluebubbles/src/setup-core.ts | 29 +++++++-- .../bluebubbles/src/setup-surface.test.ts | 62 +++++++++++++++++++ extensions/bluebubbles/src/setup-surface.ts | 17 ++++- 3 files changed, 100 insertions(+), 8 deletions(-) diff --git a/extensions/bluebubbles/src/setup-core.ts b/extensions/bluebubbles/src/setup-core.ts index abff6d04252..a8da7d1f1f5 100644 --- a/extensions/bluebubbles/src/setup-core.ts +++ b/extensions/bluebubbles/src/setup-core.ts @@ -1,6 +1,6 @@ import { + addWildcardAllowFrom, createSetupInputPresenceValidator, - createTopLevelChannelDmPolicySetter, normalizeAccountId, patchScopedAccountConfig, prepareScopedSetupConfig, @@ -11,12 +11,29 @@ import { import { applyBlueBubblesConnectionConfig } from "./config-apply.js"; const channel = "bluebubbles" as const; -const setBlueBubblesTopLevelDmPolicy = createTopLevelChannelDmPolicySetter({ - channel, -}); -export function setBlueBubblesDmPolicy(cfg: OpenClawConfig, dmPolicy: DmPolicy): OpenClawConfig { - return setBlueBubblesTopLevelDmPolicy(cfg, dmPolicy); +export function setBlueBubblesDmPolicy( + cfg: OpenClawConfig, + accountId: string, + dmPolicy: DmPolicy, +): OpenClawConfig { + const resolvedAccountId = normalizeAccountId(accountId); + const existingAllowFrom = + resolvedAccountId === "default" + ? cfg.channels?.bluebubbles?.allowFrom + : cfg.channels?.bluebubbles?.accounts?.[resolvedAccountId]?.allowFrom ?? + cfg.channels?.bluebubbles?.allowFrom; + return patchScopedAccountConfig({ + cfg, + channelKey: channel, + accountId: resolvedAccountId, + patch: { + dmPolicy, + ...(dmPolicy === "open" ? { allowFrom: addWildcardAllowFrom(existingAllowFrom) } : {}), + }, + ensureChannelEnabled: false, + ensureAccountEnabled: false, + }); } export function setBlueBubblesAllowFrom( diff --git a/extensions/bluebubbles/src/setup-surface.test.ts b/extensions/bluebubbles/src/setup-surface.test.ts index ad8ca4ba30b..10162929524 100644 --- a/extensions/bluebubbles/src/setup-surface.test.ts +++ b/extensions/bluebubbles/src/setup-surface.test.ts @@ -151,6 +151,68 @@ describe("bluebubbles setup surface", () => { expect(next?.channels?.bluebubbles?.enabled).toBe(false); }); + + it("reads the named-account DM policy instead of the channel root", async () => { + const { blueBubblesSetupWizard } = await import("./setup-surface.js"); + + expect( + blueBubblesSetupWizard.dmPolicy?.getCurrent( + { + channels: { + bluebubbles: { + dmPolicy: "disabled", + accounts: { + work: { + serverUrl: "http://localhost:1234", + password: "secret", + dmPolicy: "allowlist", + }, + }, + }, + }, + }, + "work", + ), + ).toBe("allowlist"); + }); + + it("reports account-scoped config keys for named accounts", async () => { + const { blueBubblesSetupWizard } = await import("./setup-surface.js"); + + expect(blueBubblesSetupWizard.dmPolicy?.resolveConfigKeys?.({}, "work")).toEqual({ + policyKey: "channels.bluebubbles.accounts.work.dmPolicy", + allowFromKey: "channels.bluebubbles.accounts.work.allowFrom", + }); + }); + + it('writes open policy state to the named account and preserves inherited allowFrom with "*"', async () => { + const { blueBubblesSetupWizard } = await import("./setup-surface.js"); + + const next = blueBubblesSetupWizard.dmPolicy?.setPolicy( + { + channels: { + bluebubbles: { + allowFrom: ["user@example.com"], + accounts: { + work: { + serverUrl: "http://localhost:1234", + password: "secret", + }, + }, + }, + }, + }, + "open", + "work", + ); + + expect(next?.channels?.bluebubbles?.dmPolicy).toBeUndefined(); + expect(next?.channels?.bluebubbles?.accounts?.work?.dmPolicy).toBe("open"); + expect(next?.channels?.bluebubbles?.accounts?.work?.allowFrom).toEqual([ + "user@example.com", + "*", + ]); + }); }); describe("resolveBlueBubblesAccount", () => { diff --git a/extensions/bluebubbles/src/setup-surface.ts b/extensions/bluebubbles/src/setup-surface.ts index f086331c284..61ef67e1d44 100644 --- a/extensions/bluebubbles/src/setup-surface.ts +++ b/extensions/bluebubbles/src/setup-surface.ts @@ -135,8 +135,21 @@ const dmPolicy: ChannelSetupDmPolicy = { channel, policyKey: "channels.bluebubbles.dmPolicy", allowFromKey: "channels.bluebubbles.allowFrom", - getCurrent: (cfg) => cfg.channels?.bluebubbles?.dmPolicy ?? "pairing", - setPolicy: (cfg, policy) => setBlueBubblesDmPolicy(cfg, policy), + resolveConfigKeys: (_cfg, accountId) => + accountId && accountId !== DEFAULT_ACCOUNT_ID + ? { + policyKey: `channels.bluebubbles.accounts.${accountId}.dmPolicy`, + allowFromKey: `channels.bluebubbles.accounts.${accountId}.allowFrom`, + } + : { + policyKey: "channels.bluebubbles.dmPolicy", + allowFromKey: "channels.bluebubbles.allowFrom", + }, + getCurrent: (cfg, accountId) => + resolveBlueBubblesAccount({ cfg, accountId: accountId ?? DEFAULT_ACCOUNT_ID }).config + .dmPolicy ?? "pairing", + setPolicy: (cfg, policy, accountId) => + setBlueBubblesDmPolicy(cfg, accountId ?? DEFAULT_ACCOUNT_ID, policy), promptAllowFrom: promptBlueBubblesAllowFrom, };