diff --git a/nix/package.nix b/nix/package.nix index 839a4c7d5..4b4ca73f1 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -5,6 +5,7 @@ nodejs_22, python3, makeWrapper, + autoPatchelfHook, # node-pty needs libuv headers on Linux libuv, # Exposed so downstream flakes that follow a different nixpkgs revision @@ -59,10 +60,13 @@ buildNpmPackage rec { nativeBuildInputs = [ python3 # for node-gyp (node-pty compilation) makeWrapper + ] ++ lib.optionals stdenv.hostPlatform.isLinux [ + autoPatchelfHook ]; buildInputs = lib.optionals stdenv.hostPlatform.isLinux [ libuv + stdenv.cc.cc.lib # libstdc++ for sherpa-onnx prebuilt binaries ]; # Don't use the default npm build hook — we need a custom build sequence @@ -71,10 +75,9 @@ buildNpmPackage rec { buildPhase = '' runHook preBuild - # Rebuild only node-pty (native addon for terminal emulation). - # Speech-related native modules (sherpa-onnx, onnxruntime-node) are - # intentionally left unbuilt — they're lazily loaded and gracefully - # degrade when unavailable. + # Rebuild only node-pty (native addon for terminal emulation). The sherpa + # speech runtime ships prebuilt platform packages and is copied into the + # daemon closure by scripts/trace-daemon.mjs. npm rebuild node-pty # Build all server packages in dependency order (defined in package.json) @@ -89,7 +92,7 @@ buildNpmPackage rec { # Compute the daemon's runtime closure by static module-graph tracing # (@vercel/nft from supervisor-entrypoint.js, cli/dist/index.js, and the - # forked terminal-worker-process.js) plus an explicit list of non-JS + # forked terminal/speech worker processes) plus an explicit list of non-JS # assets read at runtime. The trace script is the single source of # truth for what the daemon needs at $out — auditable in plain JS, no # npm hoisting / .bin / workspace-symlink footguns. diff --git a/scripts/trace-daemon.mjs b/scripts/trace-daemon.mjs index 5b7de8b41..31351e547 100644 --- a/scripts/trace-daemon.mjs +++ b/scripts/trace-daemon.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node // Emit the set of files the daemon and CLI need at runtime, computed by -// static module-graph tracing (@vercel/nft) from the three entry points. +// static module-graph tracing (@vercel/nft) from the daemon entry points. // Used by nix/package.nix's installPhase to materialize $out/lib/paseo // with only the bytes the daemon actually loads — no Expo, RN, Metro, // Electron, ML stacks, or other non-daemon workspace bloat. @@ -15,16 +15,27 @@ import { nodeFileTrace } from "@vercel/nft"; import { glob } from "node:fs/promises"; import path from "node:path"; +import { pathToFileURL } from "node:url"; const REPO_ROOT = path.resolve(import.meta.dirname, ".."); -// Three entry points. The terminal worker is forked into its own Node -// process and has its own require tree (node-pty, etc.) — nft does not -// follow fork boundaries, so it must be traced separately. +const { sherpaPlatformPackageName } = await import( + pathToFileURL( + path.join( + REPO_ROOT, + "packages/server/dist/server/server/speech/providers/local/sherpa/sherpa-runtime-env.js", + ), + ).href +); + +// Daemon entry points. Workers forked into their own Node processes have +// independent require trees; nft does not follow fork boundaries, so trace +// them separately. const entries = [ "packages/cli/dist/index.js", "packages/server/dist/scripts/supervisor-entrypoint.js", "packages/server/dist/server/terminal/terminal-worker-process.js", + "packages/server/dist/server/server/speech/providers/local/worker-process.js", ]; // Files read at runtime via fs APIs rather than `require`. nft only @@ -44,19 +55,22 @@ const additionalInputs = [ // the Nix derivation builds for one platform at a time and ships only // its own binaries. `node_modules/node-pty/prebuilds/${process.platform}-${process.arch}/**`, + // sherpa-onnx-node dynamically resolves a platform-specific native package. + // Copy the wrapper plus the host platform package explicitly. + "node_modules/sherpa-onnx-node/**", + `node_modules/${sherpaPlatformPackageName()}/**`, ]; // Trace. const { fileList, warnings } = await nodeFileTrace(entries, { base: REPO_ROOT, // Tolerate the conditional / dynamic patterns we already audited: - // sherpa-onnx-${platform}-${arch} package resolution (sherpa is - // intentionally not built in the Nix sandbox; voice features degrade - // gracefully when unavailable), and a handful of test-only requires - // that get tree-shaken out by tsc. + // sherpa-onnx-${platform}-${arch} package resolution (the host package + // is copied explicitly above), and a handful of test-only requires that + // get tree-shaken out by tsc. ignore: [ // Cross-platform native packages for the sherpa speech runtime; - // unsupported in the Nix build, lazily loaded when present. + // only the host platform package is needed at runtime. "sherpa-onnx-*/**", // Platform-specific clipboard variants; only the host's variant // is needed at runtime, and the package's index.js resolver picks