




















@@ -634,6 +634,149 @@ describe("preflightDiscordMessage", () => {
634634).toBe("default");
635635});
636636637+it("passes bot-loop protection facts for accepted bot-authored Discord messages (#58789)", async () => {
638+const channelId = "channel-bot-loop";
639+const guildId = "guild-bot-loop";
640+const senderBotId = "relay-bot-1";
641+const messageTimestamp = "2026-05-13T05:00:00.000Z";
642+643+const message = createDiscordMessage({
644+id: "m-loop-1",
645+ channelId,
646+content: "chatter <@openclaw-bot>",
647+mentionedUsers: [{ id: "openclaw-bot" }],
648+author: { id: senderBotId, bot: true, username: "Relay" },
649+timestamp: messageTimestamp,
650+});
651+const result = await preflightDiscordMessage(
652+createPreflightArgs({
653+cfg: DEFAULT_PREFLIGHT_CFG,
654+discordConfig: {
655+allowBots: true,
656+botLoopProtection: {
657+enabled: true,
658+maxEventsPerWindow: 3,
659+cooldownSeconds: 60,
660+},
661+} as DiscordConfig,
662+data: createGuildEvent({
663+ channelId,
664+ guildId,
665+author: message.author,
666+ message,
667+}),
668+client: createGuildTextClient(channelId),
669+}),
670+);
671+672+expect(expectPreflightResult(result).botLoopProtection).toEqual({
673+scopeId: "default",
674+conversationId: channelId,
675+senderId: senderBotId,
676+receiverId: "openclaw-bot",
677+config: {
678+enabled: true,
679+maxEventsPerWindow: 3,
680+cooldownSeconds: 60,
681+},
682+defaultsConfig: undefined,
683+defaultEnabled: true,
684+nowMs: Date.parse(messageTimestamp),
685+});
686+});
687+688+it("passes generic channel defaults for Discord bot loop budgets", async () => {
689+const channelId = "channel-bot-loop-defaults";
690+const guildId = "guild-bot-loop-defaults";
691+const discordConfig = { allowBots: true } as DiscordConfig;
692+const message = createDiscordMessage({
693+id: "m-loop-default-1",
694+ channelId,
695+content: "relay <@openclaw-bot>",
696+mentionedUsers: [{ id: "openclaw-bot" }],
697+author: { id: "relay-bot-defaults", bot: true, username: "Relay" },
698+});
699+const result = await runGuildPreflight({
700+ channelId,
701+ guildId,
702+ message,
703+ discordConfig,
704+cfg: {
705+ ...DEFAULT_PREFLIGHT_CFG,
706+channels: {
707+defaults: {
708+botLoopProtection: {
709+maxEventsPerWindow: 1,
710+cooldownSeconds: 60,
711+},
712+},
713+},
714+},
715+});
716+717+expect(expectPreflightResult(result).botLoopProtection?.defaultsConfig).toEqual({
718+maxEventsPerWindow: 1,
719+cooldownSeconds: 60,
720+});
721+});
722+723+it("does not prepare loop-guard facts for bot messages that later preflight gates drop (#58789)", async () => {
724+const channelId = "channel-bot-loop-dropped";
725+const guildId = "guild-bot-loop-dropped";
726+const senderBotId = "relay-bot-dropped";
727+const discordConfig = {
728+allowBots: true,
729+botLoopProtection: {
730+enabled: true,
731+maxEventsPerWindow: 1,
732+cooldownSeconds: 60,
733+},
734+} as DiscordConfig;
735+const guildEntries = {
736+[guildId]: {
737+requireMention: false,
738+ignoreOtherMentions: true,
739+},
740+};
741+742+for (const messageId of ["m-dropped-1", "m-dropped-2"]) {
743+const message = createDiscordMessage({
744+id: messageId,
745+ channelId,
746+content: `cc <@999> ${messageId}`,
747+mentionedUsers: [{ id: "999" }],
748+author: { id: senderBotId, bot: true, username: "Relay" },
749+});
750+751+expect(
752+await runGuildPreflight({
753+ channelId,
754+ guildId,
755+ message,
756+ discordConfig,
757+ guildEntries,
758+}),
759+).toBeNull();
760+}
761+762+const validMessage = createDiscordMessage({
763+id: "m-valid-after-dropped",
764+ channelId,
765+content: "legitimate bot relay",
766+author: { id: senderBotId, bot: true, username: "Relay" },
767+});
768+769+expect(
770+await runGuildPreflight({
771+ channelId,
772+ guildId,
773+message: validMessage,
774+ discordConfig,
775+ guildEntries,
776+}),
777+).not.toBeNull();
778+});
779+637780it("keeps bound-thread regular bot messages flowing when allowBots=true", async () => {
638781const threadBinding = createThreadBinding({
639782targetKind: "session",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。