diff --git a/extensions/discord/src/setup-surface.test.ts b/extensions/discord/src/setup-surface.test.ts new file mode 100644 index 00000000000..b5b9e507730 --- /dev/null +++ b/extensions/discord/src/setup-surface.test.ts @@ -0,0 +1,56 @@ +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; +import { describe, expect, it } from "vitest"; +import { discordSetupWizard } from "./setup-surface.js"; + +describe("discordSetupWizard.dmPolicy", () => { + it("reads the named-account DM policy instead of the channel root", () => { + expect( + discordSetupWizard.dmPolicy?.getCurrent( + { + channels: { + discord: { + dmPolicy: "disabled", + accounts: { + alerts: { + dmPolicy: "allowlist", + token: "discord-token", + }, + }, + }, + }, + } as OpenClawConfig, + "alerts", + ), + ).toBe("allowlist"); + }); + + it("reports account-scoped config keys for named accounts", () => { + expect(discordSetupWizard.dmPolicy?.resolveConfigKeys?.({}, "alerts")).toEqual({ + policyKey: "channels.discord.accounts.alerts.dmPolicy", + allowFromKey: "channels.discord.accounts.alerts.allowFrom", + }); + }); + + it('writes open policy state to the named account and preserves inherited allowFrom with "*"', () => { + const next = discordSetupWizard.dmPolicy?.setPolicy( + { + channels: { + discord: { + allowFrom: ["123"], + accounts: { + alerts: { + token: "discord-token", + }, + }, + }, + }, + } as OpenClawConfig, + "open", + "alerts", + ); + + expect(next?.channels?.discord?.dmPolicy).toBeUndefined(); + expect(next?.channels?.discord?.accounts?.alerts?.dmPolicy).toBe("open"); + expect(next?.channels?.discord?.accounts?.alerts?.allowFrom).toEqual(["123", "*"]); + }); +}); diff --git a/extensions/slack/src/setup-surface.test.ts b/extensions/slack/src/setup-surface.test.ts index 49b67b30c80..8a279b8eef2 100644 --- a/extensions/slack/src/setup-surface.test.ts +++ b/extensions/slack/src/setup-surface.test.ts @@ -63,3 +63,58 @@ describe("slackSetupWizard.finalize", () => { ).toBe(true); }); }); + +describe("slackSetupWizard.dmPolicy", () => { + it("reads the named-account DM policy instead of the channel root", () => { + expect( + slackSetupWizard.dmPolicy?.getCurrent( + { + channels: { + slack: { + dmPolicy: "disabled", + accounts: { + alerts: { + dmPolicy: "allowlist", + botToken: "xoxb-alerts", + appToken: "xapp-alerts", + }, + }, + }, + }, + } as OpenClawConfig, + "alerts", + ), + ).toBe("allowlist"); + }); + + it("reports account-scoped config keys for named accounts", () => { + expect(slackSetupWizard.dmPolicy?.resolveConfigKeys?.({}, "alerts")).toEqual({ + policyKey: "channels.slack.accounts.alerts.dmPolicy", + allowFromKey: "channels.slack.accounts.alerts.allowFrom", + }); + }); + + it('writes open policy state to the named account and preserves inherited allowFrom with "*"', () => { + const next = slackSetupWizard.dmPolicy?.setPolicy( + { + channels: { + slack: { + allowFrom: ["U123"], + accounts: { + alerts: { + botToken: "xoxb-alerts", + appToken: "xapp-alerts", + }, + }, + }, + }, + } as OpenClawConfig, + "open", + "alerts", + ); + + expect(next?.channels?.slack?.dmPolicy).toBeUndefined(); + expect(next?.channels?.slack?.accounts?.alerts?.dmPolicy).toBe("open"); + expect(next?.channels?.slack?.accounts?.alerts?.allowFrom).toEqual(["U123", "*"]); + }); +}); diff --git a/src/channels/plugins/setup-wizard-helpers.test.ts b/src/channels/plugins/setup-wizard-helpers.test.ts index 8c9d1131e77..763c9a1332e 100644 --- a/src/channels/plugins/setup-wizard-helpers.test.ts +++ b/src/channels/plugins/setup-wizard-helpers.test.ts @@ -1509,6 +1509,55 @@ describe("createLegacyCompatChannelDmPolicy", () => { expect(next.channels?.slack?.dmPolicy).toBe("open"); expect(next.channels?.slack?.allowFrom).toEqual(["U123", "*"]); }); + + it("honors named-account dm policy state and paths", () => { + const dmPolicy = createLegacyCompatChannelDmPolicy({ + label: "Slack", + channel: "slack", + }); + + expect( + dmPolicy.getCurrent( + { + channels: { + slack: { + dmPolicy: "disabled", + accounts: { + alerts: { + dmPolicy: "allowlist", + }, + }, + }, + }, + }, + "alerts", + ), + ).toBe("allowlist"); + + expect(dmPolicy.resolveConfigKeys?.({}, "alerts")).toEqual({ + policyKey: "channels.slack.accounts.alerts.dmPolicy", + allowFromKey: "channels.slack.accounts.alerts.allowFrom", + }); + + const next = dmPolicy.setPolicy( + { + channels: { + slack: { + allowFrom: ["U123"], + accounts: { + alerts: {}, + }, + }, + }, + }, + "open", + "alerts", + ); + + expect(next.channels?.slack?.dmPolicy).toBeUndefined(); + expect(next.channels?.slack?.accounts?.alerts?.dmPolicy).toBe("open"); + expect(next.channels?.slack?.accounts?.alerts?.allowFrom).toEqual(["U123", "*"]); + }); }); describe("createTopLevelChannelGroupPolicySetter", () => { diff --git a/src/channels/plugins/setup-wizard-helpers.ts b/src/channels/plugins/setup-wizard-helpers.ts index 19cc7b5b9bb..03377895f5c 100644 --- a/src/channels/plugins/setup-wizard-helpers.ts +++ b/src/channels/plugins/setup-wizard-helpers.ts @@ -629,30 +629,92 @@ export function createLegacyCompatChannelDmPolicy(params: { channel: params.channel, policyKey: `channels.${params.channel}.dmPolicy`, allowFromKey: `channels.${params.channel}.allowFrom`, - getCurrent: (cfg) => - ( - cfg.channels?.[params.channel] as + resolveConfigKeys: (_cfg, accountId) => + accountId && accountId !== DEFAULT_ACCOUNT_ID + ? { + policyKey: `channels.${params.channel}.accounts.${accountId}.dmPolicy`, + allowFromKey: `channels.${params.channel}.accounts.${accountId}.allowFrom`, + } + : { + policyKey: `channels.${params.channel}.dmPolicy`, + allowFromKey: `channels.${params.channel}.allowFrom`, + }, + getCurrent: (cfg, accountId) => { + const channelConfig = + (cfg.channels?.[params.channel] as | { dmPolicy?: DmPolicy; dm?: { policy?: DmPolicy }; + accounts?: Record; } - | undefined - )?.dmPolicy ?? - ( - cfg.channels?.[params.channel] as - | { - dmPolicy?: DmPolicy; - dm?: { policy?: DmPolicy }; - } - | undefined - )?.dm?.policy ?? - "pairing", - setPolicy: (cfg, policy) => - setLegacyChannelDmPolicyWithAllowFrom({ - cfg, - channel: params.channel, - dmPolicy: policy, - }), + | undefined) ?? {}; + const accountConfig = + accountId && accountId !== DEFAULT_ACCOUNT_ID ? channelConfig.accounts?.[accountId] : undefined; + return accountConfig?.dmPolicy ?? + accountConfig?.dm?.policy ?? + channelConfig.dmPolicy ?? + channelConfig.dm?.policy ?? + "pairing"; + }, + setPolicy: (cfg, policy, accountId) => + accountId && accountId !== DEFAULT_ACCOUNT_ID + ? patchChannelConfigForAccount({ + cfg, + channel: params.channel, + accountId, + patch: { + dmPolicy: policy, + ...(policy === "open" + ? { + allowFrom: addWildcardAllowFrom( + ( + cfg.channels?.[params.channel] as + | { + accounts?: Record< + string, + { + allowFrom?: Array; + dm?: { allowFrom?: Array }; + } + >; + } + | undefined + )?.accounts?.[accountId]?.allowFrom ?? + ( + cfg.channels?.[params.channel] as + | { + allowFrom?: Array; + dm?: { allowFrom?: Array }; + } + | undefined + )?.allowFrom ?? + ( + cfg.channels?.[params.channel] as + | { + accounts?: Record< + string, + { dm?: { allowFrom?: Array } } + >; + } + | undefined + )?.accounts?.[accountId]?.dm?.allowFrom ?? + ( + cfg.channels?.[params.channel] as + | { + dm?: { allowFrom?: Array }; + } + | undefined + )?.dm?.allowFrom, + ), + } + : {}), + }, + }) + : setLegacyChannelDmPolicyWithAllowFrom({ + cfg, + channel: params.channel, + dmPolicy: policy, + }), ...(params.promptAllowFrom ? { promptAllowFrom: params.promptAllowFrom } : {}), }; }