diff --git a/extensions/discord/npm-shrinkwrap.json b/extensions/discord/npm-shrinkwrap.json index 998eb2cefaf2..f2e750235454 100644 --- a/extensions/discord/npm-shrinkwrap.json +++ b/extensions/discord/npm-shrinkwrap.json @@ -10,7 +10,7 @@ "dependencies": { "@discordjs/voice": "0.19.2", "discord-api-types": "0.38.48", - "libopus-wasm": "0.1.0", + "libopus-wasm": "0.2.0", "typebox": "1.1.39", "undici": "8.3.0", "ws": "8.21.0" @@ -352,9 +352,9 @@ ] }, "node_modules/libopus-wasm": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/libopus-wasm/-/libopus-wasm-0.1.0.tgz", - "integrity": "sha512-/aurGcAVgy0GcBEUzFaX9pm9qv7zYcy8W5hBXFiK+cyqOXAX4lOS6rlFogkY9CcSIajhjnuXyixsbmziSHCDMQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/libopus-wasm/-/libopus-wasm-0.2.0.tgz", + "integrity": "sha512-x/2Gu1/C6L3IICY09zyfp984AWiOYjn53u4WfdY3yh+3KTzMN8Xkm77q3lenWMVIk5SnSzjGEkQT+VQMFHLBHQ==", "license": "MIT", "engines": { "node": ">=20" diff --git a/extensions/discord/package.json b/extensions/discord/package.json index bf8d07e4ef59..b86780c62bbb 100644 --- a/extensions/discord/package.json +++ b/extensions/discord/package.json @@ -10,7 +10,7 @@ "dependencies": { "@discordjs/voice": "0.19.2", "discord-api-types": "0.38.48", - "libopus-wasm": "0.1.0", + "libopus-wasm": "0.2.0", "typebox": "1.1.39", "undici": "8.3.0", "ws": "8.21.0" diff --git a/extensions/discord/src/voice/receive-recovery.test.ts b/extensions/discord/src/voice/receive-recovery.test.ts index e98162fce1da..0c29583dbd98 100644 --- a/extensions/discord/src/voice/receive-recovery.test.ts +++ b/extensions/discord/src/voice/receive-recovery.test.ts @@ -1,4 +1,4 @@ -import { OpusError } from "libopus-wasm"; +import { OpusError, OpusErrorCode } from "libopus-wasm"; import { describe, expect, it, vi } from "vitest"; import { analyzeVoiceReceiveError, @@ -33,7 +33,11 @@ describe("voice receive recovery", () => { }); it("treats corrupt Opus packets as non-recoverable decode noise", () => { - expect(analyzeVoiceReceiveError(new OpusError(-4, "not inspected", "decode"))).toEqual({ + expect( + analyzeVoiceReceiveError( + new OpusError(OpusErrorCode.InvalidPacket, "not inspected", "decode"), + ), + ).toEqual({ message: "not inspected", isAbortLike: false, isDecodeCorruption: true, @@ -42,6 +46,23 @@ describe("voice receive recovery", () => { }); }); + it("treats structurally equivalent Opus errors as decode corruption", () => { + const analysis = analyzeVoiceReceiveError({ + name: "OpusError", + message: "libopus decode failed (-4): corrupted stream", + code: OpusErrorCode.InvalidPacket, + codeName: "InvalidPacket", + operation: "decode", + }); + + expect(analysis).toMatchObject({ + isAbortLike: false, + isDecodeCorruption: true, + shouldAttemptPassthrough: false, + countsAsDecryptFailure: false, + }); + }); + it("does not classify corrupt Opus packet text without the Opus error contract", () => { expect( analyzeVoiceReceiveError(new Error("libopus decode failed (-4): corrupted stream")), diff --git a/extensions/discord/src/voice/receive-recovery.ts b/extensions/discord/src/voice/receive-recovery.ts index 5fe047109e04..48136c905751 100644 --- a/extensions/discord/src/voice/receive-recovery.ts +++ b/extensions/discord/src/voice/receive-recovery.ts @@ -1,4 +1,4 @@ -import { OpusError } from "libopus-wasm"; +import { OpusErrorCode, isOpusError } from "libopus-wasm"; import { formatErrorMessage } from "openclaw/plugin-sdk/ssrf-runtime"; const DECRYPT_FAILURE_WINDOW_MS = 30_000; @@ -6,7 +6,6 @@ const DECRYPT_FAILURE_RECONNECT_THRESHOLD = 3; const DECRYPT_FAILURE_MARKER = "DecryptionFailed("; const DAVE_PASSTHROUGH_DISABLED_MARKER = "UnencryptedWhenPassthroughDisabled"; const WASM_MEMORY_ACCESS_MARKER = "memory access out of bounds"; -const OPUS_INVALID_PACKET_CODE = -4; export const DAVE_RECEIVE_PASSTHROUGH_INITIAL_EXPIRY_SECONDS = 30; export const DAVE_RECEIVE_PASSTHROUGH_REARM_EXPIRY_SECONDS = 15; @@ -85,8 +84,8 @@ function isAbortLikeReceiveError(err: unknown): boolean { function isOpusDecodeInvalidPacketError(err: unknown): boolean { return ( - err instanceof OpusError && - err.code === OPUS_INVALID_PACKET_CODE && + isOpusError(err) && + err.code === OpusErrorCode.InvalidPacket && (err.operation === "decode" || err.operation === "decodeFloat") ); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 193a0a2f43d2..ea76561a2ebf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -678,8 +678,8 @@ importers: specifier: 0.38.48 version: 0.38.48 libopus-wasm: - specifier: 0.1.0 - version: 0.1.0 + specifier: 0.2.0 + version: 0.2.0 typebox: specifier: 1.1.39 version: 1.1.39 @@ -5541,8 +5541,8 @@ packages: resolution: {integrity: sha512-s6WVJyEZrbm6jhBpiKHsGHyePMrVQKJ85wZCFCr9W4QHv6WTjWIrdvTmO9hDEA3bNK0xkrE2DqrHsXMLWuZpQg==} engines: {node: '>=22.0.0'} - libopus-wasm@0.1.0: - resolution: {integrity: sha512-/aurGcAVgy0GcBEUzFaX9pm9qv7zYcy8W5hBXFiK+cyqOXAX4lOS6rlFogkY9CcSIajhjnuXyixsbmziSHCDMQ==} + libopus-wasm@0.2.0: + resolution: {integrity: sha512-x/2Gu1/C6L3IICY09zyfp984AWiOYjn53u4WfdY3yh+3KTzMN8Xkm77q3lenWMVIk5SnSzjGEkQT+VQMFHLBHQ==} engines: {node: '>=20'} libsignal@6.0.0: @@ -11150,7 +11150,7 @@ snapshots: kysely@0.29.2: {} - libopus-wasm@0.1.0: {} + libopus-wasm@0.2.0: {} libsignal@6.0.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ff248e63a8c2..f59d52907406 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -17,7 +17,7 @@ minimumReleaseAgeExclude: - "basic-ftp" - "baileys@7.0.0-rc13" - "hono" - - "libopus-wasm@0.1.0" + - "libopus-wasm@0.2.0" - "libsignal@6.0.0" - "openclaw" - "protobufjs"