fix: honor bluebubbles setup dm policy accounts

This commit is contained in:
Tak Hoffman 2026-04-03 10:39:27 -05:00
parent 4b93000d11
commit 625201bddc
No known key found for this signature in database
3 changed files with 100 additions and 8 deletions

View File

@ -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(

View File

@ -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", () => {

View File

@ -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,
};