fix: drop discord opus dependency

This commit is contained in:
Shadow
2026-03-03 12:22:16 -06:00
parent 16ebbd24b5
commit b0bcea03db
6 changed files with 27 additions and 69 deletions

View File

@@ -20,6 +20,7 @@ Docs: https://docs.openclaw.ai
- Discord/presence defaults: send an online presence update on ready when no custom presence is configured so bots no longer appear offline by default. Thanks @thewilloftheshadow.
- Discord/typing cleanup: stop typing indicators after silent/NO_REPLY runs by marking the run complete before dispatch idle cleanup. Thanks @thewilloftheshadow.
- Discord/voice messages: request upload slots with JSON fetch calls so voice message uploads no longer fail with content-type errors. Thanks @thewilloftheshadow.
- Discord/voice decoder fallback: drop the native Opus dependency and use opusscript for voice decoding to avoid native-opus installs. Thanks @thewilloftheshadow.
- Discord/auto presence health signal: add runtime availability-driven presence updates plus connected-state reporting to improve health monitoring and operator visibility. (#33277) Thanks @thewilloftheshadow.
- Telegram/DM draft finalization reliability: require verified final-text draft emission before treating preview finalization as delivered, and fall back to normal payload send when final draft delivery is not confirmed (preventing missing final responses and preserving media/button delivery). (#32118) Thanks @OpenCils.
- Telegram/draft preview boundary + silent-token reliability: stabilize answer-lane message boundaries across late-partial/message-start races, preserve/reset finalized preview state at the correct boundaries, and suppress `NO_REPLY` lead-fragment leaks without broad heartbeat-prefix false positives. (#33169) Thanks @obviyus.
@@ -914,7 +915,7 @@ Docs: https://docs.openclaw.ai
- Agents/Kimi: classify Moonshot `Your request exceeded model token limit` failures as context overflows so auto-compaction and user-facing overflow recovery trigger correctly instead of surfacing raw invalid-request errors. (#9562) Thanks @danilofalcao.
- Providers/Moonshot: mark Kimi K2.5 as image-capable in implicit + onboarding model definitions, and refresh stale explicit provider capability fields (`input`/`reasoning`/context limits) from implicit catalogs so existing configs pick up Moonshot vision support without manual model rewrites. (#13135, #4459) Thanks @manikv12.
- Agents/Transcript: enable consecutive-user turn merging for strict non-OpenAI `openai-completions` providers (for example Moonshot/Kimi), reducing `roles must alternate` ordering failures on OpenAI-compatible endpoints while preserving current OpenRouter/Opencode behavior. (#7693) Thanks @steipete.
- Install/Discord Voice: make `@discordjs/opus` an optional dependency so `openclaw` install/update no longer hard-fails when native Opus builds fail, while keeping `opusscript` as the runtime fallback decoder for Discord voice flows. (#23737, #23733, #23703) Thanks @jeadland, @Sheetaa, and @Breakyman.
- Install/Discord Voice: make the native Opus decoder optional so `openclaw` install/update no longer hard-fails when native builds fail, while keeping `opusscript` as the runtime fallback decoder for Discord voice flows. (#23737, #23733, #23703) Thanks @jeadland, @Sheetaa, and @Breakyman.
- Docker/Setup: precreate `$OPENCLAW_CONFIG_DIR/identity` during `docker-setup.sh` so CLI commands that need device identity (for example `devices list`) avoid `EACCES ... /home/node/.openclaw/identity` failures on restrictive bind mounts. (#23948) Thanks @ackson-beep.
- Exec/Background: stop applying the default exec timeout to background sessions (`background: true` or explicit `yieldMs`) when no explicit timeout is set, so long-running background jobs are no longer terminated at the default timeout boundary. (#23303) Thanks @steipete.
- Slack/Threading: sessions: keep parent-session forking and thread-history context active beyond first turn by removing first-turn-only gates in session init, thread-history fetch, and reply prompt context injection. (#23843, #23090) Thanks @vincentkoc and @Taskle.

View File

@@ -247,9 +247,6 @@
"@napi-rs/canvas": "^0.1.89",
"node-llama-cpp": "3.16.2"
},
"optionalDependencies": {
"@discordjs/opus": "^0.10.0"
},
"engines": {
"node": ">=22.12.0"
},

43
pnpm-lock.yaml generated
View File

@@ -29,13 +29,13 @@ importers:
version: 3.1000.0
'@buape/carbon':
specifier: 0.0.0-beta-20260216184201
version: 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.11.10)(opusscript@0.1.1)
version: 0.0.0-beta-20260216184201(hono@4.11.10)(opusscript@0.1.1)
'@clack/prompts':
specifier: ^1.0.1
version: 1.0.1
'@discordjs/voice':
specifier: ^0.19.0
version: 0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1)
version: 0.19.0(opusscript@0.1.1)
'@grammyjs/runner':
specifier: ^2.0.3
version: 2.0.3(grammy@1.41.0)
@@ -253,10 +253,6 @@ importers:
vitest:
specifier: ^4.0.18
version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.3)(@vitest/browser-playwright@4.0.18)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)
optionalDependencies:
'@discordjs/opus':
specifier: ^0.10.0
version: 0.10.0
extensions/acpx:
dependencies:
@@ -929,10 +925,6 @@ packages:
resolution: {integrity: sha512-YJOVVZ545x24mHzANfYoy0BJX5PDyeZlpiJjDkUBM/V/Ao7TFX9lcUvCN4nr0tbr5ubeaXxtEBILUrHtTphVeQ==}
hasBin: true
'@discordjs/opus@0.10.0':
resolution: {integrity: sha512-HHEnSNrSPmFEyndRdQBJN2YE6egyXS9JUnJWyP6jficK0Y+qKMEZXyYTgmzpjrxXP1exM/hKaNP7BRBUEWkU5w==}
engines: {node: '>=12.0.0'}
'@discordjs/voice@0.19.0':
resolution: {integrity: sha512-UyX6rGEXzVyPzb1yvjHtPfTlnLvB5jX/stAMdiytHhfoydX+98hfympdOwsnTktzr+IRvphxTbdErgYDJkEsvw==}
engines: {node: '>=22.12.0'}
@@ -5193,13 +5185,10 @@ packages:
prism-media@1.3.5:
resolution: {integrity: sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==}
peerDependencies:
'@discordjs/opus': '>=0.8.0 <1.0.0'
ffmpeg-static: ^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0
node-opus: ^0.3.3
opusscript: ^0.0.8
peerDependenciesMeta:
'@discordjs/opus':
optional: true
ffmpeg-static:
optional: true
node-opus:
@@ -6824,19 +6813,18 @@ snapshots:
'@borewit/text-codec@0.2.1': {}
'@buape/carbon@0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.11.10)(opusscript@0.1.1)':
'@buape/carbon@0.0.0-beta-20260216184201(hono@4.11.10)(opusscript@0.1.1)':
dependencies:
'@types/node': 25.3.3
discord-api-types: 0.38.37
optionalDependencies:
'@cloudflare/workers-types': 4.20260120.0
'@discordjs/voice': 0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1)
'@discordjs/voice': 0.19.0(opusscript@0.1.1)
'@hono/node-server': 1.19.9(hono@4.11.10)
'@types/bun': 1.3.9
'@types/ws': 8.18.1
ws: 8.19.0
transitivePeerDependencies:
- '@discordjs/opus'
- bufferutil
- ffmpeg-static
- hono
@@ -6971,24 +6959,14 @@ snapshots:
- supports-color
optional: true
'@discordjs/opus@0.10.0':
dependencies:
'@discordjs/node-pre-gyp': 0.4.5
node-addon-api: 8.5.0
transitivePeerDependencies:
- encoding
- supports-color
optional: true
'@discordjs/voice@0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1)':
'@discordjs/voice@0.19.0(opusscript@0.1.1)':
dependencies:
'@types/ws': 8.18.1
discord-api-types: 0.38.40
prism-media: 1.3.5(@discordjs/opus@0.10.0)(opusscript@0.1.1)
prism-media: 1.3.5(opusscript@0.1.1)
tslib: 2.8.1
ws: 8.19.0
transitivePeerDependencies:
- '@discordjs/opus'
- bufferutil
- ffmpeg-static
- node-opus
@@ -11197,9 +11175,9 @@ snapshots:
dependencies:
'@agentclientprotocol/sdk': 0.14.1(zod@4.3.6)
'@aws-sdk/client-bedrock': 3.1000.0
'@buape/carbon': 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.11.10)(opusscript@0.1.1)
'@buape/carbon': 0.0.0-beta-20260216184201(hono@4.11.10)(opusscript@0.1.1)
'@clack/prompts': 1.0.1
'@discordjs/voice': 0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1)
'@discordjs/voice': 0.19.0(opusscript@0.1.1)
'@grammyjs/runner': 2.0.3(grammy@1.41.0)
'@grammyjs/transformer-throttler': 1.2.1(grammy@1.41.0)
'@homebridge/ciao': 1.3.5
@@ -11254,8 +11232,6 @@ snapshots:
ws: 8.19.0
yaml: 2.8.2
zod: 4.3.6
optionalDependencies:
'@discordjs/opus': 0.10.0
transitivePeerDependencies:
- '@modelcontextprotocol/sdk'
- '@types/express'
@@ -11509,9 +11485,8 @@ snapshots:
dependencies:
parse-ms: 4.0.0
prism-media@1.3.5(@discordjs/opus@0.10.0)(opusscript@0.1.1):
prism-media@1.3.5(opusscript@0.1.1):
optionalDependencies:
'@discordjs/opus': 0.10.0
opusscript: 0.1.1
process-nextick-args@2.0.1: {}

View File

@@ -36,11 +36,8 @@ describe("inferUpdateFailureHints", () => {
expect(hints.join("\n")).toContain("npm config set prefix ~/.local");
});
it("returns native optional dependency hint for node-gyp/opus failures", () => {
const result = makeResult(
"global update",
"node-pre-gyp ERR!\n@discordjs/opus\nnode-gyp rebuild failed",
);
it("returns native optional dependency hint for node-gyp failures", () => {
const result = makeResult("global update", "node-pre-gyp ERR!\nnode-gyp rebuild failed");
const hints = inferUpdateFailureHints(result);
expect(hints.join("\n")).toContain("--omit=optional");
});

View File

@@ -57,12 +57,10 @@ export function inferUpdateFailureHints(result: UpdateRunResult): string[] {
if (
failedStep.name.startsWith("global update") &&
(stderr.includes("node-gyp") ||
stderr.includes("@discordjs/opus") ||
stderr.includes("prebuild"))
(stderr.includes("node-gyp") || stderr.includes("prebuild"))
) {
hints.push(
"Detected native optional dependency build failure (e.g. opus). The updater retries with --omit=optional automatically.",
"Detected native optional dependency build failure. The updater retries with --omit=optional automatically.",
);
hints.push("If it still fails: npm i -g openclaw@latest --omit=optional");
}

View File

@@ -157,32 +157,22 @@ type OpusDecoder = {
decode: (buffer: Buffer) => Buffer;
};
let warnedOpusFallback = false;
let warnedOpusMissing = false;
function createOpusDecoder(): { decoder: OpusDecoder; name: string } | null {
try {
const { OpusEncoder } = require("@discordjs/opus") as {
OpusEncoder: new (sampleRate: number, channels: number) => OpusDecoder;
const OpusScript = require("opusscript") as {
new (sampleRate: number, channels: number, application: number): OpusDecoder;
Application: { AUDIO: number };
};
const decoder = new OpusEncoder(SAMPLE_RATE, CHANNELS);
return { decoder, name: "@discordjs/opus" };
} catch (nativeErr) {
try {
const OpusScript = require("opusscript") as {
new (sampleRate: number, channels: number, application: number): OpusDecoder;
Application: { AUDIO: number };
};
const decoder = new OpusScript(SAMPLE_RATE, CHANNELS, OpusScript.Application.AUDIO);
if (!warnedOpusFallback) {
warnedOpusFallback = true;
logger.warn(
`discord voice: @discordjs/opus unavailable (${formatErrorMessage(nativeErr)}); using opusscript fallback`,
);
}
return { decoder, name: "opusscript" };
} catch (jsErr) {
logger.warn(`discord voice: opus decoder init failed: ${formatErrorMessage(nativeErr)}`);
logger.warn(`discord voice: opusscript init failed: ${formatErrorMessage(jsErr)}`);
const decoder = new OpusScript(SAMPLE_RATE, CHANNELS, OpusScript.Application.AUDIO);
return { decoder, name: "opusscript" };
} catch (err) {
if (!warnedOpusMissing) {
warnedOpusMissing = true;
logger.warn(
`discord voice: opusscript unavailable (${formatErrorMessage(err)}); cannot decode voice audio`,
);
}
}
return null;