fix: harden Discord voice commands in threads

This commit is contained in:
Peter Steinberger
2026-04-22 18:49:21 +01:00
parent 9d66a900e5
commit a12fcd3f18
3 changed files with 38 additions and 7 deletions

View File

@@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai
- Codex harness: apply the GPT-5 behavior and heartbeat prompt overlay to native Codex app-server runs, so `codex/gpt-5.x` sessions get the same follow-through, tool-use, and proactive heartbeat guidance as OpenAI GPT-5 runs.
- OpenAI/Responses: keep embedded OpenAI Responses runs on HTTP when `models.providers.openai.baseUrl` points at a local mock or other non-public endpoint, so mocked/custom endpoints no longer drift onto the hardcoded public websocket transport. (#69815) Thanks @vincentkoc.
- Channels/config: require resolved runtime config on channel send/action/client helpers and block runtime helper `loadConfig()` calls, so SecretRefs are resolved at startup/boundaries instead of being re-read during sends.
- Discord: pass resolved runtime config through guild and moderation action helpers, so thread-originated Discord commands can run channel, member, role, and guild actions without falling back to runtime config reads. (#70215) Thanks @szponeczek.
- CLI/channels: preserve bundled setup promotion metadata when a loaded partial channel plugin omits it, so adding a non-default account still moves legacy single-account fields such as Telegram `streaming` into `accounts.default`.
- Telegram: keep the sent-message ownership cache isolated per configured session store, so own-message reaction filtering remains correct with custom `session.store` paths.
- Security/update: fail closed when exact pinned npm plugin or hook-pack updates detect integrity drift, and expose aborted plugin drift details in `openclaw update --json`.
@@ -35,6 +36,7 @@ Docs: https://docs.openclaw.ai
- Memory-core/dreaming: suppress the startup-only managed dreaming cron unavailable warning when the cron service is still attaching, while preserving the runtime warning if cron genuinely remains unavailable. Fixes #69939. (#69941) Thanks @Sanjays2402.
- Mattermost: suppress reasoning-only payloads even when they arrive as blockquoted `> Reasoning:` text, preventing `/reasoning on` from leaking thinking into channel posts. (#69927) Thanks @lawrence3699.
- Discord: read `channel.parentId` through a safe accessor in the slash-command, reaction, and model-picker paths so partial `GuildThreadChannel` prototype getters no longer throw `Cannot access rawData on partial Channel` when commands like `/new` run from inside a thread. Fixes #69861. (#69908) Thanks @neeravmakwana.
- Discord: use safe channel name and parent accessors across voice command authorization, so `/vc` commands from partial Discord thread channels no longer crash on Carbon rawData getters. (#70199) Thanks @hanamizuki.
- Browser/Chrome MCP: reset cached existing-session control sessions when a `navigate_page` call times out, so one stuck navigation no longer poisons the browser profile until a gateway restart. (#69733) Thanks @ayeshakhalid192007-dev.
- Browser/Chrome MCP: propagate click timeouts and abort signals to existing-session actions so a stuck click fails fast and reconnects instead of poisoning the browser tool until gateway restart. (#63524) Thanks @dongseok0.
- Gateway/channel health: base stale-socket recovery on provider-proven transport activity instead of inbound app-event freshness, preventing quiet Slack, Discord, Telegram, Matrix, and local-style channels from being restarted solely because no user traffic arrived. (#69833) Thanks @bek91.

View File

@@ -96,4 +96,38 @@ describe("createDiscordVoiceCommand", () => {
ephemeral: true,
});
});
it("vc status tolerates partial thread channels with throwing getters", async () => {
const statusSpy = vi.fn(() => []);
const manager = {
status: statusSpy,
} as unknown as DiscordVoiceManager;
const { status } = createVoiceCommandHarness(manager);
const partialChannel = { id: "123456789012345678" };
Object.defineProperties(partialChannel, {
name: {
get() {
throw new Error("Cannot access rawData on partial Channel");
},
},
parentId: {
get() {
throw new Error("Cannot access rawData on partial Channel");
},
},
});
const { interaction, reply } = createInteraction({
channel: partialChannel as CommandInteraction["channel"],
client: { fetchChannel: vi.fn(async () => null) } as unknown as CommandInteraction["client"],
guild: { id: "g1", name: "Guild" } as CommandInteraction["guild"],
});
await expect(status.run(interaction)).resolves.toBeUndefined();
expect(statusSpy).toHaveBeenCalledTimes(1);
expect(reply).toHaveBeenCalledWith({
content: "No active voice sessions.",
ephemeral: true,
});
});
});

View File

@@ -66,13 +66,8 @@ async function authorizeVoiceCommand(
}
const channelId = channelOverride?.id ?? channel?.id ?? "";
const rawChannelName =
channelOverride?.name ?? (channel && "name" in channel ? (channel.name as string) : undefined);
const rawParentId =
channelOverride?.parentId ??
("parentId" in (channel ?? {})
? ((channel as { parentId?: string }).parentId ?? undefined)
: undefined);
const rawChannelName = channelOverride?.name ?? resolveDiscordChannelNameSafe(channel);
const rawParentId = channelOverride?.parentId ?? resolveDiscordChannelParentIdSafe(channel);
const channelInfo = channelId
? await resolveDiscordChannelInfo(interaction.client, channelId)
: null;