Install the engine
1
Install at the exact pin
2
Copy the patch out of the SDK
3
Apply it on every install
VOICE_ENGINE_PIN and VOICE_ENGINE_PATCH_FILE are exported so your own setup scripts can read the version and filename rather than hardcoding them.
Why the patch is mandatory
The engine’s only per-participant key setter takes no key argument, at any published version, while its wire protocol marks that field required. Every call throws. Under per-participant encryption a publisher must install a key under its own identity, so an unpatched engine cannot publish audio anyone can decrypt. The SDK repo carries a one-line fix. Patches are repo-local: the engine installs into yournode_modules unpatched, so a bot that publishes audio has to apply the same patch in its own app. The patch retires when upstream lands the fix.
Why the pin is exact
Apatch-package patch is named for one version. A floating range resolves to a version the patch does not name, patch-package skips it with a warning, and the bot publishes audio nobody can decrypt. The frame-key parameters the engine sends are also a cross-platform contract with the Cloak desktop, web, and mobile apps, and a minor version bump can change a default.
Host requirements
The Node floor is a crypto requirement, not a transport or voice one. The HPKE stack needs
globalThis.crypto.getRandomValues, which Node 18 does not define in ESM. The default wire is a plain wss:// WebSocket over the pure-JS ws package, and installing the SDK itself compiles nothing. The voice engine ships prebuilt binaries, so it does not compile either.optionalDependencies specifically because npm tolerates platform and install-script failures there. A musl host or a prebuild miss still gets a working text bot.
Optional executables
Neither is a dependency of correctness. Feeding the player raw PCM needs neither.Checking the host
Two different questions, two different tools.probeVoiceRuntime() never throws. A text-only bot must not be failed by an absent voice engine, so it is a diagnostic rather than a gate.
probe.remedy to whoever invoked the command. It is written to be shown to a human.
voiceEnvError(cause) is the same mapping the SDK uses internally: hand it a load failure and it returns a CloakEnvironmentError with a remedy naming what to change on the machine. It recognises old glibc, musl hosts, and a missing package.
The blind spot
Because of that, the SDK ships no voice health check and no retry-on-decrypt-failure path. Both would be theatre. Do not build one.
The only defence is to be correct by construction, which is what
VoiceMediaSession.attach() does: install exactly what voiceFrameKeyFor() returns, under the identity the SFU reported, before publishing, and fail closed on every precondition. The real acceptance test is a live call with a human on the other end.
Symptom to cause
Next
Publishing audio
The media session, the player, and the resource helpers.
Voice keys and scopes
The other half of every silence bug.
Voice reference
probeVoiceRuntime, voiceEnvError, and the voice constants.Handling errors
CloakEnvironmentError and the rest of the error model.