fix(discord): short-circuit bound thread self-loop drops

This commit is contained in:
Gustavo Madeira Santana
2026-04-05 19:11:29 -04:00
parent 40c499d489
commit 95079949c3

View File

@@ -122,6 +122,56 @@ function isBoundThreadBotSystemMessage(params: {
return DISCORD_BOUND_THREAD_SYSTEM_PREFIXES.some((prefix) => text.startsWith(prefix));
}
type BoundThreadLookupRecordLike = {
webhookId?: string | null;
metadata?: {
webhookId?: string | null;
};
};
function isDiscordThreadChannelType(type: ChannelType | undefined): boolean {
return (
type === ChannelType.PublicThread ||
type === ChannelType.PrivateThread ||
type === ChannelType.AnnouncementThread
);
}
function isDiscordThreadChannelMessage(params: {
isGuildMessage: boolean;
message: Message;
channelInfo: import("./message-utils.js").DiscordChannelInfo | null;
}): boolean {
if (!params.isGuildMessage) {
return false;
}
const channel =
"channel" in params.message ? (params.message as { channel?: unknown }).channel : undefined;
return Boolean(
(channel &&
typeof channel === "object" &&
"isThread" in channel &&
typeof (channel as { isThread?: unknown }).isThread === "function" &&
(channel as { isThread: () => boolean }).isThread()) ||
isDiscordThreadChannelType(params.channelInfo?.type),
);
}
function resolveInjectedBoundThreadLookupRecord(params: {
threadBindings: DiscordMessagePreflightParams["threadBindings"];
threadId: string;
}): BoundThreadLookupRecordLike | undefined {
const getByThreadId = (params.threadBindings as { getByThreadId?: (threadId: string) => unknown })
.getByThreadId;
if (typeof getByThreadId !== "function") {
return undefined;
}
const binding = getByThreadId(params.threadId);
return binding && typeof binding === "object"
? (binding as BoundThreadLookupRecordLike)
: undefined;
}
function resolveDiscordMentionState(params: {
authorIsBot: boolean;
botId?: string;
@@ -180,16 +230,18 @@ export function shouldIgnoreBoundThreadWebhookMessage(params: {
accountId?: string;
threadId?: string;
webhookId?: string | null;
threadBinding?: SessionBindingRecord;
threadBinding?: BoundThreadLookupRecordLike;
}): boolean {
const webhookId = params.webhookId?.trim() || "";
if (!webhookId) {
return false;
}
const boundWebhookId =
typeof params.threadBinding?.metadata?.webhookId === "string"
? params.threadBinding.metadata.webhookId.trim()
: "";
typeof params.threadBinding?.webhookId === "string"
? params.threadBinding.webhookId.trim()
: typeof params.threadBinding?.metadata?.webhookId === "string"
? params.threadBinding.metadata.webhookId.trim()
: "";
if (!boundWebhookId) {
const threadId = params.threadId?.trim() || "";
if (!threadId) {
@@ -371,6 +423,43 @@ export async function preflightDiscordMessage(
}
const isDirectMessage = channelInfo?.type === ChannelType.DM;
const isGroupDm = channelInfo?.type === ChannelType.GroupDM;
const messageText = resolveDiscordMessageText(message, {
includeForwarded: true,
});
const injectedBoundThreadBinding =
!isDirectMessage && !isGroupDm
? resolveInjectedBoundThreadLookupRecord({
threadBindings: params.threadBindings,
threadId: messageChannelId,
})
: undefined;
if (
shouldIgnoreBoundThreadWebhookMessage({
accountId: params.accountId,
threadId: messageChannelId,
webhookId,
threadBinding: injectedBoundThreadBinding,
})
) {
logVerbose(`discord: drop bound-thread webhook echo message ${message.id}`);
return null;
}
if (
isBoundThreadBotSystemMessage({
isBoundThreadSession:
Boolean(injectedBoundThreadBinding) &&
isDiscordThreadChannelMessage({
isGuildMessage,
message,
channelInfo,
}),
isBotAuthor: Boolean(author.bot),
text: messageText,
})
) {
logVerbose(`discord: drop bound-thread bot system message ${message.id}`);
return null;
}
const data = message === params.data.message ? params.data : { ...params.data, message };
logDebug(
`[discord-preflight] channelId=${messageChannelId} guild_id=${params.data.guild_id} channelType=${channelInfo?.type} isGuild=${isGuildMessage} isDM=${isDirectMessage} isGroupDm=${isGroupDm}`,
@@ -461,9 +550,6 @@ export async function preflightDiscordMessage(
const baseText = resolveDiscordMessageText(message, {
includeForwarded: false,
});
const messageText = resolveDiscordMessageText(message, {
includeForwarded: true,
});
// Intercept text-only slash commands (e.g. user typing "/reset" instead of using Discord's slash command picker)
// These should not be forwarded to the agent; proper slash command interactions are handled elsewhere