Discord: inherit thread allowlists
This commit is contained in:
committed by
Peter Steinberger
parent
852aa16ca0
commit
277e43e32c
@@ -20,6 +20,7 @@ Docs: https://docs.clawd.bot
|
||||
- Memory: split overly long lines to keep embeddings under token limits.
|
||||
- Memory: skip empty chunks to avoid invalid embedding inputs.
|
||||
- Sessions: fall back to session labels when listing display names. (#1124) — thanks @abdaraxus.
|
||||
- Discord: inherit parent channel allowlists for thread slash commands and reactions. (#1123) — thanks @thewilloftheshadow.
|
||||
|
||||
## 2026.1.17-1
|
||||
|
||||
|
||||
@@ -137,6 +137,34 @@ export function resolveDiscordGuildEntry(params: {
|
||||
return null;
|
||||
}
|
||||
|
||||
type DiscordChannelEntry = NonNullable<DiscordGuildEntryResolved["channels"]>[string];
|
||||
|
||||
function resolveDiscordChannelEntry(
|
||||
channels: NonNullable<DiscordGuildEntryResolved["channels"]>,
|
||||
channelId: string,
|
||||
channelName?: string,
|
||||
channelSlug?: string,
|
||||
): DiscordChannelEntry | null {
|
||||
if (channelId && channels[channelId]) return channels[channelId];
|
||||
if (channelSlug && channels[channelSlug]) return channels[channelSlug];
|
||||
if (channelName && channels[channelName]) return channels[channelName];
|
||||
return null;
|
||||
}
|
||||
|
||||
function resolveDiscordChannelConfigEntry(
|
||||
entry: DiscordChannelEntry,
|
||||
): DiscordChannelConfigResolved {
|
||||
return {
|
||||
allowed: entry.allow !== false,
|
||||
requireMention: entry.requireMention,
|
||||
skills: entry.skills,
|
||||
enabled: entry.enabled,
|
||||
users: entry.users,
|
||||
systemPrompt: entry.systemPrompt,
|
||||
autoThread: entry.autoThread,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveDiscordChannelConfig(params: {
|
||||
guildInfo?: DiscordGuildEntryResolved | null;
|
||||
channelId: string;
|
||||
@@ -146,40 +174,35 @@ export function resolveDiscordChannelConfig(params: {
|
||||
const { guildInfo, channelId, channelName, channelSlug } = params;
|
||||
const channels = guildInfo?.channels;
|
||||
if (!channels) return null;
|
||||
const byId = channels[channelId];
|
||||
if (byId)
|
||||
return {
|
||||
allowed: byId.allow !== false,
|
||||
requireMention: byId.requireMention,
|
||||
skills: byId.skills,
|
||||
enabled: byId.enabled,
|
||||
users: byId.users,
|
||||
systemPrompt: byId.systemPrompt,
|
||||
autoThread: byId.autoThread,
|
||||
};
|
||||
if (channelSlug && channels[channelSlug]) {
|
||||
const entry = channels[channelSlug];
|
||||
return {
|
||||
allowed: entry.allow !== false,
|
||||
requireMention: entry.requireMention,
|
||||
skills: entry.skills,
|
||||
enabled: entry.enabled,
|
||||
users: entry.users,
|
||||
systemPrompt: entry.systemPrompt,
|
||||
autoThread: entry.autoThread,
|
||||
};
|
||||
}
|
||||
if (channelName && channels[channelName]) {
|
||||
const entry = channels[channelName];
|
||||
return {
|
||||
allowed: entry.allow !== false,
|
||||
requireMention: entry.requireMention,
|
||||
skills: entry.skills,
|
||||
enabled: entry.enabled,
|
||||
users: entry.users,
|
||||
systemPrompt: entry.systemPrompt,
|
||||
autoThread: entry.autoThread,
|
||||
};
|
||||
const entry = resolveDiscordChannelEntry(channels, channelId, channelName, channelSlug);
|
||||
if (!entry) return { allowed: false };
|
||||
return resolveDiscordChannelConfigEntry(entry);
|
||||
}
|
||||
|
||||
export function resolveDiscordChannelConfigWithFallback(params: {
|
||||
guildInfo?: DiscordGuildEntryResolved | null;
|
||||
channelId: string;
|
||||
channelName?: string;
|
||||
channelSlug: string;
|
||||
parentId?: string;
|
||||
parentName?: string;
|
||||
parentSlug?: string;
|
||||
}): DiscordChannelConfigResolved | null {
|
||||
const { guildInfo, channelId, channelName, channelSlug, parentId, parentName, parentSlug } =
|
||||
params;
|
||||
const channels = guildInfo?.channels;
|
||||
if (!channels) return null;
|
||||
const entry = resolveDiscordChannelEntry(channels, channelId, channelName, channelSlug);
|
||||
if (entry) return resolveDiscordChannelConfigEntry(entry);
|
||||
if (parentId || parentName || parentSlug) {
|
||||
const resolvedParentSlug = parentSlug ?? (parentName ? normalizeDiscordSlug(parentName) : "");
|
||||
const parentEntry = resolveDiscordChannelEntry(
|
||||
channels,
|
||||
parentId ?? "",
|
||||
parentName,
|
||||
resolvedParentSlug,
|
||||
);
|
||||
if (parentEntry) return resolveDiscordChannelConfigEntry(parentEntry);
|
||||
}
|
||||
return { allowed: false };
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
isDiscordGroupAllowedByPolicy,
|
||||
normalizeDiscordAllowList,
|
||||
normalizeDiscordSlug,
|
||||
resolveDiscordChannelConfig,
|
||||
resolveDiscordChannelConfigWithFallback,
|
||||
resolveDiscordGuildEntry,
|
||||
resolveDiscordShouldRequireMention,
|
||||
resolveDiscordUserAllowed,
|
||||
@@ -236,13 +236,19 @@ export async function preflightDiscordMessage(
|
||||
guildInfo?.slug ||
|
||||
(params.data.guild?.name ? normalizeDiscordSlug(params.data.guild.name) : "");
|
||||
|
||||
const threadChannelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
|
||||
const threadParentSlug = threadParentName ? normalizeDiscordSlug(threadParentName) : "";
|
||||
|
||||
const baseSessionKey = route.sessionKey;
|
||||
const channelConfig = isGuildMessage
|
||||
? resolveDiscordChannelConfig({
|
||||
? resolveDiscordChannelConfigWithFallback({
|
||||
guildInfo,
|
||||
channelId: threadParentId ?? message.channelId,
|
||||
channelName: configChannelName,
|
||||
channelSlug: configChannelSlug,
|
||||
channelId: message.channelId,
|
||||
channelName,
|
||||
channelSlug: threadChannelSlug,
|
||||
parentId: threadParentId ?? undefined,
|
||||
parentName: threadParentName ?? undefined,
|
||||
parentSlug: threadParentSlug,
|
||||
})
|
||||
: null;
|
||||
if (isGuildMessage && channelConfig?.enabled === false) {
|
||||
|
||||
@@ -47,11 +47,13 @@ import {
|
||||
isDiscordGroupAllowedByPolicy,
|
||||
normalizeDiscordAllowList,
|
||||
normalizeDiscordSlug,
|
||||
resolveDiscordChannelConfig,
|
||||
resolveDiscordChannelConfigWithFallback,
|
||||
resolveDiscordGuildEntry,
|
||||
resolveDiscordUserAllowed,
|
||||
} from "./allow-list.js";
|
||||
import { formatDiscordUserTag } from "./format.js";
|
||||
import { resolveDiscordChannelInfo } from "./message-utils.js";
|
||||
import { resolveDiscordThreadParentInfo } from "./threading.js";
|
||||
|
||||
type DiscordConfig = NonNullable<ClawdbotConfig["channels"]>["discord"];
|
||||
|
||||
@@ -499,8 +501,13 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
const channelType = channel?.type;
|
||||
const isDirectMessage = channelType === ChannelType.DM;
|
||||
const isGroupDm = channelType === ChannelType.GroupDM;
|
||||
const isThreadChannel =
|
||||
channelType === ChannelType.PublicThread ||
|
||||
channelType === ChannelType.PrivateThread ||
|
||||
channelType === ChannelType.AnnouncementThread;
|
||||
const channelName = channel && "name" in channel ? (channel.name as string) : undefined;
|
||||
const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
|
||||
const rawChannelId = channel?.id ?? "";
|
||||
const ownerAllowList = normalizeDiscordAllowList(discordConfig?.dm?.allowFrom ?? [], [
|
||||
"discord:",
|
||||
"user:",
|
||||
@@ -517,12 +524,35 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
guild: interaction.guild ?? undefined,
|
||||
guildEntries: discordConfig?.guilds,
|
||||
});
|
||||
let threadParentId: string | undefined;
|
||||
let threadParentName: string | undefined;
|
||||
let threadParentSlug = "";
|
||||
if (interaction.guild && channel && isThreadChannel && rawChannelId) {
|
||||
// Threads inherit parent channel config unless explicitly overridden.
|
||||
const channelInfo = await resolveDiscordChannelInfo(interaction.client, rawChannelId);
|
||||
const parentInfo = await resolveDiscordThreadParentInfo({
|
||||
client: interaction.client,
|
||||
threadChannel: {
|
||||
id: rawChannelId,
|
||||
name: channelName,
|
||||
parentId: "parentId" in channel ? channel.parentId ?? undefined : undefined,
|
||||
parent: undefined,
|
||||
},
|
||||
channelInfo,
|
||||
});
|
||||
threadParentId = parentInfo.id;
|
||||
threadParentName = parentInfo.name;
|
||||
threadParentSlug = threadParentName ? normalizeDiscordSlug(threadParentName) : "";
|
||||
}
|
||||
const channelConfig = interaction.guild
|
||||
? resolveDiscordChannelConfig({
|
||||
? resolveDiscordChannelConfigWithFallback({
|
||||
guildInfo,
|
||||
channelId: channel?.id ?? "",
|
||||
channelId: rawChannelId,
|
||||
channelName,
|
||||
channelSlug,
|
||||
parentId: threadParentId,
|
||||
parentName: threadParentName,
|
||||
parentSlug: threadParentSlug,
|
||||
})
|
||||
: null;
|
||||
if (channelConfig?.enabled === false) {
|
||||
@@ -664,7 +694,7 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
}
|
||||
|
||||
const isGuild = Boolean(interaction.guild);
|
||||
const channelId = channel?.id ?? "unknown";
|
||||
const channelId = rawChannelId || "unknown";
|
||||
const interactionId = interaction.rawData.id;
|
||||
const route = resolveAgentRoute({
|
||||
cfg,
|
||||
|
||||
Reference in New Issue
Block a user