fix(discord): route guild text commands (#78080)

This commit is contained in:
Peter Steinberger
2026-05-06 04:56:09 +01:00
parent b5c33bc204
commit d7bd9fe049
4 changed files with 93 additions and 8 deletions

View File

@@ -102,6 +102,7 @@ Docs: https://docs.openclaw.ai
### Fixes
- Discord/gateway: measure heartbeat ACK timeouts from the actual heartbeat send, preventing late initial heartbeats from triggering false reconnect loops while the channel is still awaiting readiness. Fixes #77668. (#78087) Thanks @bryce-d-greybeard and @NikolaFC.
- Discord/guilds: route plain text control commands such as `/steer` through the normal authorization and mention gate instead of silently dropping them before an agent session can see them. Fixes #78080. Thanks @ramitrkar-hash.
- Control UI/Sessions: make the compaction count a compact `N Checkpoint(s)` disclosure and show expanded session-level details with modern checkpoint history cards across responsive table layouts. Thanks @BunsDev.
- Control UI/performance: keep chat and channel tabs responsive while history payloads and channel probes are slow, label partial channel status, and record slow chat/config render timings in the event log. Thanks @BunsDev.
- Control UI/sessions: fire the documented `/new` command and lifecycle hooks only for explicit Control UI session creation, restoring session-memory and custom hook capture without changing SDK parent-session creates. Fixes #76957. Thanks @BunsDev.

View File

@@ -65,9 +65,11 @@ export function createDiscordMessage(params: {
mentionedEveryone?: boolean;
attachments?: Array<Record<string, unknown>>;
webhookId?: string;
type?: import("../internal/discord.js").MessageType;
}): import("../internal/discord.js").Message {
return {
id: params.id,
type: params.type,
content: params.content,
timestamp: new Date().toISOString(),
channelId: params.channelId,

View File

@@ -1,5 +1,5 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { ChannelType } from "../internal/discord.js";
import { ChannelType, MessageType } from "../internal/discord.js";
import { createPartialDiscordChannelWithThrowingGetters } from "../test-support/partial-channel.js";
const transcribeFirstAudioMock = vi.hoisted(() => vi.fn());
@@ -966,6 +966,95 @@ describe("preflightDiscordMessage", () => {
expect(result).not.toBeNull();
});
it("routes ordinary guild text control commands through authorization instead of dropping them", async () => {
const channelId = "channel-text-control-command";
const guildId = "guild-text-control-command";
const message = createDiscordMessage({
id: "m-text-control-command",
channelId,
content: "/steer keep digging",
author: {
id: "user-1",
bot: false,
username: "Alice",
},
});
const result = await preflightDiscordMessage({
...createPreflightArgs({
cfg: DEFAULT_PREFLIGHT_CFG,
discordConfig: {} as DiscordConfig,
data: createGuildEvent({
channelId,
guildId,
author: message.author,
message,
}),
client: createGuildTextClient(channelId),
}),
allowFrom: ["discord:user-1"],
guildEntries: {
[guildId]: {
channels: {
[channelId]: {
enabled: true,
requireMention: true,
},
},
},
},
});
expect(result).not.toBeNull();
expect(result?.baseText).toBe("/steer keep digging");
expect(result?.commandAuthorized).toBe(true);
expect(result?.shouldRequireMention).toBe(true);
expect(result?.shouldBypassMention).toBe(true);
});
it("still drops Discord native command echo messages", async () => {
const channelId = "channel-native-command-echo";
const guildId = "guild-native-command-echo";
const message = createDiscordMessage({
id: "m-native-command-echo",
channelId,
content: "/steer keep digging",
type: MessageType.ChatInputCommand,
author: {
id: "user-1",
bot: false,
username: "Alice",
},
});
const result = await preflightDiscordMessage({
...createPreflightArgs({
cfg: DEFAULT_PREFLIGHT_CFG,
discordConfig: {} as DiscordConfig,
data: createGuildEvent({
channelId,
guildId,
author: message.author,
message,
}),
client: createGuildTextClient(channelId),
}),
allowFrom: ["discord:user-1"],
guildEntries: {
[guildId]: {
channels: {
[channelId]: {
enabled: true,
requireMention: true,
},
},
},
},
});
expect(result).toBeNull();
});
it("does not mask mention gating when bot id is missing but mention patterns can detect", async () => {
const channelId = "channel-missing-bot-id-mention-gate";
const guildId = "guild-missing-bot-id-mention-gate";

View File

@@ -230,13 +230,6 @@ export async function preflightDiscordMessage(
includeForwarded: false,
});
// 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
if (!isDirectMessage && baseText && hasControlCommand(baseText, params.cfg)) {
logVerbose(`discord: drop text-based slash command ${message.id} (intercepted at gateway)`);
return null;
}
recordChannelActivity({
channel: "discord",
accountId: params.accountId,