diff --git a/nix/checks.nix b/nix/checks.nix index bb8801a0b8..8adb56628d 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -4,9 +4,9 @@ # transitive deps like onnxruntime that lack compatible wheels on # aarch64-darwin. The package and devShell still work on macOS. { inputs, ... }: { - perSystem = { pkgs, system, lib, ... }: + perSystem = { pkgs, lib, self', ... }: let - hermes-agent = inputs.self.packages.${system}.default; + hermes-agent = self'.packages.default; hermesVenv = hermes-agent.hermesVenv; configMergeScript = pkgs.callPackage ./configMergeScript.nix { }; @@ -51,7 +51,7 @@ json.dump(sorted(leaf_paths(DEFAULT_CONFIG)), sys.stdout, indent=2) failMsg = lib.concatMapStringsSep "\n" (r: " - ${r.sys}") failures; in pkgs.runCommand "hermes-cross-eval" { } ( if failures != [] then - builtins.throw "Package fails to evaluate on:\n${failMsg}" + throw "Package fails to evaluate on:\n${failMsg}" else '' echo "PASS: package evaluates on all ${toString (builtins.length targetSystems)} platforms" mkdir -p $out diff --git a/nix/devShell.nix b/nix/devShell.nix index d0d56e40b0..82b0dc1fc8 100644 --- a/nix/devShell.nix +++ b/nix/devShell.nix @@ -1,29 +1,30 @@ # nix/devShell.nix — Dev shell that delegates setup to each package # -# Each package in inputsFrom exposes passthru.devShellHook — a bash snippet +# Each package in inputsFrom might expose passthru.devShellHook — a bash snippet # with stamp-checked setup logic. This file collects and runs them all. -{ inputs, ... }: { - perSystem = { pkgs, system, ... }: +{ ... }: +{ + perSystem = + { pkgs, self', ... }: let - hermes-agent = inputs.self.packages.${system}.default; - hermes-tui = inputs.self.packages.${system}.tui; - hermes-web = inputs.self.packages.${system}.web; - packages = [ hermes-agent hermes-tui hermes-web ]; - in { + packages = builtins.attrValues self'.packages; + in + { devShells.default = pkgs.mkShell { inputsFrom = packages; packages = with pkgs; [ - python312 uv nodejs_22 ripgrep git openssh ffmpeg + uv ]; - - shellHook = let - hooks = map (p: p.passthru.devShellHook or "") packages; - combined = pkgs.lib.concatStringsSep "\n" (builtins.filter (h: h != "") hooks); - in '' - echo "Hermes Agent dev shell" - ${combined} - echo "Ready. Run 'hermes' to start." - ''; + shellHook = + let + hooks = map (p: p.passthru.devShellHook or "") packages; + combined = pkgs.lib.concatStringsSep "\n" (builtins.filter (h: h != "") hooks); + in + '' + echo "Hermes Agent dev shell" + ${combined} + echo "Ready. Run 'hermes' to start." + ''; }; }; } diff --git a/nix/hermes-agent.nix b/nix/hermes-agent.nix index 886bb1aadb..7b6b9ecd5a 100644 --- a/nix/hermes-agent.nix +++ b/nix/hermes-agent.nix @@ -27,12 +27,13 @@ extraPythonPackages ? [ ], }: let + nodejs = nodejs_22; hermesVenv = callPackage ./python.nix { inherit uv2nix pyproject-nix pyproject-build-systems; }; hermesNpmLib = callPackage ./lib.nix { - inherit npm-lockfile-fix; + inherit npm-lockfile-fix nodejs; }; hermesTui = callPackage ./tui.nix { @@ -57,7 +58,7 @@ let }; runtimeDeps = [ - nodejs_22 + nodejs ripgrep git openssh @@ -82,10 +83,49 @@ let builtins.hashString "sha256" (builtins.readFile ../uv.lock) else "none"; + checkPackageCollisions = '' + import pathlib, sys, re + + def canonical(name): + return re.sub(r'[-_.]+', '-', name).lower() + + # Collect core venv package names + core = set() + venv_sp = pathlib.Path('${hermesVenv}/${sitePackagesPath}') + for di in venv_sp.glob('*.dist-info'): + meta = di / 'METADATA' + if meta.exists(): + for line in meta.read_text().splitlines(): + if line.startswith('Name:'): + core.add(canonical(line.split(':', 1)[1].strip())) + break + + # Check each extra package for collisions + extras_dirs = [${lib.concatMapStringsSep ", " (p: "'${toString p}'") allExtraPythonPackages}] + for edir in extras_dirs: + sp = pathlib.Path(edir) / '${sitePackagesPath}' + if not sp.exists(): + continue + for di in sp.glob('*.dist-info'): + meta = di / 'METADATA' + if not meta.exists(): + continue + for line in meta.read_text().splitlines(): + if line.startswith('Name:'): + pkg = canonical(line.split(':', 1)[1].strip()) + if pkg in core: + print(f'ERROR: plugin package \"{pkg}\" collides with a package in hermes sealed venv', file=sys.stderr) + print(f' from: {di}', file=sys.stderr) + print(f' Remove this dependency from extraPythonPackages.', file=sys.stderr) + sys.exit(1) + break + + print('No collisions found.') + ''; in stdenv.mkDerivation { pname = "hermes-agent"; - version = (builtins.fromTOML (builtins.readFile ../pyproject.toml)).project.version; + version = (fromTOML (builtins.readFile ../pyproject.toml)).project.version; dontUnpack = true; dontBuild = true; @@ -111,7 +151,7 @@ stdenv.mkDerivation { --set HERMES_WEB_DIST $out/share/hermes-agent/web_dist \ --set HERMES_TUI_DIR $out/ui-tui \ --set HERMES_PYTHON ${hermesVenv}/bin/python3 \ - --set HERMES_NODE ${nodejs_22}/bin/node \ + --set HERMES_NODE ${lib.getExe nodejs} \ ${lib.optionalString (rev != null) ''--set HERMES_REVISION ${rev} \''} ${lib.optionalString (extraPythonPackages != [ ]) ''--suffix PYTHONPATH : "${pythonPath}"''} '') @@ -124,53 +164,20 @@ stdenv.mkDerivation { ${lib.optionalString (extraPythonPackages != [ ]) '' echo "=== Checking for plugin/core package collisions ===" - ${hermesVenv}/bin/python3 -c " -import pathlib, sys, re - -def canonical(name): - return re.sub(r'[-_.]+', '-', name).lower() - -# Collect core venv package names -core = set() -venv_sp = pathlib.Path('${hermesVenv}/${sitePackagesPath}') -for di in venv_sp.glob('*.dist-info'): - meta = di / 'METADATA' - if meta.exists(): - for line in meta.read_text().splitlines(): - if line.startswith('Name:'): - core.add(canonical(line.split(':', 1)[1].strip())) - break - -# Check each extra package for collisions -extras_dirs = [${lib.concatMapStringsSep ", " (p: "'${toString p}'") allExtraPythonPackages}] -for edir in extras_dirs: - sp = pathlib.Path(edir) / '${sitePackagesPath}' - if not sp.exists(): - continue - for di in sp.glob('*.dist-info'): - meta = di / 'METADATA' - if not meta.exists(): - continue - for line in meta.read_text().splitlines(): - if line.startswith('Name:'): - pkg = canonical(line.split(':', 1)[1].strip()) - if pkg in core: - print(f'ERROR: plugin package \"{pkg}\" collides with a package in hermes sealed venv', file=sys.stderr) - print(f' from: {di}', file=sys.stderr) - print(f' Remove this dependency from extraPythonPackages.', file=sys.stderr) - sys.exit(1) - break - -print('No collisions found.') - " - echo "=== No collisions ===" + ${hermesVenv}/bin/python3 -c "${checkPackageCollisions}" + echo "=== No collisions ===" ''} runHook postInstall ''; passthru = { - inherit hermesTui hermesWeb hermesNpmLib hermesVenv; + inherit + hermesTui + hermesWeb + hermesNpmLib + hermesVenv + ; devShellHook = '' STAMP=".nix-stamps/hermes-agent" diff --git a/nix/lib.nix b/nix/lib.nix index 020525fd80..3740ef1057 100644 --- a/nix/lib.nix +++ b/nix/lib.nix @@ -1,11 +1,16 @@ # nix/lib.nix — Shared helpers for nix stuff -{ pkgs, npm-lockfile-fix }: +{ + pkgs, + npm-lockfile-fix, + nodejs, +}: { # Returns a buildNpmPackage-compatible attrs set that provides: - # patchPhase — ensures lockfile has exactly one trailing newline - # nativeBuildInputs — [ updateLockfileScript ] (list, prepend with ++ for more) + # patchPhase — ensures lockfile has exactly one trailing newline + # nativeBuildInputs — [ updateLockfileScript ] (list, prepend with ++ for more) # passthru.devShellHook — stamp-checked npm install + hash auto-update # passthru.npmLockfile — metadata for mkFixLockfiles + # nodejs — fixed nodejs version for all packages we use in the repo # # NOTE: npmConfigHook runs `diff` between the source lockfile and the # npm-deps cache lockfile. fetchNpmDeps preserves whatever trailing @@ -24,6 +29,7 @@ nixFile ? "nix/${attr}.nix", # defaults to nix/.nix }: { + inherit nodejs; patchPhase = '' runHook prePatch # Normalize trailing newlines so source and npm-deps always match, @@ -56,8 +62,8 @@ cd "$REPO_ROOT/${folder}" rm -rf node_modules/ - npm cache clean --force - CI=true npm install + ${pkgs.lib.getExe' nodejs "npm"} cache clean --force + CI=true ${pkgs.lib.getExe' nodejs "npm"} install ${pkgs.lib.getExe npm-lockfile-fix} ./package-lock.json NIX_FILE="$REPO_ROOT/${nixFile}" @@ -83,7 +89,7 @@ STAMP_VALUE="$(_hermes_npm_stamp)" if [ ! -f "$STAMP" ] || [ "$(cat "$STAMP")" != "$STAMP_VALUE" ]; then echo "${pname}: installing npm dependencies..." - ( cd ${folder} && CI=true npm install --silent --no-fund --no-audit 2>/dev/null ) + ( cd ${folder} && CI=true ${pkgs.lib.getExe' nodejs "npm"} install --silent --no-fund --no-audit 2>/dev/null ) # Auto-update the nix hash so it stays in sync with the lockfile echo "${pname}: prefetching npm deps..." diff --git a/nix/web.nix b/nix/web.nix index 7084a04c8e..a5793dff7a 100644 --- a/nix/web.nix +++ b/nix/web.nix @@ -8,11 +8,13 @@ let }; npm = hermesNpmLib.mkNpmPassthru { folder = "web"; attr = "web"; pname = "hermes-web"; }; + + packageJson = builtins.fromJSON (builtins.readFile (src + "/package.json")); + version = packageJson.version; in pkgs.buildNpmPackage (npm // { pname = "hermes-web"; - version = "0.0.0"; - inherit src npmDeps; + inherit src npmDeps version; doCheck = false;