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: split overly long lines to keep embeddings under token limits.
|
||||||
- Memory: skip empty chunks to avoid invalid embedding inputs.
|
- Memory: skip empty chunks to avoid invalid embedding inputs.
|
||||||
- Sessions: fall back to session labels when listing display names. (#1124) — thanks @abdaraxus.
|
- 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
|
## 2026.1.17-1
|
||||||
|
|
||||||
|
|||||||
@@ -137,6 +137,34 @@ export function resolveDiscordGuildEntry(params: {
|
|||||||
return null;
|
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: {
|
export function resolveDiscordChannelConfig(params: {
|
||||||
guildInfo?: DiscordGuildEntryResolved | null;
|
guildInfo?: DiscordGuildEntryResolved | null;
|
||||||
channelId: string;
|
channelId: string;
|
||||||
@@ -146,40 +174,35 @@ export function resolveDiscordChannelConfig(params: {
|
|||||||
const { guildInfo, channelId, channelName, channelSlug } = params;
|
const { guildInfo, channelId, channelName, channelSlug } = params;
|
||||||
const channels = guildInfo?.channels;
|
const channels = guildInfo?.channels;
|
||||||
if (!channels) return null;
|
if (!channels) return null;
|
||||||
const byId = channels[channelId];
|
const entry = resolveDiscordChannelEntry(channels, channelId, channelName, channelSlug);
|
||||||
if (byId)
|
if (!entry) return { allowed: false };
|
||||||
return {
|
return resolveDiscordChannelConfigEntry(entry);
|
||||||
allowed: byId.allow !== false,
|
}
|
||||||
requireMention: byId.requireMention,
|
|
||||||
skills: byId.skills,
|
export function resolveDiscordChannelConfigWithFallback(params: {
|
||||||
enabled: byId.enabled,
|
guildInfo?: DiscordGuildEntryResolved | null;
|
||||||
users: byId.users,
|
channelId: string;
|
||||||
systemPrompt: byId.systemPrompt,
|
channelName?: string;
|
||||||
autoThread: byId.autoThread,
|
channelSlug: string;
|
||||||
};
|
parentId?: string;
|
||||||
if (channelSlug && channels[channelSlug]) {
|
parentName?: string;
|
||||||
const entry = channels[channelSlug];
|
parentSlug?: string;
|
||||||
return {
|
}): DiscordChannelConfigResolved | null {
|
||||||
allowed: entry.allow !== false,
|
const { guildInfo, channelId, channelName, channelSlug, parentId, parentName, parentSlug } =
|
||||||
requireMention: entry.requireMention,
|
params;
|
||||||
skills: entry.skills,
|
const channels = guildInfo?.channels;
|
||||||
enabled: entry.enabled,
|
if (!channels) return null;
|
||||||
users: entry.users,
|
const entry = resolveDiscordChannelEntry(channels, channelId, channelName, channelSlug);
|
||||||
systemPrompt: entry.systemPrompt,
|
if (entry) return resolveDiscordChannelConfigEntry(entry);
|
||||||
autoThread: entry.autoThread,
|
if (parentId || parentName || parentSlug) {
|
||||||
};
|
const resolvedParentSlug = parentSlug ?? (parentName ? normalizeDiscordSlug(parentName) : "");
|
||||||
}
|
const parentEntry = resolveDiscordChannelEntry(
|
||||||
if (channelName && channels[channelName]) {
|
channels,
|
||||||
const entry = channels[channelName];
|
parentId ?? "",
|
||||||
return {
|
parentName,
|
||||||
allowed: entry.allow !== false,
|
resolvedParentSlug,
|
||||||
requireMention: entry.requireMention,
|
);
|
||||||
skills: entry.skills,
|
if (parentEntry) return resolveDiscordChannelConfigEntry(parentEntry);
|
||||||
enabled: entry.enabled,
|
|
||||||
users: entry.users,
|
|
||||||
systemPrompt: entry.systemPrompt,
|
|
||||||
autoThread: entry.autoThread,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return { allowed: false };
|
return { allowed: false };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
isDiscordGroupAllowedByPolicy,
|
isDiscordGroupAllowedByPolicy,
|
||||||
normalizeDiscordAllowList,
|
normalizeDiscordAllowList,
|
||||||
normalizeDiscordSlug,
|
normalizeDiscordSlug,
|
||||||
resolveDiscordChannelConfig,
|
resolveDiscordChannelConfigWithFallback,
|
||||||
resolveDiscordGuildEntry,
|
resolveDiscordGuildEntry,
|
||||||
resolveDiscordShouldRequireMention,
|
resolveDiscordShouldRequireMention,
|
||||||
resolveDiscordUserAllowed,
|
resolveDiscordUserAllowed,
|
||||||
@@ -236,13 +236,19 @@ export async function preflightDiscordMessage(
|
|||||||
guildInfo?.slug ||
|
guildInfo?.slug ||
|
||||||
(params.data.guild?.name ? normalizeDiscordSlug(params.data.guild.name) : "");
|
(params.data.guild?.name ? normalizeDiscordSlug(params.data.guild.name) : "");
|
||||||
|
|
||||||
|
const threadChannelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
|
||||||
|
const threadParentSlug = threadParentName ? normalizeDiscordSlug(threadParentName) : "";
|
||||||
|
|
||||||
const baseSessionKey = route.sessionKey;
|
const baseSessionKey = route.sessionKey;
|
||||||
const channelConfig = isGuildMessage
|
const channelConfig = isGuildMessage
|
||||||
? resolveDiscordChannelConfig({
|
? resolveDiscordChannelConfigWithFallback({
|
||||||
guildInfo,
|
guildInfo,
|
||||||
channelId: threadParentId ?? message.channelId,
|
channelId: message.channelId,
|
||||||
channelName: configChannelName,
|
channelName,
|
||||||
channelSlug: configChannelSlug,
|
channelSlug: threadChannelSlug,
|
||||||
|
parentId: threadParentId ?? undefined,
|
||||||
|
parentName: threadParentName ?? undefined,
|
||||||
|
parentSlug: threadParentSlug,
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
if (isGuildMessage && channelConfig?.enabled === false) {
|
if (isGuildMessage && channelConfig?.enabled === false) {
|
||||||
|
|||||||
@@ -47,11 +47,13 @@ import {
|
|||||||
isDiscordGroupAllowedByPolicy,
|
isDiscordGroupAllowedByPolicy,
|
||||||
normalizeDiscordAllowList,
|
normalizeDiscordAllowList,
|
||||||
normalizeDiscordSlug,
|
normalizeDiscordSlug,
|
||||||
resolveDiscordChannelConfig,
|
resolveDiscordChannelConfigWithFallback,
|
||||||
resolveDiscordGuildEntry,
|
resolveDiscordGuildEntry,
|
||||||
resolveDiscordUserAllowed,
|
resolveDiscordUserAllowed,
|
||||||
} from "./allow-list.js";
|
} from "./allow-list.js";
|
||||||
import { formatDiscordUserTag } from "./format.js";
|
import { formatDiscordUserTag } from "./format.js";
|
||||||
|
import { resolveDiscordChannelInfo } from "./message-utils.js";
|
||||||
|
import { resolveDiscordThreadParentInfo } from "./threading.js";
|
||||||
|
|
||||||
type DiscordConfig = NonNullable<ClawdbotConfig["channels"]>["discord"];
|
type DiscordConfig = NonNullable<ClawdbotConfig["channels"]>["discord"];
|
||||||
|
|
||||||
@@ -499,8 +501,13 @@ async function dispatchDiscordCommandInteraction(params: {
|
|||||||
const channelType = channel?.type;
|
const channelType = channel?.type;
|
||||||
const isDirectMessage = channelType === ChannelType.DM;
|
const isDirectMessage = channelType === ChannelType.DM;
|
||||||
const isGroupDm = channelType === ChannelType.GroupDM;
|
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 channelName = channel && "name" in channel ? (channel.name as string) : undefined;
|
||||||
const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
|
const channelSlug = channelName ? normalizeDiscordSlug(channelName) : "";
|
||||||
|
const rawChannelId = channel?.id ?? "";
|
||||||
const ownerAllowList = normalizeDiscordAllowList(discordConfig?.dm?.allowFrom ?? [], [
|
const ownerAllowList = normalizeDiscordAllowList(discordConfig?.dm?.allowFrom ?? [], [
|
||||||
"discord:",
|
"discord:",
|
||||||
"user:",
|
"user:",
|
||||||
@@ -517,12 +524,35 @@ async function dispatchDiscordCommandInteraction(params: {
|
|||||||
guild: interaction.guild ?? undefined,
|
guild: interaction.guild ?? undefined,
|
||||||
guildEntries: discordConfig?.guilds,
|
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
|
const channelConfig = interaction.guild
|
||||||
? resolveDiscordChannelConfig({
|
? resolveDiscordChannelConfigWithFallback({
|
||||||
guildInfo,
|
guildInfo,
|
||||||
channelId: channel?.id ?? "",
|
channelId: rawChannelId,
|
||||||
channelName,
|
channelName,
|
||||||
channelSlug,
|
channelSlug,
|
||||||
|
parentId: threadParentId,
|
||||||
|
parentName: threadParentName,
|
||||||
|
parentSlug: threadParentSlug,
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
if (channelConfig?.enabled === false) {
|
if (channelConfig?.enabled === false) {
|
||||||
@@ -664,7 +694,7 @@ async function dispatchDiscordCommandInteraction(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isGuild = Boolean(interaction.guild);
|
const isGuild = Boolean(interaction.guild);
|
||||||
const channelId = channel?.id ?? "unknown";
|
const channelId = rawChannelId || "unknown";
|
||||||
const interactionId = interaction.rawData.id;
|
const interactionId = interaction.rawData.id;
|
||||||
const route = resolveAgentRoute({
|
const route = resolveAgentRoute({
|
||||||
cfg,
|
cfg,
|
||||||
|
|||||||
Reference in New Issue
Block a user