build(discord): skip native opus builds by default (#80071)

This commit is contained in:
Peter Steinberger
2026-05-09 22:59:09 -04:00
committed by GitHub
parent 048ca8c765
commit 487687a6f0
8 changed files with 68 additions and 12 deletions

View File

@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
### Changes ### Changes
- Talk: add `talk.realtime.instructions` so operators can append realtime voice style instructions while preserving OpenClaw's built-in agent-consult guidance. (#79081) Thanks @VACInc. - Talk: add `talk.realtime.instructions` so operators can append realtime voice style instructions while preserving OpenClaw's built-in agent-consult guidance. (#79081) Thanks @VACInc.
- Discord/voice: default test and source installs to the pure-JS `opusscript` decoder by ignoring optional native `@discordjs/opus` builds, avoiding slow native addon compiles outside dedicated voice-performance lanes.
- Gateway/skills: add an opt-in private skill archive upload install path gated by `skills.install.allowUploadedArchives`, so trusted Gateway clients can stage and install zip-backed skills only when operators explicitly enable the code-install surface. (#74430) Thanks @samzong. - Gateway/skills: add an opt-in private skill archive upload install path gated by `skills.install.allowUploadedArchives`, so trusted Gateway clients can stage and install zip-backed skills only when operators explicitly enable the code-install surface. (#74430) Thanks @samzong.
- Dependencies: refresh workspace pins and patch targets, including ACPX `@agentclientprotocol/claude-agent-acp` `0.33.1`, Codex ACP `0.14.0`, Baileys `7.0.0-rc10`, Google GenAI `2.0.1`, OpenAI `6.37.0`, AWS SDK `3.1045.0`, Kysely `0.29.0`, Tlon skill `0.3.6`, Aimock `1.19.5`, and tsdown `0.22.0`. - Dependencies: refresh workspace pins and patch targets, including ACPX `@agentclientprotocol/claude-agent-acp` `0.33.1`, Codex ACP `0.14.0`, Baileys `7.0.0-rc10`, Google GenAI `2.0.1`, OpenAI `6.37.0`, AWS SDK `3.1045.0`, Kysely `0.29.0`, Tlon skill `0.3.6`, Aimock `1.19.5`, and tsdown `0.22.0`.
- Agents/compaction: preserve scoped background exec/process session references across embedded compaction and after-turn runtime contexts without exposing sessions from unrelated scopes. Fixes #79284. (#79307) Thanks @TurboTheTurtle. - Agents/compaction: preserve scoped background exec/process session references across embedded compaction and after-turn runtime contexts without exposing sessions from unrelated scopes. Fixes #79284. (#79307) Thanks @TurboTheTurtle.

View File

@@ -1213,6 +1213,7 @@ Notes:
- If `voice.autoJoin` has multiple entries for the same guild, OpenClaw joins the last configured channel for that guild. - 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. - `voice.daveEncryption` and `voice.decryptionFailureTolerance` pass through to `@discordjs/voice` join options.
- `@discordjs/voice` defaults are `daveEncryption=true` and `decryptionFailureTolerance=24` if unset. - `@discordjs/voice` defaults are `daveEncryption=true` and `decryptionFailureTolerance=24` if unset.
- OpenClaw defaults to the pure-JS `opusscript` decoder for Discord voice receive. The optional native `@discordjs/opus` package is ignored by the repo pnpm install policy so normal installs and tests do not compile a native addon; only opt into a native opus build in a dedicated voice-performance or live-lane environment.
- `voice.connectTimeoutMs` controls the initial `@discordjs/voice` Ready wait for `/vc join` and auto-join attempts. Default: `30000`. - `voice.connectTimeoutMs` controls the initial `@discordjs/voice` Ready wait for `/vc join` and auto-join attempts. Default: `30000`.
- `voice.reconnectGraceMs` controls how long OpenClaw waits for a disconnected voice session to begin reconnecting before destroying it. Default: `15000`. - `voice.reconnectGraceMs` controls how long OpenClaw waits for a disconnected voice session to begin reconnecting before destroying it. Default: `15000`.
- In `stt-tts` mode, voice playback does not stop just because another user starts speaking. To avoid feedback loops, OpenClaw ignores new voice capture while TTS is playing; speak after playback finishes for the next turn. Realtime modes forward speaker starts as barge-in signals to the realtime provider. - In `stt-tts` mode, voice playback does not stop just because another user starts speaking. To avoid feedback loops, OpenClaw ignores new voice capture while TTS is playing; speak after playback finishes for the next turn. Realtime modes forward speaker starts as barge-in signals to the realtime provider.

View File

@@ -443,6 +443,11 @@ Think of the suites as "increasing realism" (and increasing flakiness/cost):
real bundled plugin source APIs. Real plugin API loads belong in real bundled plugin source APIs. Real plugin API loads belong in
plugin-owned contract/integration suites. plugin-owned contract/integration suites.
Native dependency policy:
- Default test installs skip optional native Discord opus builds. Discord voice receive uses the pure-JS `opusscript` decoder, and `@discordjs/opus` stays in `ignoredBuiltDependencies` so local tests and Testbox lanes do not compile the native addon.
- Use a dedicated Discord voice performance or live lane if you intentionally need to compare a native opus build. Do not add `@discordjs/opus` back to the default `onlyBuiltDependencies`; that makes unrelated install/test loops compile native code.
<AccordionGroup> <AccordionGroup>
<Accordion title="Projects, shards, and scoped lanes"> <Accordion title="Projects, shards, and scoped lanes">

View File

@@ -0,0 +1,19 @@
import { Readable } from "node:stream";
import { describe, expect, it } from "vitest";
import { decodeOpusStream } from "./audio.js";
describe("discord voice opus decoder selection", () => {
it("prefers the pure-JS opusscript decoder over optional native opus", async () => {
const verbose: string[] = [];
const warnings: string[] = [];
const decoded = await decodeOpusStream(Readable.from([]), {
onVerbose: (message) => verbose.push(message),
onWarn: (message) => warnings.push(message),
});
expect(decoded.length).toBe(0);
expect(verbose).toContain("opus decoder: opusscript");
expect(warnings).toEqual([]);
});
});

View File

@@ -48,6 +48,16 @@ function resolveOpusDecoderFactory(params: {
onWarn: (message: string) => void; onWarn: (message: string) => void;
}): OpusDecoderFactory | null { }): OpusDecoderFactory | null {
const factories: OpusDecoderFactory[] = [ const factories: OpusDecoderFactory[] = [
{
name: "opusscript",
load: () => {
const OpusScript = require("opusscript") as {
new (sampleRate: number, channels: number, application: number): OpusDecoder;
Application: { AUDIO: number };
};
return new OpusScript(SAMPLE_RATE, CHANNELS, OpusScript.Application.AUDIO);
},
},
{ {
name: "@discordjs/opus", name: "@discordjs/opus",
load: () => { load: () => {
@@ -62,16 +72,6 @@ function resolveOpusDecoderFactory(params: {
return new DiscordOpus.OpusEncoder(SAMPLE_RATE, CHANNELS); return new DiscordOpus.OpusEncoder(SAMPLE_RATE, CHANNELS);
}, },
}, },
{
name: "opusscript",
load: () => {
const OpusScript = require("opusscript") as {
new (sampleRate: number, channels: number, application: number): OpusDecoder;
Application: { AUDIO: number };
};
return new OpusScript(SAMPLE_RATE, CHANNELS, OpusScript.Application.AUDIO);
},
},
]; ];
const failures: string[] = []; const failures: string[] = [];

View File

@@ -1820,7 +1820,6 @@
}, },
"onlyBuiltDependencies": [ "onlyBuiltDependencies": [
"@openclaw/fs-safe", "@openclaw/fs-safe",
"@discordjs/opus",
"@google/genai", "@google/genai",
"@lydell/node-pty", "@lydell/node-pty",
"@matrix-org/matrix-sdk-crypto-nodejs", "@matrix-org/matrix-sdk-crypto-nodejs",
@@ -1835,6 +1834,7 @@
"sharp" "sharp"
], ],
"ignoredBuiltDependencies": [ "ignoredBuiltDependencies": [
"@discordjs/opus",
"koffi", "koffi",
"tree-sitter-bash" "tree-sitter-bash"
], ],

View File

@@ -34,7 +34,6 @@ minimumReleaseAgeExclude:
onlyBuiltDependencies: onlyBuiltDependencies:
- "@openclaw/fs-safe" - "@openclaw/fs-safe"
- "@discordjs/opus"
- "@google/genai" - "@google/genai"
- "@lydell/node-pty" - "@lydell/node-pty"
- "@matrix-org/matrix-sdk-crypto-nodejs" - "@matrix-org/matrix-sdk-crypto-nodejs"
@@ -49,5 +48,6 @@ onlyBuiltDependencies:
- sharp - sharp
ignoredBuiltDependencies: ignoredBuiltDependencies:
- "@discordjs/opus"
- koffi - koffi
- tree-sitter-bash - tree-sitter-bash

View File

@@ -0,0 +1,30 @@
import fs from "node:fs";
import { describe, expect, it } from "vitest";
import { parse } from "yaml";
type PnpmBuildConfig = {
ignoredBuiltDependencies?: string[];
onlyBuiltDependencies?: string[];
};
type RootPackageJson = {
pnpm?: PnpmBuildConfig;
};
type WorkspaceConfig = PnpmBuildConfig;
function readJson<T>(filePath: string): T {
return JSON.parse(fs.readFileSync(filePath, "utf8")) as T;
}
describe("package manager build policy", () => {
it("keeps optional native Discord opus builds disabled by default", () => {
const packageJson = readJson<RootPackageJson>("package.json");
const workspace = parse(fs.readFileSync("pnpm-workspace.yaml", "utf8")) as WorkspaceConfig;
for (const config of [packageJson.pnpm, workspace]) {
expect(config?.ignoredBuiltDependencies ?? []).toContain("@discordjs/opus");
expect(config?.onlyBuiltDependencies ?? []).not.toContain("@discordjs/opus");
}
});
});