diff --git a/extensions/discord/src/accounts.ts b/extensions/discord/src/accounts.ts index a9890d78075..fe206dd971a 100644 --- a/extensions/discord/src/accounts.ts +++ b/extensions/discord/src/accounts.ts @@ -5,6 +5,7 @@ import { } from "openclaw/plugin-sdk/account-helpers"; import { normalizeAccountId } from "openclaw/plugin-sdk/account-id"; import { + mapAllowFromEntries, normalizeChannelDmPolicy, resolveChannelDmAllowFrom, resolveChannelDmPolicy, @@ -58,10 +59,11 @@ export function resolveDiscordAccountAllowFrom(params: { const accountConfig = resolveDiscordAccountConfig(params.cfg, accountId); const rootConfig = params.cfg.channels?.discord as DiscordAccountConfig | undefined; - return resolveChannelDmAllowFrom({ + const allowFrom = resolveChannelDmAllowFrom({ account: accountConfig as Record | undefined, parent: rootConfig as Record | undefined, - }) as string[] | undefined; + }); + return allowFrom ? mapAllowFromEntries(allowFrom) : undefined; } export function resolveDiscordAccountDmPolicy(params: { diff --git a/extensions/discord/src/shared.test.ts b/extensions/discord/src/shared.test.ts index 5322119969b..9b2121f4ef8 100644 --- a/extensions/discord/src/shared.test.ts +++ b/extensions/discord/src/shared.test.ts @@ -110,4 +110,22 @@ describe("discordConfigAdapter", () => { "account-legacy", ]); }); + + it("coerces numeric allowFrom entries at the config boundary", () => { + const cfg = { + channels: { + discord: { + accounts: { + default: { + allowFrom: [123456789], + }, + }, + }, + }, + } as unknown as OpenClawConfig; + + expect(discordConfigAdapter.resolveAllowFrom?.({ cfg, accountId: "default" })).toEqual([ + "123456789", + ]); + }); }); diff --git a/extensions/slack/src/accounts.test.ts b/extensions/slack/src/accounts.test.ts index bb809ecc627..fa52018e8ec 100644 --- a/extensions/slack/src/accounts.test.ts +++ b/extensions/slack/src/accounts.test.ts @@ -1,6 +1,10 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types"; import { describe, expect, it } from "vitest"; -import { resolveSlackAccount, resolveSlackAccountAllowFrom } from "./accounts.js"; +import { + resolveSlackAccount, + resolveSlackAccountAllowFrom, + resolveSlackAccountDmPolicy, +} from "./accounts.js"; describe("resolveSlackAccount allowFrom precedence", () => { it("uses configured defaultAccount when accountId is omitted", () => { @@ -126,6 +130,43 @@ describe("resolveSlackAccount allowFrom precedence", () => { expect(resolveSlackAccountAllowFrom({ cfg, accountId: "work" })).toEqual(["account-legacy"]); }); + + it("coerces numeric allowFrom entries at the config boundary", () => { + const cfg = { + channels: { + slack: { + accounts: { + work: { + botToken: "xoxb-work", + appToken: "xapp-work", + allowFrom: [12345], + }, + }, + }, + }, + } as unknown as OpenClawConfig; + + expect(resolveSlackAccountAllowFrom({ cfg, accountId: "work" })).toEqual(["12345"]); + }); + + it("resolves account legacy dm policy before inherited root policy", () => { + const cfg = { + channels: { + slack: { + dmPolicy: "open", + accounts: { + work: { + botToken: "xoxb-work", + appToken: "xapp-work", + dm: { policy: "allowlist" }, + }, + }, + }, + }, + } satisfies OpenClawConfig; + + expect(resolveSlackAccountDmPolicy({ cfg, accountId: "work" })).toBe("allowlist"); + }); }); describe("resolveSlackAccount active secret surfaces", () => { diff --git a/extensions/slack/src/accounts.ts b/extensions/slack/src/accounts.ts index c03842607ff..cf146a1ab43 100644 --- a/extensions/slack/src/accounts.ts +++ b/extensions/slack/src/accounts.ts @@ -6,6 +6,7 @@ import { type OpenClawConfig, } from "openclaw/plugin-sdk/account-resolution"; import { + mapAllowFromEntries, normalizeChannelDmPolicy, resolveChannelDmAllowFrom, resolveChannelDmPolicy, @@ -57,10 +58,11 @@ export function resolveSlackAccountAllowFrom(params: { ); const accountConfig = params.cfg.channels?.slack?.accounts?.[accountId]; const rootConfig = params.cfg.channels?.slack as SlackAccountConfig | undefined; - return resolveChannelDmAllowFrom({ + const allowFrom = resolveChannelDmAllowFrom({ account: accountConfig as Record | undefined, parent: rootConfig as Record | undefined, - }) as string[] | undefined; + }); + return allowFrom ? mapAllowFromEntries(allowFrom) : undefined; } export function resolveSlackAccountDmPolicy(params: { diff --git a/extensions/slack/src/monitor/provider.ts b/extensions/slack/src/monitor/provider.ts index 5327feebbe8..752862821fe 100644 --- a/extensions/slack/src/monitor/provider.ts +++ b/extensions/slack/src/monitor/provider.ts @@ -22,7 +22,11 @@ import { import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input"; import { normalizeStringEntries } from "openclaw/plugin-sdk/text-runtime"; import { installRequestBodyLimitGuard } from "openclaw/plugin-sdk/webhook-request-guards"; -import { resolveSlackAccount, resolveSlackAccountAllowFrom } from "../accounts.js"; +import { + resolveSlackAccount, + resolveSlackAccountAllowFrom, + resolveSlackAccountDmPolicy, +} from "../accounts.js"; import { resolveSlackWebClientOptions } from "../client-options.js"; import { isSlackExecApprovalClientEnabled } from "../exec-approvals.js"; import { normalizeSlackWebhookPath, registerSlackHttpHandler } from "../http/index.js"; @@ -148,7 +152,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) { const dmConfig = slackCfg.dm; const dmEnabled = dmConfig?.enabled ?? true; - const dmPolicy = slackCfg.dmPolicy ?? dmConfig?.policy ?? "pairing"; + const dmPolicy = resolveSlackAccountDmPolicy({ cfg, accountId: account.accountId }) ?? "pairing"; let allowFrom = resolveSlackAccountAllowFrom({ cfg, accountId: account.accountId }); const groupDmEnabled = dmConfig?.groupEnabled ?? false; const groupDmChannels = dmConfig?.groupChannels;