mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
Config: require Discord ID strings (#18220)
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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<string, { users: string[]; roles: string[] }>;
|
||||
}
|
||||
>;
|
||||
accounts: Record<
|
||||
string,
|
||||
{
|
||||
allowFrom: string[];
|
||||
dm: { allowFrom: string[]; groupChannels: string[] };
|
||||
execApprovals: { approvers: string[] };
|
||||
guilds: Record<
|
||||
string,
|
||||
{
|
||||
users: string[];
|
||||
roles: string[];
|
||||
channels: Record<string, { users: string[]; roles: string[] }>;
|
||||
}
|
||||
>;
|
||||
}
|
||||
>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
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",
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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<string, unknown>;
|
||||
key: string;
|
||||
};
|
||||
|
||||
function collectDiscordAccountScopes(
|
||||
cfg: OpenClawConfig,
|
||||
): Array<{ prefix: string; account: Record<string, unknown> }> {
|
||||
const scopes: Array<{ prefix: string; account: Record<string, unknown> }> = [];
|
||||
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<string, unknown>,
|
||||
): 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<string, unknown>, 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<string[]> {
|
||||
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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,11 +17,11 @@ export type DiscordDmConfig = {
|
||||
/** Direct message access policy (default: pairing). */
|
||||
policy?: DmPolicy;
|
||||
/** Allowlist for DM senders (ids or names). */
|
||||
allowFrom?: Array<string | number>;
|
||||
allowFrom?: string[];
|
||||
/** If true, allow group DMs (default: false). */
|
||||
groupEnabled?: boolean;
|
||||
/** Optional allowlist for group DM channels (ids or slugs). */
|
||||
groupChannels?: Array<string | number>;
|
||||
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<string | number>;
|
||||
users?: string[];
|
||||
/** Optional allowlist for channel senders by role ID. */
|
||||
roles?: Array<string | number>;
|
||||
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<string | number>;
|
||||
users?: string[];
|
||||
/** Optional allowlist for guild senders by role ID. */
|
||||
roles?: Array<string | number>;
|
||||
roles?: string[];
|
||||
channels?: Record<string, DiscordGuildChannelConfig>;
|
||||
};
|
||||
|
||||
@@ -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<string | number>;
|
||||
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<string | number>;
|
||||
allowFrom?: string[];
|
||||
dm?: DiscordDmConfig;
|
||||
/** New per-guild config keyed by guild id or slug. */
|
||||
guilds?: Record<string, DiscordGuildEntry>;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -334,7 +334,7 @@ export type AgentComponentContext = {
|
||||
token?: string;
|
||||
guildEntries?: Record<string, DiscordGuildEntryResolved>;
|
||||
/** DM allowlist (from allowFrom config; legacy: dm.allowFrom) */
|
||||
allowFrom?: Array<string | number>;
|
||||
allowFrom?: string[];
|
||||
/** DM policy (default: "pairing") */
|
||||
dmPolicy?: "open" | "pairing" | "allowlist" | "disabled";
|
||||
};
|
||||
|
||||
@@ -21,8 +21,8 @@ export type DiscordGuildEntryResolved = {
|
||||
slug?: string;
|
||||
requireMention?: boolean;
|
||||
reactionNotifications?: "off" | "own" | "all" | "allowlist";
|
||||
users?: Array<string | number>;
|
||||
roles?: Array<string | number>;
|
||||
users?: string[];
|
||||
roles?: string[];
|
||||
channels?: Record<
|
||||
string,
|
||||
{
|
||||
@@ -30,8 +30,8 @@ export type DiscordGuildEntryResolved = {
|
||||
requireMention?: boolean;
|
||||
skills?: string[];
|
||||
enabled?: boolean;
|
||||
users?: Array<string | number>;
|
||||
roles?: Array<string | number>;
|
||||
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<string | number>;
|
||||
roles?: Array<string | number>;
|
||||
users?: string[];
|
||||
roles?: string[];
|
||||
systemPrompt?: string;
|
||||
includeThreadStarter?: boolean;
|
||||
autoThread?: boolean;
|
||||
@@ -53,10 +53,7 @@ export type DiscordChannelConfigResolved = {
|
||||
matchSource?: ChannelMatchSource;
|
||||
};
|
||||
|
||||
export function normalizeDiscordAllowList(
|
||||
raw: Array<string | number> | 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<string | number>;
|
||||
allowList?: string[];
|
||||
userId: string;
|
||||
userName?: string;
|
||||
userTag?: string;
|
||||
@@ -158,10 +155,10 @@ export function resolveDiscordUserAllowed(params: {
|
||||
}
|
||||
|
||||
export function resolveDiscordRoleAllowed(params: {
|
||||
allowList?: Array<string | number>;
|
||||
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<string | number>;
|
||||
roleAllowList?: Array<string | number>;
|
||||
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<string | number>;
|
||||
allowFrom?: string[];
|
||||
guildInfo?: DiscordGuildEntryResolved | null;
|
||||
author: User;
|
||||
}) {
|
||||
@@ -478,7 +475,7 @@ export function isDiscordGroupAllowedByPolicy(params: {
|
||||
}
|
||||
|
||||
export function resolveGroupDmAllow(params: {
|
||||
channels?: Array<string | number>;
|
||||
channels?: string[];
|
||||
channelId: string;
|
||||
channelName?: string;
|
||||
channelSlug: string;
|
||||
@@ -503,7 +500,7 @@ export function shouldEmitDiscordReactionNotification(params: {
|
||||
userId: string;
|
||||
userName?: string;
|
||||
userTag?: string;
|
||||
allowlist?: Array<string | number>;
|
||||
allowlist?: string[];
|
||||
}) {
|
||||
const mode = params.mode ?? "own";
|
||||
if (mode === "off") {
|
||||
|
||||
@@ -721,7 +721,7 @@ export class DiscordExecApprovalHandler {
|
||||
}
|
||||
|
||||
/** Return the list of configured approver IDs. */
|
||||
getApprovers(): Array<string | number> {
|
||||
getApprovers(): string[] {
|
||||
return this.opts.config.approvers ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,8 @@ export type DiscordMessagePreflightParams = {
|
||||
replyToMode: ReplyToMode;
|
||||
dmEnabled: boolean;
|
||||
groupDmEnabled: boolean;
|
||||
groupDmChannels?: Array<string | number>;
|
||||
allowFrom?: Array<string | number>;
|
||||
groupDmChannels?: string[];
|
||||
allowFrom?: string[];
|
||||
guildEntries?: Record<string, DiscordGuildEntryResolved>;
|
||||
ackReactionScope: DiscordMessagePreflightContext["ackReactionScope"];
|
||||
groupPolicy: DiscordMessagePreflightContext["groupPolicy"];
|
||||
|
||||
@@ -77,7 +77,7 @@ export type MonitorDiscordOpts = {
|
||||
replyToMode?: ReplyToMode;
|
||||
};
|
||||
|
||||
function summarizeAllowList(list?: Array<string | number>) {
|
||||
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<string, unknown>;
|
||||
const users = (guildConfig as { users?: Array<string | number> }).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 });
|
||||
|
||||
Reference in New Issue
Block a user