diff --git a/CHANGELOG.md b/CHANGELOG.md index d5daba859d4..7901b18b501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/package.json b/package.json index d8263bd49b4..0bdbf1da2d7 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 54cb62a8327..2e991dded9f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/src/cli/update-cli/progress.test.ts b/src/cli/update-cli/progress.test.ts index d8ddf52128e..86cbe25ea7f 100644 --- a/src/cli/update-cli/progress.test.ts +++ b/src/cli/update-cli/progress.test.ts @@ -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"); }); diff --git a/src/cli/update-cli/progress.ts b/src/cli/update-cli/progress.ts index edaf4d3d665..8d397a73d58 100644 --- a/src/cli/update-cli/progress.ts +++ b/src/cli/update-cli/progress.ts @@ -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"); } diff --git a/src/discord/voice/manager.ts b/src/discord/voice/manager.ts index 301e8b74c10..31b964ccbdb 100644 --- a/src/discord/voice/manager.ts +++ b/src/discord/voice/manager.ts @@ -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;