mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 18:50:42 +00:00
fix(discord): prefer latest voice auto-join channel
This commit is contained in:
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Slack: route handled top-level channel turns in implicit-conversation channels to thread-scoped sessions when Slack reply threading is enabled, keeping the root turn and later thread replies on one OpenClaw session. (#78522) Thanks @zeroth-blip.
|
||||
- Telegram: re-probe the primary fetch transport after repeated sticky fallback success so transient IPv4 or pinned-IP fallback promotion can recover without a gateway restart. Fixes #77088. (#77157) Thanks @MkDev11.
|
||||
- Runtime/install: raise the supported Node 22 floor to `22.16+` so native SQLite query handling can rely on the `node:sqlite` statement metadata API while continuing to recommend Node 24. (#78921)
|
||||
- Discord/voice: make duplicate same-guild auto-join entries resolve to the last configured channel so moving an agent between voice channels does not keep joining the stale channel.
|
||||
- Discord/voice: include a bounded one-line STT transcript preview in verbose voice logs so live voice debugging shows what speakers said before the agent reply.
|
||||
- Codex app-server: pin the managed Codex harness and Codex CLI smoke package to `@openai/codex@0.129.0`, defer OpenClaw integration dynamic tools behind Codex tool search by default, and accept current Codex service-tier values so legacy `fast` settings survive the stable harness upgrade as `priority`.
|
||||
- Codex app-server: default implicit local stdio app-server permissions to guardian when Codex system requirements disallow the YOLO approval, reviewer, or sandbox value, including hostname-scoped remote sandbox entries, avoiding turn-start failures on managed hosts that permit only reviewed approval or narrower sandboxes.
|
||||
|
||||
@@ -1206,6 +1206,7 @@ Notes:
|
||||
- Voice transcript turns derive owner status from Discord `allowFrom` (or `dm.allowFrom`); non-owner speakers cannot access owner-only tools (for example `gateway` and `cron`).
|
||||
- Discord voice is opt-in for text-only configs; set `channels.discord.voice.enabled=true` (or keep an existing `channels.discord.voice` block) to enable `/vc` commands, the voice runtime, and the `GuildVoiceStates` gateway intent.
|
||||
- `channels.discord.intents.voiceStates` can explicitly override voice-state intent subscription. Leave it unset for the intent to follow effective voice enablement.
|
||||
- If `voice.autoJoin` has multiple entries for the same guild, OpenClaw joins the last configured channel for that guild.
|
||||
- `voice.daveEncryption` and `voice.decryptionFailureTolerance` pass through to `@discordjs/voice` join options.
|
||||
- `@discordjs/voice` defaults are `daveEncryption=true` and `decryptionFailureTolerance=24` if unset.
|
||||
- `voice.connectTimeoutMs` controls the initial `@discordjs/voice` Ready wait for `/vc join` and auto-join attempts. Default: `30000`.
|
||||
|
||||
@@ -389,6 +389,29 @@ describe("DiscordVoiceManager", () => {
|
||||
expectConnectedStatus(manager, "1001");
|
||||
});
|
||||
|
||||
it("autoJoin uses the last configured channel for duplicate guild entries", async () => {
|
||||
const manager = createManager({
|
||||
voice: {
|
||||
enabled: true,
|
||||
autoJoin: [
|
||||
{ guildId: "g1", channelId: "1001" },
|
||||
{ guildId: "g1", channelId: "1002" },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
await manager.autoJoin();
|
||||
|
||||
expect(joinVoiceChannelMock).toHaveBeenCalledTimes(1);
|
||||
expect(joinVoiceChannelMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
guildId: "g1",
|
||||
channelId: "1002",
|
||||
}),
|
||||
);
|
||||
expectConnectedStatus(manager, "1002");
|
||||
});
|
||||
|
||||
it("does not throw when stale tracked voice connections are already destroyed", async () => {
|
||||
const staleConnection = createConnectionMock();
|
||||
staleConnection.state.status = "destroyed";
|
||||
|
||||
@@ -134,21 +134,32 @@ export class DiscordVoiceManager {
|
||||
}
|
||||
this.autoJoinTask = (async () => {
|
||||
const entries = this.params.discordConfig.voice?.autoJoin ?? [];
|
||||
logVoiceVerbose(`autoJoin: ${entries.length} entries`);
|
||||
const seenGuilds = new Set<string>();
|
||||
const entriesByGuild = new Map<string, { guildId: string; channelId: string }>();
|
||||
const duplicateGuilds = new Set<string>();
|
||||
for (const entry of entries) {
|
||||
const guildId = entry.guildId.trim();
|
||||
if (!guildId) {
|
||||
const channelId = entry.channelId.trim();
|
||||
if (!guildId || !channelId) {
|
||||
continue;
|
||||
}
|
||||
if (seenGuilds.has(guildId)) {
|
||||
if (entriesByGuild.has(guildId)) {
|
||||
duplicateGuilds.add(guildId);
|
||||
}
|
||||
entriesByGuild.set(guildId, { guildId, channelId });
|
||||
}
|
||||
|
||||
logVoiceVerbose(`autoJoin: ${entries.length} entries, ${entriesByGuild.size} guilds`);
|
||||
for (const guildId of duplicateGuilds) {
|
||||
const selected = entriesByGuild.get(guildId);
|
||||
if (selected) {
|
||||
logger.warn(
|
||||
`discord voice: autoJoin has multiple entries for guild ${guildId}; skipping`,
|
||||
`discord voice: autoJoin has multiple entries for guild ${guildId}; using channel ${selected.channelId}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
seenGuilds.add(guildId);
|
||||
logVoiceVerbose(`autoJoin: joining guild ${guildId} channel ${entry.channelId}`);
|
||||
}
|
||||
|
||||
for (const entry of entriesByGuild.values()) {
|
||||
logVoiceVerbose(`autoJoin: joining guild ${entry.guildId} channel ${entry.channelId}`);
|
||||
await this.join({
|
||||
guildId: entry.guildId,
|
||||
channelId: entry.channelId,
|
||||
|
||||
Reference in New Issue
Block a user