diff --git a/CHANGELOG.md b/CHANGELOG.md index a843ebdabbb..71f6e183465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/extensions/discord/src/voice/command.test.ts b/extensions/discord/src/voice/command.test.ts index 8d3dc5f5a88..8225468bc1e 100644 --- a/extensions/discord/src/voice/command.test.ts +++ b/extensions/discord/src/voice/command.test.ts @@ -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, + }); + }); }); diff --git a/extensions/discord/src/voice/command.ts b/extensions/discord/src/voice/command.ts index 7b5e63eb203..185ba326d34 100644 --- a/extensions/discord/src/voice/command.ts +++ b/extensions/discord/src/voice/command.ts @@ -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;