From 1b7301051b4ee0c0237d4712cdcd4ce415b052bd Mon Sep 17 00:00:00 2001 From: Shadow Date: Mon, 16 Feb 2026 12:22:58 -0600 Subject: [PATCH] Config: require Discord ID strings (#18220) --- CHANGELOG.md | 1 + src/channels/plugins/onboarding/discord.ts | 13 +- src/commands/doctor-config-flow.e2e.test.ts | 109 +++++++++++ src/commands/doctor-config-flow.ts | 177 ++++++++++++++++++ src/config/config.discord.test.ts | 30 ++- src/config/types.discord.ts | 16 +- src/config/zod-schema.providers-core.ts | 23 ++- src/discord/monitor/agent-components.ts | 2 +- src/discord/monitor/allow-list.ts | 33 ++-- src/discord/monitor/exec-approvals.ts | 2 +- .../message-handler.preflight.types.ts | 4 +- src/discord/monitor/provider.ts | 4 +- 12 files changed, 371 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4675e2729d..52b2b8145c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai - Security/Sessions: create new session transcript JSONL files with user-only (`0o600`) permissions and extend `openclaw security audit --fix` to remediate existing transcript file permissions. - Infra/Fetch: ensure foreign abort-signal listener cleanup never masks original fetch successes/failures, while still preventing detached-finally unhandled rejection noise in `wrapFetchWithAbortSignal`. Thanks @Jackten. - Gateway/Config: prevent `config.patch` object-array merges from falling back to full-array replacement when some patch entries lack `id`, so partial `agents.list` updates no longer drop unrelated agents. (#17989) Thanks @stakeswky. +- Config/Discord: require string IDs in Discord allowlists, keep onboarding inputs string-only, and add doctor repair for numeric entries. (#18220) Thanks @thewilloftheshadow. - Agents/Models: probe the primary model when its auth-profile cooldown is near expiry (with per-provider throttling), so runs recover from temporary rate limits without staying on fallback models until restart. (#17478) Thanks @PlayerGhost. - Agents/Tools: scope the `message` tool schema to the active channel so Telegram uses `buttons` and Discord uses `components`. (#18215) Thanks @obviyus. - Telegram: keep draft-stream preview replies attached to the user message for `replyToMode: "all"` in groups and DMs, preserving threaded reply context from preview through finalization. (#17880) Thanks @yinghaosang. diff --git a/src/channels/plugins/onboarding/discord.ts b/src/channels/plugins/onboarding/discord.ts index 3a4187f3b2..23a87d0ca3 100644 --- a/src/channels/plugins/onboarding/discord.ts +++ b/src/channels/plugins/onboarding/discord.ts @@ -17,14 +17,23 @@ import { resolveDiscordUserAllowlist } from "../../../discord/resolve-users.js"; import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js"; import { formatDocsLink } from "../../../terminal/links.js"; import { promptChannelAccessConfig } from "./channel-access.js"; -import { addWildcardAllowFrom, promptAccountId } from "./helpers.js"; +import { promptAccountId } from "./helpers.js"; + +function addDiscordWildcardAllowFrom(allowFrom?: string[] | null): string[] { + const next = (allowFrom ?? []).map((entry) => entry.trim()).filter(Boolean); + if (!next.includes("*")) { + next.push("*"); + } + return next; +} const channel = "discord" as const; function setDiscordDmPolicy(cfg: OpenClawConfig, dmPolicy: DmPolicy) { const existingAllowFrom = cfg.channels?.discord?.allowFrom ?? cfg.channels?.discord?.dm?.allowFrom; - const allowFrom = dmPolicy === "open" ? addWildcardAllowFrom(existingAllowFrom) : undefined; + const allowFrom = + dmPolicy === "open" ? addDiscordWildcardAllowFrom(existingAllowFrom) : undefined; return { ...cfg, channels: { diff --git a/src/commands/doctor-config-flow.e2e.test.ts b/src/commands/doctor-config-flow.e2e.test.ts index aff8049ca0..25638d5b94 100644 --- a/src/commands/doctor-config-flow.e2e.test.ts +++ b/src/commands/doctor-config-flow.e2e.test.ts @@ -120,4 +120,113 @@ describe("doctor config flow", () => { vi.unstubAllGlobals(); } }); + + it("converts numeric discord ids to strings on repair", async () => { + await withTempHome(async (home) => { + const configDir = path.join(home, ".openclaw"); + await fs.mkdir(configDir, { recursive: true }); + await fs.writeFile( + path.join(configDir, "openclaw.json"), + JSON.stringify( + { + channels: { + discord: { + allowFrom: [123], + dm: { allowFrom: [456], groupChannels: [789] }, + execApprovals: { approvers: [321] }, + guilds: { + "100": { + users: [111], + roles: [222], + channels: { + general: { users: [333], roles: [444] }, + }, + }, + }, + accounts: { + work: { + allowFrom: [555], + dm: { allowFrom: [666], groupChannels: [777] }, + execApprovals: { approvers: [888] }, + guilds: { + "200": { + users: [999], + roles: [1010], + channels: { + help: { users: [1111], roles: [1212] }, + }, + }, + }, + }, + }, + }, + }, + }, + null, + 2, + ), + "utf-8", + ); + + const result = await loadAndMaybeMigrateDoctorConfig({ + options: { nonInteractive: true, repair: true }, + confirm: async () => false, + }); + + const cfg = result.cfg as unknown as { + channels: { + discord: { + allowFrom: string[]; + dm: { allowFrom: string[]; groupChannels: string[] }; + execApprovals: { approvers: string[] }; + guilds: Record< + string, + { + users: string[]; + roles: string[]; + channels: Record; + } + >; + accounts: Record< + string, + { + allowFrom: string[]; + dm: { allowFrom: string[]; groupChannels: string[] }; + execApprovals: { approvers: string[] }; + guilds: Record< + string, + { + users: string[]; + roles: string[]; + channels: Record; + } + >; + } + >; + }; + }; + }; + + expect(cfg.channels.discord.allowFrom).toEqual(["123"]); + expect(cfg.channels.discord.dm.allowFrom).toEqual(["456"]); + expect(cfg.channels.discord.dm.groupChannels).toEqual(["789"]); + expect(cfg.channels.discord.execApprovals.approvers).toEqual(["321"]); + expect(cfg.channels.discord.guilds["100"].users).toEqual(["111"]); + expect(cfg.channels.discord.guilds["100"].roles).toEqual(["222"]); + expect(cfg.channels.discord.guilds["100"].channels.general.users).toEqual(["333"]); + expect(cfg.channels.discord.guilds["100"].channels.general.roles).toEqual(["444"]); + expect(cfg.channels.discord.accounts.work.allowFrom).toEqual(["555"]); + expect(cfg.channels.discord.accounts.work.dm.allowFrom).toEqual(["666"]); + expect(cfg.channels.discord.accounts.work.dm.groupChannels).toEqual(["777"]); + expect(cfg.channels.discord.accounts.work.execApprovals.approvers).toEqual(["888"]); + expect(cfg.channels.discord.accounts.work.guilds["200"].users).toEqual(["999"]); + expect(cfg.channels.discord.accounts.work.guilds["200"].roles).toEqual(["1010"]); + expect(cfg.channels.discord.accounts.work.guilds["200"].channels.help.users).toEqual([ + "1111", + ]); + expect(cfg.channels.discord.accounts.work.guilds["200"].channels.help.roles).toEqual([ + "1212", + ]); + }); + }); }); diff --git a/src/commands/doctor-config-flow.ts b/src/commands/doctor-config-flow.ts index 4c8707d0c5..d36a40222b 100644 --- a/src/commands/doctor-config-flow.ts +++ b/src/commands/doctor-config-flow.ts @@ -395,6 +395,164 @@ async function maybeRepairTelegramAllowFromUsernames(cfg: OpenClawConfig): Promi return { config: next, changes }; } +type DiscordNumericIdHit = { path: string; entry: number }; + +type DiscordIdListRef = { + pathLabel: string; + holder: Record; + key: string; +}; + +function collectDiscordAccountScopes( + cfg: OpenClawConfig, +): Array<{ prefix: string; account: Record }> { + const scopes: Array<{ prefix: string; account: Record }> = []; + const discord = asObjectRecord(cfg.channels?.discord); + if (!discord) { + return scopes; + } + + scopes.push({ prefix: "channels.discord", account: discord }); + const accounts = asObjectRecord(discord.accounts); + if (!accounts) { + return scopes; + } + for (const key of Object.keys(accounts)) { + const account = asObjectRecord(accounts[key]); + if (!account) { + continue; + } + scopes.push({ prefix: `channels.discord.accounts.${key}`, account }); + } + + return scopes; +} + +function collectDiscordIdLists( + prefix: string, + account: Record, +): DiscordIdListRef[] { + const refs: DiscordIdListRef[] = [ + { pathLabel: `${prefix}.allowFrom`, holder: account, key: "allowFrom" }, + ]; + const dm = asObjectRecord(account.dm); + if (dm) { + refs.push({ pathLabel: `${prefix}.dm.allowFrom`, holder: dm, key: "allowFrom" }); + refs.push({ pathLabel: `${prefix}.dm.groupChannels`, holder: dm, key: "groupChannels" }); + } + const execApprovals = asObjectRecord(account.execApprovals); + if (execApprovals) { + refs.push({ + pathLabel: `${prefix}.execApprovals.approvers`, + holder: execApprovals, + key: "approvers", + }); + } + const guilds = asObjectRecord(account.guilds); + if (!guilds) { + return refs; + } + + for (const guildId of Object.keys(guilds)) { + const guild = asObjectRecord(guilds[guildId]); + if (!guild) { + continue; + } + refs.push({ pathLabel: `${prefix}.guilds.${guildId}.users`, holder: guild, key: "users" }); + refs.push({ pathLabel: `${prefix}.guilds.${guildId}.roles`, holder: guild, key: "roles" }); + const channels = asObjectRecord(guild.channels); + if (!channels) { + continue; + } + for (const channelId of Object.keys(channels)) { + const channel = asObjectRecord(channels[channelId]); + if (!channel) { + continue; + } + refs.push({ + pathLabel: `${prefix}.guilds.${guildId}.channels.${channelId}.users`, + holder: channel, + key: "users", + }); + refs.push({ + pathLabel: `${prefix}.guilds.${guildId}.channels.${channelId}.roles`, + holder: channel, + key: "roles", + }); + } + } + return refs; +} + +function scanDiscordNumericIdEntries(cfg: OpenClawConfig): DiscordNumericIdHit[] { + const hits: DiscordNumericIdHit[] = []; + const scanList = (pathLabel: string, list: unknown) => { + if (!Array.isArray(list)) { + return; + } + for (const [index, entry] of list.entries()) { + if (typeof entry !== "number") { + continue; + } + hits.push({ path: `${pathLabel}[${index}]`, entry }); + } + }; + + for (const scope of collectDiscordAccountScopes(cfg)) { + for (const ref of collectDiscordIdLists(scope.prefix, scope.account)) { + scanList(ref.pathLabel, ref.holder[ref.key]); + } + } + + return hits; +} + +function maybeRepairDiscordNumericIds(cfg: OpenClawConfig): { + config: OpenClawConfig; + changes: string[]; +} { + const hits = scanDiscordNumericIdEntries(cfg); + if (hits.length === 0) { + return { config: cfg, changes: [] }; + } + + const next = structuredClone(cfg); + const changes: string[] = []; + + const repairList = (pathLabel: string, holder: Record, key: string) => { + const raw = holder[key]; + if (!Array.isArray(raw)) { + return; + } + let converted = 0; + const updated = raw.map((entry) => { + if (typeof entry === "number") { + converted += 1; + return String(entry); + } + return entry; + }); + if (converted === 0) { + return; + } + holder[key] = updated; + changes.push( + `- ${pathLabel}: converted ${converted} numeric ${converted === 1 ? "entry" : "entries"} to strings`, + ); + }; + + for (const scope of collectDiscordAccountScopes(next)) { + for (const ref of collectDiscordIdLists(scope.prefix, scope.account)) { + repairList(ref.pathLabel, ref.holder, ref.key); + } + } + + if (changes.length === 0) { + return { config: cfg, changes: [] }; + } + return { config: next, changes }; +} + async function maybeMigrateLegacyConfig(): Promise { const changes: string[] = []; const home = resolveHomeDir(); @@ -533,6 +691,14 @@ export async function loadAndMaybeMigrateDoctorConfig(params: { pendingChanges = true; cfg = repair.config; } + + const discordRepair = maybeRepairDiscordNumericIds(candidate); + if (discordRepair.changes.length > 0) { + note(discordRepair.changes.join("\n"), "Doctor changes"); + candidate = discordRepair.config; + pendingChanges = true; + cfg = discordRepair.config; + } } else { const hits = scanTelegramAllowFromUsernameEntries(candidate); if (hits.length > 0) { @@ -544,6 +710,17 @@ export async function loadAndMaybeMigrateDoctorConfig(params: { "Doctor warnings", ); } + + const discordHits = scanDiscordNumericIdEntries(candidate); + if (discordHits.length > 0) { + note( + [ + `- Discord allowlists contain ${discordHits.length} numeric entries (e.g. ${discordHits[0]?.path}=${discordHits[0]?.entry}).`, + `- Discord IDs must be strings; run "${formatCliCommand("openclaw doctor --fix")}" to convert numeric IDs to quoted strings.`, + ].join("\n"), + "Doctor warnings", + ); + } } const unknown = stripUnknownConfigKeys(candidate); diff --git a/src/config/config.discord.test.ts b/src/config/config.discord.test.ts index 47c9e996fb..bd0ac31822 100644 --- a/src/config/config.discord.test.ts +++ b/src/config/config.discord.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; -import { loadConfig } from "./config.js"; +import { loadConfig, validateConfigObject } from "./config.js"; import { withTempHome } from "./test-helpers.js"; describe("config discord", () => { @@ -68,4 +68,32 @@ describe("config discord", () => { expect(cfg.channels?.discord?.guilds?.["123"]?.channels?.general?.allow).toBe(true); }); }); + + it("rejects numeric discord allowlist entries", () => { + const res = validateConfigObject({ + channels: { + discord: { + allowFrom: [123], + dm: { allowFrom: [456], groupChannels: [789] }, + guilds: { + "123": { + users: [111], + roles: [222], + channels: { + general: { users: [333], roles: [444] }, + }, + }, + }, + execApprovals: { approvers: [555] }, + }, + }, + }); + + expect(res.ok).toBe(false); + if (!res.ok) { + expect( + res.issues.some((issue) => issue.message.includes("Discord IDs must be strings")), + ).toBe(true); + } + }); }); diff --git a/src/config/types.discord.ts b/src/config/types.discord.ts index b2e1652907..b7a42cd091 100644 --- a/src/config/types.discord.ts +++ b/src/config/types.discord.ts @@ -17,11 +17,11 @@ export type DiscordDmConfig = { /** Direct message access policy (default: pairing). */ policy?: DmPolicy; /** Allowlist for DM senders (ids or names). */ - allowFrom?: Array; + allowFrom?: string[]; /** If true, allow group DMs (default: false). */ groupEnabled?: boolean; /** Optional allowlist for group DM channels (ids or slugs). */ - groupChannels?: Array; + groupChannels?: string[]; }; export type DiscordGuildChannelConfig = { @@ -35,9 +35,9 @@ export type DiscordGuildChannelConfig = { /** If false, disable the bot for this channel. */ enabled?: boolean; /** Optional allowlist for channel senders (ids or names). */ - users?: Array; + users?: string[]; /** Optional allowlist for channel senders by role ID. */ - roles?: Array; + roles?: string[]; /** Optional system prompt snippet for this channel. */ systemPrompt?: string; /** If false, omit thread starter context for this channel (default: true). */ @@ -55,9 +55,9 @@ export type DiscordGuildEntry = { /** Reaction notification mode (off|own|all|allowlist). Default: own. */ reactionNotifications?: DiscordReactionNotificationMode; /** Optional allowlist for guild senders (ids or names). */ - users?: Array; + users?: string[]; /** Optional allowlist for guild senders by role ID. */ - roles?: Array; + roles?: string[]; channels?: Record; }; @@ -95,7 +95,7 @@ export type DiscordExecApprovalConfig = { /** Enable exec approval forwarding to Discord DMs. Default: false. */ enabled?: boolean; /** Discord user IDs to receive approval prompts. Required if enabled. */ - approvers?: Array; + approvers?: string[]; /** Only forward approvals for these agent IDs. Omit = all agents. */ agentFilter?: string[]; /** Only forward approvals matching these session key patterns (substring or regex). */ @@ -182,7 +182,7 @@ export type DiscordAccountConfig = { * Alias for dm.allowFrom (prefer this so it inherits cleanly via base->account shallow merge). * Legacy key: channels.discord.dm.allowFrom. */ - allowFrom?: Array; + allowFrom?: string[]; dm?: DiscordDmConfig; /** New per-guild config keyed by guild id or slug. */ guilds?: Record; diff --git a/src/config/zod-schema.providers-core.ts b/src/config/zod-schema.providers-core.ts index ed40d5e62b..7e1dd80131 100644 --- a/src/config/zod-schema.providers-core.ts +++ b/src/config/zod-schema.providers-core.ts @@ -25,6 +25,13 @@ import { sensitive } from "./zod-schema.sensitive.js"; const ToolPolicyBySenderSchema = z.record(z.string(), ToolPolicySchema).optional(); +const DiscordIdSchema = z + .union([z.string(), z.number()]) + .refine((value) => typeof value === "string", { + message: "Discord IDs must be strings (wrap numeric IDs in quotes).", + }); +const DiscordIdListSchema = z.array(DiscordIdSchema); + const TelegramInlineButtonsScopeSchema = z.enum(["off", "dm", "group", "all", "allowlist"]); const TelegramCapabilitiesSchema = z.union([ @@ -214,9 +221,9 @@ export const DiscordDmSchema = z .object({ enabled: z.boolean().optional(), policy: DmPolicySchema.optional(), - allowFrom: z.array(z.union([z.string(), z.number()])).optional(), + allowFrom: DiscordIdListSchema.optional(), groupEnabled: z.boolean().optional(), - groupChannels: z.array(z.union([z.string(), z.number()])).optional(), + groupChannels: DiscordIdListSchema.optional(), }) .strict(); @@ -228,8 +235,8 @@ export const DiscordGuildChannelSchema = z toolsBySender: ToolPolicyBySenderSchema, skills: z.array(z.string()).optional(), enabled: z.boolean().optional(), - users: z.array(z.union([z.string(), z.number()])).optional(), - roles: z.array(z.union([z.string(), z.number()])).optional(), + users: DiscordIdListSchema.optional(), + roles: DiscordIdListSchema.optional(), systemPrompt: z.string().optional(), includeThreadStarter: z.boolean().optional(), autoThread: z.boolean().optional(), @@ -243,8 +250,8 @@ export const DiscordGuildSchema = z tools: ToolPolicySchema, toolsBySender: ToolPolicyBySenderSchema, reactionNotifications: z.enum(["off", "own", "all", "allowlist"]).optional(), - users: z.array(z.union([z.string(), z.number()])).optional(), - roles: z.array(z.union([z.string(), z.number()])).optional(), + users: DiscordIdListSchema.optional(), + roles: DiscordIdListSchema.optional(), channels: z.record(z.string(), DiscordGuildChannelSchema.optional()).optional(), }) .strict(); @@ -311,14 +318,14 @@ export const DiscordAccountSchema = z // Aliases for channels.discord.dm.policy / channels.discord.dm.allowFrom. Prefer these for // inheritance in multi-account setups (shallow merge works; nested dm object doesn't). dmPolicy: DmPolicySchema.optional(), - allowFrom: z.array(z.union([z.string(), z.number()])).optional(), + allowFrom: DiscordIdListSchema.optional(), dm: DiscordDmSchema.optional(), guilds: z.record(z.string(), DiscordGuildSchema.optional()).optional(), heartbeat: ChannelHeartbeatVisibilitySchema, execApprovals: z .object({ enabled: z.boolean().optional(), - approvers: z.array(z.union([z.string(), z.number()])).optional(), + approvers: DiscordIdListSchema.optional(), agentFilter: z.array(z.string()).optional(), sessionFilter: z.array(z.string()).optional(), cleanupAfterResolve: z.boolean().optional(), diff --git a/src/discord/monitor/agent-components.ts b/src/discord/monitor/agent-components.ts index 87f6a3f5ea..5e8c797881 100644 --- a/src/discord/monitor/agent-components.ts +++ b/src/discord/monitor/agent-components.ts @@ -334,7 +334,7 @@ export type AgentComponentContext = { token?: string; guildEntries?: Record; /** DM allowlist (from allowFrom config; legacy: dm.allowFrom) */ - allowFrom?: Array; + allowFrom?: string[]; /** DM policy (default: "pairing") */ dmPolicy?: "open" | "pairing" | "allowlist" | "disabled"; }; diff --git a/src/discord/monitor/allow-list.ts b/src/discord/monitor/allow-list.ts index b81b0af999..da83cb556f 100644 --- a/src/discord/monitor/allow-list.ts +++ b/src/discord/monitor/allow-list.ts @@ -21,8 +21,8 @@ export type DiscordGuildEntryResolved = { slug?: string; requireMention?: boolean; reactionNotifications?: "off" | "own" | "all" | "allowlist"; - users?: Array; - roles?: Array; + users?: string[]; + roles?: string[]; channels?: Record< string, { @@ -30,8 +30,8 @@ export type DiscordGuildEntryResolved = { requireMention?: boolean; skills?: string[]; enabled?: boolean; - users?: Array; - roles?: Array; + users?: string[]; + roles?: string[]; systemPrompt?: string; includeThreadStarter?: boolean; autoThread?: boolean; @@ -44,8 +44,8 @@ export type DiscordChannelConfigResolved = { requireMention?: boolean; skills?: string[]; enabled?: boolean; - users?: Array; - roles?: Array; + users?: string[]; + roles?: string[]; systemPrompt?: string; includeThreadStarter?: boolean; autoThread?: boolean; @@ -53,10 +53,7 @@ export type DiscordChannelConfigResolved = { matchSource?: ChannelMatchSource; }; -export function normalizeDiscordAllowList( - raw: Array | undefined, - prefixes: string[], -) { +export function normalizeDiscordAllowList(raw: string[] | undefined, prefixes: string[]) { if (!raw || raw.length === 0) { return null; } @@ -141,7 +138,7 @@ export function resolveDiscordAllowListMatch(params: { } export function resolveDiscordUserAllowed(params: { - allowList?: Array; + allowList?: string[]; userId: string; userName?: string; userTag?: string; @@ -158,10 +155,10 @@ export function resolveDiscordUserAllowed(params: { } export function resolveDiscordRoleAllowed(params: { - allowList?: Array; + allowList?: string[]; memberRoleIds: string[]; }) { - // Role allowlists accept role IDs only (string or number). Names are ignored. + // Role allowlists accept role IDs only. Names are ignored. const allowList = normalizeDiscordAllowList(params.allowList, ["role:"]); if (!allowList) { return true; @@ -173,8 +170,8 @@ export function resolveDiscordRoleAllowed(params: { } export function resolveDiscordMemberAllowed(params: { - userAllowList?: Array; - roleAllowList?: Array; + userAllowList?: string[]; + roleAllowList?: string[]; memberRoleIds: string[]; userId: string; userName?: string; @@ -253,7 +250,7 @@ export function resolveDiscordOwnerAllowFrom(params: { export function resolveDiscordCommandAuthorized(params: { isDirectMessage: boolean; - allowFrom?: Array; + allowFrom?: string[]; guildInfo?: DiscordGuildEntryResolved | null; author: User; }) { @@ -478,7 +475,7 @@ export function isDiscordGroupAllowedByPolicy(params: { } export function resolveGroupDmAllow(params: { - channels?: Array; + channels?: string[]; channelId: string; channelName?: string; channelSlug: string; @@ -503,7 +500,7 @@ export function shouldEmitDiscordReactionNotification(params: { userId: string; userName?: string; userTag?: string; - allowlist?: Array; + allowlist?: string[]; }) { const mode = params.mode ?? "own"; if (mode === "off") { diff --git a/src/discord/monitor/exec-approvals.ts b/src/discord/monitor/exec-approvals.ts index 2cefb30e6b..04fa106308 100644 --- a/src/discord/monitor/exec-approvals.ts +++ b/src/discord/monitor/exec-approvals.ts @@ -721,7 +721,7 @@ export class DiscordExecApprovalHandler { } /** Return the list of configured approver IDs. */ - getApprovers(): Array { + getApprovers(): string[] { return this.opts.config.approvers ?? []; } } diff --git a/src/discord/monitor/message-handler.preflight.types.ts b/src/discord/monitor/message-handler.preflight.types.ts index 931bdd75bb..f06b8d453b 100644 --- a/src/discord/monitor/message-handler.preflight.types.ts +++ b/src/discord/monitor/message-handler.preflight.types.ts @@ -95,8 +95,8 @@ export type DiscordMessagePreflightParams = { replyToMode: ReplyToMode; dmEnabled: boolean; groupDmEnabled: boolean; - groupDmChannels?: Array; - allowFrom?: Array; + groupDmChannels?: string[]; + allowFrom?: string[]; guildEntries?: Record; ackReactionScope: DiscordMessagePreflightContext["ackReactionScope"]; groupPolicy: DiscordMessagePreflightContext["groupPolicy"]; diff --git a/src/discord/monitor/provider.ts b/src/discord/monitor/provider.ts index 5ee54ab68c..d3893f9917 100644 --- a/src/discord/monitor/provider.ts +++ b/src/discord/monitor/provider.ts @@ -77,7 +77,7 @@ export type MonitorDiscordOpts = { replyToMode?: ReplyToMode; }; -function summarizeAllowList(list?: Array) { +function summarizeAllowList(list?: string[]) { if (!list || list.length === 0) { return "any"; } @@ -352,7 +352,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) { continue; } const nextGuild = { ...guildConfig } as Record; - const users = (guildConfig as { users?: Array }).users; + const users = (guildConfig as { users?: string[] }).users; if (Array.isArray(users) && users.length > 0) { const additions = resolveAllowlistIdAdditions({ existing: users, resolvedMap }); nextGuild.users = mergeAllowlist({ existing: users, additions });