fix(discord): suppress bound thread webhook copies

This commit is contained in:
Peter Steinberger
2026-05-02 05:29:33 +01:00
parent 66d8fcea99
commit bca4e440bb
3 changed files with 62 additions and 15 deletions

View File

@@ -146,16 +146,19 @@ export function shouldIgnoreBoundThreadWebhookMessage(params: {
normalizeOptionalString(params.threadBinding?.webhookId) ??
normalizeOptionalString(params.threadBinding?.metadata?.webhookId) ??
"";
if (!boundWebhookId) {
const threadId = normalizeOptionalString(params.threadId) ?? "";
if (!threadId) {
return false;
}
return isRecentlyUnboundThreadWebhookMessage({
accountId: params.accountId,
threadId,
webhookId,
});
if (boundWebhookId && webhookId === boundWebhookId) {
return true;
}
return webhookId === boundWebhookId;
const threadId = normalizeOptionalString(params.threadId) ?? "";
if (!threadId) {
return false;
}
if (params.threadBinding) {
return true;
}
return isRecentlyUnboundThreadWebhookMessage({
accountId: params.accountId,
threadId,
webhookId,
});
}

View File

@@ -624,7 +624,7 @@ describe("preflightDiscordMessage", () => {
expect(result?.boundSessionKey).toBe(threadBinding.targetSessionKey);
});
it("drops hydrated bound-thread webhook echoes after fetching an empty payload", async () => {
it("drops hydrated bound-thread webhook copies after fetching an empty payload", async () => {
const threadBinding = createThreadBinding({
targetKind: "session",
targetSessionKey: "agent:main:acp:discord-thread-1",
@@ -685,6 +685,38 @@ describe("preflightDiscordMessage", () => {
expect(result).toBeNull();
});
it("drops bound-thread webhook copies from other webhook ids", async () => {
const threadBinding = createThreadBinding({
targetKind: "session",
targetSessionKey: "agent:main:acp:discord-thread-1",
});
const threadId = "thread-webhook-proxy-1";
const parentId = "channel-parent-webhook-proxy-1";
const message = createDiscordMessage({
id: "m-webhook-proxy-1",
channelId: threadId,
content: "proxied user message",
webhook_id: "pluralkit-webhook-1",
author: {
id: "relay-bot-1",
bot: true,
username: "Proxy",
},
});
const result = await runThreadBoundPreflight({
threadId,
parentId,
message,
threadBinding,
discordConfig: {
allowBots: true,
} as DiscordConfig,
});
expect(result).toBeNull();
});
it("bypasses mention gating in bound threads for allowed bot senders", async () => {
const threadBinding = createThreadBinding();
const threadId = "thread-bot-focus";
@@ -1445,18 +1477,20 @@ describe("shouldIgnoreBoundThreadWebhookMessage", () => {
).toBe(true);
});
it("returns false when webhook ids differ", () => {
it("returns true when a bound thread receives a different webhook id", () => {
expect(
shouldIgnoreBoundThreadWebhookMessage({
threadId: "thread-1",
webhookId: "wh-other",
threadBinding: createThreadBinding(),
}),
).toBe(false);
).toBe(true);
});
it("returns false when there is no bound thread webhook", () => {
it("returns true when a bound thread receives a webhook without a recorded bound webhook id", () => {
expect(
shouldIgnoreBoundThreadWebhookMessage({
threadId: "thread-1",
webhookId: "wh-1",
threadBinding: createThreadBinding({
metadata: {
@@ -1464,6 +1498,15 @@ describe("shouldIgnoreBoundThreadWebhookMessage", () => {
},
}),
}),
).toBe(true);
});
it("returns false for differing webhook ids without a known thread id", () => {
expect(
shouldIgnoreBoundThreadWebhookMessage({
webhookId: "wh-other",
threadBinding: createThreadBinding(),
}),
).toBe(false);
});