Compare commits

...

1 Commits

Author SHA1 Message Date
ethernet
5b68ec7d1b change(tooling): update node to 26 everywhere, keep node version managed 2026-06-11 18:17:39 -04:00
23 changed files with 358 additions and 288 deletions

View File

@@ -48,7 +48,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
node-version: 26
cache: npm
- name: Install npm dependencies

View File

@@ -44,7 +44,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
node-version: 26
cache: npm
cache-dependency-path: website/package-lock.json

View File

@@ -18,7 +18,7 @@ jobs:
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
node-version: 26
cache: npm
cache-dependency-path: website/package-lock.json

View File

@@ -10,16 +10,15 @@ on:
jobs:
typecheck:
runs-on: ubuntu-latest
strategy:
matrix:
package:
[ui-tui, web, apps/bootstrap-installer, apps/desktop, apps/shared]
fail-fast: false # report all failures, not just the first one
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 22
node-version: 26
cache: npm
- run: npm ci
- run: npm run --prefix ${{ matrix.package }} typecheck
- run: npm run --prefix ui-tui typecheck
- run: npm run --prefix web typecheck
- run: npm run --prefix apps/bootstrap-installer typecheck
- run: npm run --prefix apps/desktop typecheck
- run: npm run --prefix apps/shared typecheck

View File

@@ -53,7 +53,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
node-version: '26'
- name: Build web dashboard
run: cd web && npm ci && npm run build

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
26.3.0

View File

@@ -1,12 +1,12 @@
FROM ghcr.io/astral-sh/uv:0.11.6-python3.13-trixie@sha256:b3c543b6c4f23a5f2df22866bd7857e5d304b67a564f4feab6ac22044dde719b AS uv_source
# Node 22 LTS source stage. Debian trixie's bundled nodejs is pinned to 20.x
# Node 26 source stage. Debian trixie's bundled nodejs is pinned to 20.x
# which reached EOL in April 2026 — we copy node + npm + corepack from the
# upstream node:22 image instead so we can stay on a supported LTS without
# waiting for Debian 14 (forky, ~mid-2027). Bookworm-based slim image used
# upstream node:26 image instead so we can stay on the supported node without
# waiting for Debian 15+. Bookworm-based slim image used
# so the produced binary links against glibc 2.36, which runs cleanly on
# our Debian 13 (trixie, glibc 2.41) runtime. Bumping to a new Node major
# is a one-line ARG change; see #4977.
FROM node:22-bookworm-slim@sha256:7af03b14a13c8cdd38e45058fd957bf00a72bbe17feac43b1c15a689c029c732 AS node_source
FROM node:26-bookworm-slim@sha256:3fe807a03a4436e7bc76b7e84e6861899cd75c9028ae99bc00581940141ae150 AS node_source
FROM debian:13.4
# Disable Python stdout buffering to ensure logs are printed immediately
@@ -90,17 +90,15 @@ RUN useradd -u 10000 -m -d /opt/data hermes
COPY --chmod=0755 --from=uv_source /usr/local/bin/uv /usr/local/bin/uvx /usr/local/bin/
# Node 22 LTS: copy the node binary plus the bundled npm + corepack JS
# installs from the upstream image. npm and npx are recreated as symlinks
# Node 26: copy the node binary plus the bundled npm JS
# installs from the upstream image. npm and npx are recreated as symlinks
# because they're symlinks in the source image (and need to live on PATH).
# See node_source stage at the top of the file for the version-bump
# rationale (#4977).
COPY --chmod=0755 --from=node_source /usr/local/bin/node /usr/local/bin/
COPY --from=node_source /usr/local/lib/node_modules/npm /usr/local/lib/node_modules/npm
COPY --from=node_source /usr/local/lib/node_modules/corepack /usr/local/lib/node_modules/corepack
RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx && \
ln -sf /usr/local/lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack
ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
WORKDIR /opt/hermes
@@ -119,7 +117,7 @@ COPY ui-tui/packages/hermes-ink/ ui-tui/packages/hermes-ink/
# `npm_config_install_links=false` forces npm to install `file:` deps as
# symlinks instead of copies. This is the default since npm 10+, which is
# what the image ships now (via the node:22 source stage). We set it
# what the image ships now (via the node:26 source stage). We set it
# explicitly anyway as defense-in-depth: the previous Debian-bundled npm
# 9.x defaulted to install-as-copy, which produced a hidden
# node_modules/.package-lock.json that permanently disagreed with the root

View File

@@ -8,7 +8,7 @@
"type": "module",
"main": "electron/main.cjs",
"engines": {
"node": "^20.19.0 || >=22.12.0"
"node": ">=26.0.0"
},
"scripts": {
"dev": "concurrently -k \"npm:dev:renderer\" \"npm:dev:electron\"",

View File

@@ -212,7 +212,7 @@ terminal:
# cwd: "/workspace" # Path INSIDE the container (default: /)
# timeout: 180
# lifetime_seconds: 300
# docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
# docker_image: "nikolaik/python-nodejs:python3.11-nodejs26"
# docker_mount_cwd_to_workspace: true # Explicit opt-in: mount your launch cwd into /workspace
# # Optional: run the container as your host user's uid:gid so files written
# # into bind-mounted dirs are owned by you, not root. Drops SETUID/SETGID
@@ -242,7 +242,7 @@ terminal:
# cwd: "/workspace" # Path INSIDE the container (default: /root)
# timeout: 180
# lifetime_seconds: 300
# singularity_image: "docker://nikolaik/python-nodejs:python3.11-nodejs20"
# singularity_image: "docker://nikolaik/python-nodejs:python3.11-nodejs26"
# -----------------------------------------------------------------------------
# OPTION 5: Modal cloud execution
@@ -254,7 +254,7 @@ terminal:
# cwd: "/workspace" # Path INSIDE the sandbox (default: /root)
# timeout: 180
# lifetime_seconds: 300
# modal_image: "nikolaik/python-nodejs:python3.11-nodejs20"
# modal_image: "nikolaik/python-nodejs:python3.11-nodejs26"
# -----------------------------------------------------------------------------
# OPTION 6: Daytona cloud execution
@@ -267,7 +267,7 @@ terminal:
# cwd: "~"
# timeout: 180
# lifetime_seconds: 300
# daytona_image: "nikolaik/python-nodejs:python3.11-nodejs20"
# daytona_image: "nikolaik/python-nodejs:python3.11-nodejs26"
# container_disk: 10240 # Daytona max is 10GB per sandbox
#

6
flake.lock generated
View File

@@ -22,11 +22,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1775036866,
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
"lastModified": 1780749050,
"narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
"rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
"type": "github"
},
"original": {

View File

@@ -7440,7 +7440,55 @@ def _ensure_uv_for_termux(pip_cmd: list[str]) -> str | None:
return resolve_uv() or shutil.which("uv")
def _ensure_managed_node() -> None:
"""Ensure Hermes-managed Node.js matches .nvmrc, updating if necessary."""
nvmrc_path = PROJECT_ROOT / ".nvmrc"
if not nvmrc_path.exists():
return # Let system node handle it
target_version = nvmrc_path.read_text().strip()
if not target_version:
return
hermes_home = Path(os.environ.get("HERMES_HOME", Path.home() / ".hermes"))
managed_node = hermes_home / "node" / "bin" / "node"
if managed_node.exists():
try:
current_version = subprocess.check_output(
[str(managed_node), "--version"], text=True
).strip().lstrip("v")
if current_version == target_version:
return # Already up to date
except Exception:
pass # Fall through to reinstall
bootstrap_script = PROJECT_ROOT / "scripts" / "lib" / "node-bootstrap.sh"
if not bootstrap_script.exists():
return
print(f"→ Updating Hermes-managed Node.js to {target_version}...")
env = {**os.environ, "HERMES_HOME": str(hermes_home)}
try:
result = subprocess.run(
["bash", "-c", f'source "{bootstrap_script}" && ensure_node'],
env=env,
cwd=PROJECT_ROOT,
capture_output=True,
text=True,
)
if result.returncode == 0:
print(f" ✓ Node.js updated to {target_version}")
else:
print(f" ⚠ Node.js update failed (non-fatal): {result.stderr.strip()}")
except Exception as e:
print(f" ⚠ Node.js update failed (non-fatal): {e}")
def _update_node_dependencies() -> None:
# Ensure managed Node.js matches .nvmrc before running npm
_ensure_managed_node()
npm = shutil.which("npm")
if not npm:
return

View File

@@ -29,6 +29,7 @@
inputsFrom = packages;
packages = with pkgs; [
uv
nodejs_26
];
shellHook = ''
echo "Hermes Agent dev shell"

View File

@@ -10,7 +10,7 @@
makeWrapper,
callPackage,
python312,
nodejs_22,
nodejs_26,
electron,
ripgrep,
git,
@@ -36,7 +36,7 @@
extraDependencyGroups ? [ ],
}:
let
nodejs = nodejs_22;
nodejs = nodejs_26;
hermesVenv = callPackage ./python.nix {
inherit uv2nix pyproject-nix pyproject-build-systems;
dependency-groups = [ "all" ] ++ extraDependencyGroups;

View File

@@ -21,7 +21,7 @@ let
# Single npm deps fetch from the workspace root lockfile.
# All workspace packages share this derivation.
npmDepsHash = "sha256-jN6rD+vVhTCWz3lFZzlmFYXmcMRPTtYWy3XVSiDYbvM=";
npmDepsHash = "sha256-yGATLQu+tk1XvXc6KCBkq6/LoI4aeowdkIQ4vHWSfI8=";
npmDeps = pkgs.fetchNpmDeps {
inherit src;

View File

@@ -130,7 +130,7 @@ docker build --no-cache -t my-app . # clean rebuild
DOCKER_BUILDKIT=1 docker build -t my-app . # faster with BuildKit
# Pull and push
docker pull node:20-alpine
docker pull node:26-alpine
docker login ghcr.io
docker tag my-app:latest registry/my-app:v1.0
docker push registry/my-app:v1.0
@@ -276,6 +276,6 @@ When reviewing or creating a Dockerfile, suggest these improvements:
2. **Layer ordering** — put dependencies before source code so changes don't invalidate cached layers
3. **Combine RUN commands** — fewer layers, smaller image
4. **Use .dockerignore** — exclude `node_modules`, `.git`, `__pycache__`, etc.
5. **Pin base image versions**`node:20-alpine` not `node:latest`
5. **Pin base image versions**`node:26-alpine` not `node:latest`
6. **Run as non-root** — add `USER` instruction for security
7. **Use slim/alpine bases**`python:3.12-slim` not `python:3.12`

273
package-lock.json generated
View File

@@ -20,7 +20,8 @@
"agent-browser": "^0.26.0"
},
"engines": {
"node": ">=20.0.0"
"node": ">=26.0.0 <27.0.0",
"npm": ">=11.0.0 <12.0.0"
}
},
"apps/bootstrap-installer": {
@@ -160,7 +161,7 @@
"wait-on": "^9.0.5"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
"node": ">=26.0.0"
}
},
"apps/desktop/node_modules/@nous-research/ui": {
@@ -402,17 +403,17 @@
}
},
"apps/desktop/node_modules/vite": {
"version": "8.0.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
"version": "8.0.16",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.16.tgz",
"integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.10",
"rolldown": "1.0.0-rc.17",
"tinyglobby": "^0.2.16"
"postcss": "^8.5.15",
"rolldown": "1.0.3",
"tinyglobby": "^0.2.17"
},
"bin": {
"vite": "bin/vite.js"
@@ -428,7 +429,7 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
"@vitejs/devtools": "^0.1.0",
"@vitejs/devtools": "^0.1.18",
"esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
@@ -1709,6 +1710,40 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@epic-web/invariant": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
@@ -2721,14 +2756,14 @@
}
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.5.tgz",
"integrity": "sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@tybys/wasm-util": "^0.10.1"
"@tybys/wasm-util": "^0.10.2"
},
"funding": {
"type": "github",
@@ -2798,9 +2833,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.127.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
"version": "0.133.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.133.0.tgz",
"integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==",
"dev": true,
"license": "MIT",
"funding": {
@@ -6442,9 +6477,9 @@
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.3.tgz",
"integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==",
"cpu": [
"arm64"
],
@@ -6459,9 +6494,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.3.tgz",
"integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==",
"cpu": [
"arm64"
],
@@ -6476,9 +6511,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
"integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.3.tgz",
"integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==",
"cpu": [
"x64"
],
@@ -6493,9 +6528,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
"integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.3.tgz",
"integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==",
"cpu": [
"x64"
],
@@ -6510,9 +6545,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
"integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.3.tgz",
"integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==",
"cpu": [
"arm"
],
@@ -6527,13 +6562,16 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.3.tgz",
"integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6544,13 +6582,16 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
"integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.3.tgz",
"integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==",
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6561,13 +6602,16 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.3.tgz",
"integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==",
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6578,13 +6622,16 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.3.tgz",
"integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==",
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6595,13 +6642,16 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.3.tgz",
"integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6612,13 +6662,16 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
"integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.3.tgz",
"integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==",
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -6629,9 +6682,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.3.tgz",
"integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==",
"cpu": [
"arm64"
],
@@ -6646,9 +6699,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
"integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.3.tgz",
"integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==",
"cpu": [
"wasm32"
],
@@ -6665,9 +6718,9 @@
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
"integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.3.tgz",
"integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==",
"cpu": [
"arm64"
],
@@ -6682,9 +6735,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
"integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.3.tgz",
"integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==",
"cpu": [
"x64"
],
@@ -7989,9 +8042,9 @@
}
},
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz",
"integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -17106,9 +17159,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.13.tgz",
"integrity": "sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==",
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"funding": [
{
"type": "opencollective",
@@ -17125,7 +17178,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"nanoid": "^3.3.12",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@@ -18382,14 +18435,14 @@
"license": "Unlicense"
},
"node_modules/rolldown": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.3.tgz",
"integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.127.0",
"@rolldown/pluginutils": "1.0.0-rc.17"
"@oxc-project/types": "=0.133.0",
"@rolldown/pluginutils": "^1.0.0"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -18398,27 +18451,27 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.0-rc.17",
"@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
"@rolldown/binding-darwin-x64": "1.0.0-rc.17",
"@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
"@rolldown/binding-android-arm64": "1.0.3",
"@rolldown/binding-darwin-arm64": "1.0.3",
"@rolldown/binding-darwin-x64": "1.0.3",
"@rolldown/binding-freebsd-x64": "1.0.3",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.3",
"@rolldown/binding-linux-arm64-gnu": "1.0.3",
"@rolldown/binding-linux-arm64-musl": "1.0.3",
"@rolldown/binding-linux-ppc64-gnu": "1.0.3",
"@rolldown/binding-linux-s390x-gnu": "1.0.3",
"@rolldown/binding-linux-x64-gnu": "1.0.3",
"@rolldown/binding-linux-x64-musl": "1.0.3",
"@rolldown/binding-openharmony-arm64": "1.0.3",
"@rolldown/binding-wasm32-wasi": "1.0.3",
"@rolldown/binding-win32-arm64-msvc": "1.0.3",
"@rolldown/binding-win32-x64-msvc": "1.0.3"
}
},
"node_modules/rolldown/node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"dev": true,
"license": "MIT"
},
@@ -19481,9 +19534,9 @@
}
},
"node_modules/tinyglobby": {
"version": "0.2.16",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@@ -19671,7 +19724,6 @@
"os": [
"aix"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19688,7 +19740,6 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19705,7 +19756,6 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19722,7 +19772,6 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19739,7 +19788,6 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19756,7 +19804,6 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19773,7 +19820,6 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19790,7 +19836,6 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19807,7 +19852,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19824,7 +19868,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19841,7 +19884,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19858,7 +19900,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19875,7 +19916,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19892,7 +19932,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19909,7 +19948,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19926,7 +19964,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19943,7 +19980,6 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19960,7 +19996,6 @@
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19977,7 +20012,6 @@
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -19994,7 +20028,6 @@
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -20011,7 +20044,6 @@
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -20028,7 +20060,6 @@
"os": [
"openharmony"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -20045,7 +20076,6 @@
"os": [
"sunos"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -20062,7 +20092,6 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -20079,7 +20108,6 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -20096,7 +20124,6 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -20738,9 +20765,9 @@
}
},
"node_modules/vite": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
"integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz",
"integrity": "sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==",
"license": "MIT",
"dependencies": {
"esbuild": "^0.27.0",

View File

@@ -39,6 +39,16 @@
"lodash": "4.18.1"
},
"engines": {
"node": ">=20.0.0"
"node": ">=26.0.0 <27.0.0",
"npm": ">=11.0.0 <12.0.0"
},
"allowScripts": {
"agent-browser@0.26.0": true,
"electron@40.9.3": true,
"electron-winstaller@5.4.0": true,
"esbuild@0.27.7": true,
"esbuild@0.28.0": true,
"node-pty@1.1.0": true,
"unicode-animations": false
}
}

View File

@@ -95,7 +95,7 @@ try {
$RepoUrlSsh = "git@github.com:NousResearch/hermes-agent.git"
$RepoUrlHttps = "https://github.com/NousResearch/hermes-agent.git"
$PythonVersion = "3.11"
$NodeVersion = "22"
$NodeVersion = "26"
# Stage-protocol version. Bumped only for genuinely breaking changes to the
# manifest schema, stage-name set semantics, or stdout JSON shape. Adding a

View File

@@ -57,7 +57,7 @@ else
INSTALL_DIR_EXPLICIT=false
fi
PYTHON_VERSION="3.11"
NODE_VERSION="22"
NODE_VERSION="26"
# FHS-style root install layout (set by resolve_install_layout when applicable):
# code at /usr/local/lib/hermes-agent, command at /usr/local/bin/hermes,
@@ -703,7 +703,7 @@ check_git() {
}
# The desktop build runs Vite ^8, which refuses to start on Node outside
# `^20.19 || >=22.12` — older Node lacks `node:util.styleText`, so `vite build`
# `>=26.0.0` — older Node lacks the required features, so `vite build`
# crashes with a SyntaxError that surfaces only as the opaque "Build desktop
# app … exit code 1" install failure. Returns 0 when the given `node --version`
# string clears that floor; anything below it is replaced with the Hermes-
@@ -737,7 +737,7 @@ check_node() {
fi
if command -v node &> /dev/null; then
log_warn "Node.js $(node --version) is too old for the desktop build (need ^20.19 or >=22.12) — installing Hermes-managed Node $NODE_VERSION LTS..."
log_warn "Node.js $(node --version) is too old for the desktop build (need >=26.0.0) — installing Hermes-managed Node $NODE_VERSION LTS..."
elif [ "$DISTRO" = "termux" ]; then
log_info "Node.js not found — installing Node.js via pkg..."
else

View File

@@ -2,15 +2,13 @@
# ============================================================================
# scripts/lib/node-bootstrap.sh
# ----------------------------------------------------------------------------
# Sourceable helper: ensure Node.js >= MIN_VERSION is available for the TUI
# (React + Ink), browser tools, and the WhatsApp bridge.
# Sourceable helper: ensure Node.js matching .nvmrc (or default 26.3.0) is
# available for the TUI, browser tools, and the WhatsApp bridge.
#
# Strategy (first hit wins — respects the user's existing tooling):
# 1. modern `node` already on PATH
# 2. ~/.hermes/node/ from a prior Hermes-managed install
# 3. fnm, proto, nvm (in that order) if the user already uses a version manager
# 4. Termux `pkg`, macOS Homebrew
# 5. pinned nodejs.org tarball into ~/.hermes/node/ (always works, zero shell rc edits)
# Strategy (strict order):
# 1. ~/.hermes/node/ (Hermes-managed, auto-upgrades if .nvmrc changes)
# 2. `node` already on PATH (fallback, validated against package.json engines)
# 3. Termux `pkg install nodejs` (fallback for Termux)
#
# Usage:
# source scripts/lib/node-bootstrap.sh
@@ -18,20 +16,24 @@
# if [ "$HERMES_NODE_AVAILABLE" = true ]; then ...; fi
#
# Env inputs (set before sourcing to override defaults):
# HERMES_NODE_MIN_VERSION (default: 20) — accepted on PATH
# HERMES_NODE_TARGET_MAJOR (default: 22) — installed when we install
# HERMES_HOME (default: $HOME/.hermes)
# HERMES_NODE_TARGET_VERSION (default: read from .nvmrc, fallback 26.3.0)
# HERMES_HOME (default: $HOME/.hermes)
# ============================================================================
HERMES_NODE_MIN_VERSION="${HERMES_NODE_MIN_VERSION:-20}"
HERMES_NODE_TARGET_MAJOR="${HERMES_NODE_TARGET_MAJOR:-22}"
# Read target version from .nvmrc if present, otherwise default to 26.3.0
if [ -f ".nvmrc" ]; then
HERMES_NODE_TARGET_VERSION=$(cat .nvmrc | tr -d '[:space:]')
else
HERMES_NODE_TARGET_VERSION="${HERMES_NODE_TARGET_VERSION:-26.3.0}"
fi
# Extract major version for fallback compatibility checks
HERMES_NODE_TARGET_MAJOR="${HERMES_NODE_TARGET_VERSION%%.*}"
HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}"
HERMES_NODE_AVAILABLE=false
# ---------------------------------------------------------------------------
# Logging — prefer the host script's log_* helpers when present
# Logging
# ---------------------------------------------------------------------------
_nb_log() { declare -F log_info >/dev/null 2>&1 && log_info "$*" || printf '→ %s\n' "$*" >&2; }
_nb_ok() { declare -F log_success >/dev/null 2>&1 && log_success "$*" || printf '✓ %s\n' "$*" >&2; }
_nb_warn() { declare -F log_warn >/dev/null 2>&1 && log_warn "$*" || printf '⚠ %s\n' "$*" >&2; }
@@ -39,103 +41,53 @@ _nb_warn() { declare -F log_warn >/dev/null 2>&1 && log_warn "$*" || print
# ---------------------------------------------------------------------------
# Platform + version helpers
# ---------------------------------------------------------------------------
_nb_is_termux() {
[ -n "${TERMUX_VERSION:-}" ] || [[ "${PREFIX:-}" == *"com.termux/files/usr"* ]]
}
# Where to symlink node/npm/npx so they land on PATH.
# Mirrors get_command_link_dir() from install.sh: root FHS → /usr/local/bin,
# Termux → $PREFIX/bin, otherwise ~/.local/bin.
_nb_get_link_dir() {
if _nb_is_termux && [ -n "${PREFIX:-}" ]; then
echo "$PREFIX/bin"
elif [ "$(id -u)" = 0 ] && [ "$(uname -s)" = "Linux" ]; then
echo "/usr/local/bin"
else
echo "$HOME/.local/bin"
fi
}
_nb_node_major() {
local v
v=$(node --version 2>/dev/null | sed 's/^v//' | cut -d. -f1)
[[ "$v" =~ ^[0-9]+$ ]] && echo "$v" || echo 0
}
# Dynamically read the minimum required Node major version from package.json's engines field.
# Falls back to HERMES_NODE_TARGET_MAJOR if package.json is missing or unreadable.
_nb_get_engines_min_major() {
local pkg_json="package.json"
if [ ! -f "$pkg_json" ] && [ -f "../package.json" ]; then
pkg_json="../package.json"
fi
if [ -f "$pkg_json" ]; then
# Extract the first number from the "node" engine string (e.g., ">=26.0.0" -> 26)
# This avoids matching "node_modules" or other fields by requiring the specific JSON structure.
local min_major
min_major=$(grep -E '"node"[[:space:]]*:[[:space:]]*"[^"]*"' "$pkg_json" 2>/dev/null | grep -o '[0-9]\+' | head -1)
if [[ "$min_major" =~ ^[0-9]+$ ]]; then
echo "$min_major"
return 0
fi
fi
# Fallback to target major version if parsing fails
echo "${HERMES_NODE_TARGET_MAJOR}"
}
_nb_have_modern_node() {
command -v node >/dev/null 2>&1 || return 1
[ "$(_nb_node_major)" -ge "$HERMES_NODE_MIN_VERSION" ]
local current_major
current_major=$(_nb_node_major)
local required_major
required_major=$(_nb_get_engines_min_major)
[ "$current_major" -ge "$required_major" ]
}
# ---------------------------------------------------------------------------
# Version-manager paths — respect what the user already uses
# Hermes-managed Node.js installation (fallback to nodejs.org tarball)
# ---------------------------------------------------------------------------
_nb_try_fnm() {
command -v fnm >/dev/null 2>&1 || return 1
_nb_log "fnm detected — installing Node $HERMES_NODE_TARGET_MAJOR..."
eval "$(fnm env 2>/dev/null)" || true
fnm install "$HERMES_NODE_TARGET_MAJOR" >/dev/null 2>&1 || return 1
fnm use "$HERMES_NODE_TARGET_MAJOR" >/dev/null 2>&1 || return 1
_nb_have_modern_node || return 1
_nb_ok "Node $(node --version) activated via fnm"
return 0
}
_nb_try_proto() {
command -v proto >/dev/null 2>&1 || return 1
_nb_log "proto detected — installing Node $HERMES_NODE_TARGET_MAJOR..."
proto install node "$HERMES_NODE_TARGET_MAJOR" >/dev/null 2>&1 || return 1
_nb_have_modern_node || return 1
_nb_ok "Node $(node --version) activated via proto"
return 0
}
_nb_try_nvm() {
local nvm_sh="${NVM_DIR:-$HOME/.nvm}/nvm.sh"
[ -s "$nvm_sh" ] || return 1
# shellcheck source=/dev/null
\. "$nvm_sh" >/dev/null 2>&1 || return 1
_nb_log "nvm detected — installing Node $HERMES_NODE_TARGET_MAJOR..."
nvm install "$HERMES_NODE_TARGET_MAJOR" >/dev/null 2>&1 || return 1
nvm use "$HERMES_NODE_TARGET_MAJOR" >/dev/null 2>&1 || return 1
_nb_have_modern_node || return 1
_nb_ok "Node $(node --version) activated via nvm"
return 0
}
# ---------------------------------------------------------------------------
# Platform package managers
# ---------------------------------------------------------------------------
_nb_try_termux_pkg() {
_nb_is_termux || return 1
_nb_log "Installing Node.js via pkg..."
pkg install -y nodejs >/dev/null 2>&1 || return 1
_nb_have_modern_node || return 1
_nb_ok "Node $(node --version) installed via pkg"
return 0
}
_nb_try_brew() {
[ "$(uname -s)" = "Darwin" ] || return 1
command -v brew >/dev/null 2>&1 || return 1
_nb_log "Installing Node via Homebrew..."
brew install "node@${HERMES_NODE_TARGET_MAJOR}" >/dev/null 2>&1 \
|| brew install node >/dev/null 2>&1 \
|| return 1
brew link --overwrite --force "node@${HERMES_NODE_TARGET_MAJOR}" >/dev/null 2>&1 || true
_nb_have_modern_node || return 1
_nb_ok "Node $(node --version) installed via Homebrew"
return 0
}
# ---------------------------------------------------------------------------
# Bundled binary fallback — always works, no shell rc edits
# ---------------------------------------------------------------------------
_nb_install_bundled_node() {
_nb_install_hermes_node() {
local arch node_arch os_name node_os
arch=$(uname -m)
case "$arch" in
@@ -143,7 +95,7 @@ _nb_install_bundled_node() {
aarch64|arm64) node_arch="arm64" ;;
armv7l) node_arch="armv7l" ;;
*)
_nb_warn "Unsupported arch ($arch) — install Node.js manually: https://nodejs.org/"
_nb_warn "Unsupported arch ($arch) for Hermes-managed Node.js"
return 1
;;
esac
@@ -153,7 +105,7 @@ _nb_install_bundled_node() {
Linux*) node_os="linux" ;;
Darwin*) node_os="darwin" ;;
*)
_nb_warn "Unsupported OS ($os_name) — install Node.js manually: https://nodejs.org/"
_nb_warn "Unsupported OS ($os_name) for Hermes-managed Node.js"
return 1
;;
esac
@@ -169,7 +121,7 @@ _nb_install_bundled_node() {
| head -1)
fi
if [ -z "$tarball" ]; then
_nb_warn "Could not resolve Node $HERMES_NODE_TARGET_MAJOR binary for $node_os-$node_arch"
_nb_warn "Could not resolve Node $HERMES_NODE_TARGET_VERSION binary for $node_os-$node_arch"
return 1
fi
@@ -200,54 +152,88 @@ _nb_install_bundled_node() {
mv "$extracted" "$HERMES_HOME/node"
rm -rf "$tmp"
local _link_dir
_link_dir="$(_nb_get_link_dir)"
# Symlink to standard bin dir so it's on PATH for other tools
local _link_dir="$HOME/.local/bin"
if _nb_is_termux && [ -n "${PREFIX:-}" ]; then
_link_dir="$PREFIX/bin"
elif [ "$(id -u)" = 0 ] && [ "$os_name" = "Linux" ]; then
_link_dir="/usr/local/bin"
fi
mkdir -p "$_link_dir"
ln -sf "$HERMES_HOME/node/bin/node" "$_link_dir/node"
ln -sf "$HERMES_HOME/node/bin/npm" "$_link_dir/npm"
ln -sf "$HERMES_HOME/node/bin/npx" "$_link_dir/npx"
export PATH="$HERMES_HOME/node/bin:$PATH"
_nb_have_modern_node || return 1
if ! _nb_have_modern_node; then
_nb_warn "Installed Node.js version check failed"
return 1
fi
_nb_ok "Node $(node --version) installed to $HERMES_HOME/node/"
return 0
}
# ---------------------------------------------------------------------------
# Termux pkg fallback
# ---------------------------------------------------------------------------
_nb_try_termux_pkg() {
_nb_is_termux || return 1
_nb_log "Installing Node.js via pkg..."
pkg install -y nodejs >/dev/null 2>&1 || return 1
if _nb_have_modern_node; then
_nb_ok "Node $(node --version) installed via pkg"
return 0
fi
return 1
}
# ---------------------------------------------------------------------------
# Public entry point
# ---------------------------------------------------------------------------
ensure_node() {
HERMES_NODE_AVAILABLE=false
if _nb_have_modern_node; then
_nb_ok "Node $(node --version) found"
HERMES_NODE_AVAILABLE=true
return 0
fi
# 1. Hermes-managed Node.js (~/.hermes/node/)
if [ -x "$HERMES_HOME/node/bin/node" ]; then
export PATH="$HERMES_HOME/node/bin:$PATH"
if _nb_have_modern_node; then
local managed_ver
managed_ver=$("$HERMES_HOME/node/bin/node" --version 2>/dev/null | sed 's/^v//')
if [ "$managed_ver" = "$HERMES_NODE_TARGET_VERSION" ]; then
export PATH="$HERMES_HOME/node/bin:$PATH"
_nb_ok "Node $(node --version) found (Hermes-managed)"
HERMES_NODE_AVAILABLE=true
return 0
else
_nb_log "Managed Node.js is $managed_ver, but .nvmrc requires $HERMES_NODE_TARGET_VERSION. Updating..."
if _nb_install_hermes_node; then
HERMES_NODE_AVAILABLE=true
return 0
fi
fi
else
# Not installed yet, try to install it first
if _nb_install_hermes_node; then
HERMES_NODE_AVAILABLE=true
return 0
fi
fi
# Version managers first — respect the user's existing setup.
_nb_try_fnm && { HERMES_NODE_AVAILABLE=true; return 0; }
_nb_try_proto && { HERMES_NODE_AVAILABLE=true; return 0; }
_nb_try_nvm && { HERMES_NODE_AVAILABLE=true; return 0; }
# 2. Node already on PATH (validated against package.json engines)
if _nb_have_modern_node; then
_nb_ok "Node $(node --version) found on PATH (meets package.json engines requirement)"
HERMES_NODE_AVAILABLE=true
return 0
fi
# Platform package managers.
_nb_try_termux_pkg && { HERMES_NODE_AVAILABLE=true; return 0; }
_nb_try_brew && { HERMES_NODE_AVAILABLE=true; return 0; }
# Last resort: pinned nodejs.org tarball.
_nb_install_bundled_node && { HERMES_NODE_AVAILABLE=true; return 0; }
# 3. Termux pkg (fallback)
if _nb_try_termux_pkg; then
HERMES_NODE_AVAILABLE=true
return 0
fi
_nb_warn "Node.js install failed — TUI and browser tools will be unavailable."
_nb_warn "Install manually: https://nodejs.org/en/download/ (or: \`brew install node\`, \`fnm install $HERMES_NODE_TARGET_MAJOR\`, etc.)"
_nb_warn "Install manually: https://nodejs.org/en/download/"
return 1
}

View File

@@ -1068,7 +1068,7 @@ def _safe_getcwd() -> str:
def _get_env_config() -> Dict[str, Any]:
"""Get terminal environment configuration from environment variables."""
# Default image with Python and Node.js for maximum compatibility
default_image = "nikolaik/python-nodejs:python3.11-nodejs20"
default_image = "nikolaik/python-nodejs:python3.11-nodejs26"
env_type = os.getenv("TERMINAL_ENV", "local")
mount_docker_cwd = os.getenv("TERMINAL_DOCKER_MOUNT_CWD_TO_WORKSPACE", "false").lower() in {"true", "1", "yes"}
@@ -2592,7 +2592,7 @@ if __name__ == "__main__":
print(" result = terminal_tool(command='python server.py', background=True)")
print("\nEnvironment Variables:")
default_img = "nikolaik/python-nodejs:python3.11-nodejs20"
default_img = "nikolaik/python-nodejs:python3.11-nodejs26"
print(
" TERMINAL_ENV: "
f"{os.getenv('TERMINAL_ENV', 'local')} "

View File

@@ -93,7 +93,7 @@ This is the standalone command. The Zed registry's terminal-auth flow (`hermes a
What it does:
- Installs Node.js 22 LTS into `~/.hermes/node/` if missing
- Installs Node.js 26 LTS into `~/.hermes/node/` if missing
- `npm install -g agent-browser @askjo/camofox-browser` into that prefix (no sudo needed — `npm`'s `--prefix` points at the user-writable Hermes-managed Node)
- Installs Playwright Chromium, or uses a detected system Chrome/Chromium when available

View File

@@ -69,7 +69,7 @@ iex (irm https://hermes-agent.nousresearch.com/install.ps1)
1. **引导 `uv`** — Astral 的快速 Python 管理器。安装到 `%USERPROFILE%\.local\bin`
2. **通过 `uv` 安装 Python 3.11**。无需预先安装 Python。
3. **安装 Node.js 22**(优先使用 winget否则将便携式 Node 压缩包解压到 `%LOCALAPPDATA%\hermes\node`)。用于浏览器工具和 WhatsApp 桥接。
3. **安装 Node.js 26**(优先使用 winget否则将便携式 Node 压缩包解压到 `%LOCALAPPDATA%\hermes\node`)。用于浏览器工具和 WhatsApp 桥接。
4. **安装便携式 Git** — 如果 `git` 已在 PATH 中,安装程序直接使用;否则从官方 `git-for-windows` 发布版下载精简的自包含 **PortableGit**(约 45 MB`%LOCALAPPDATA%\hermes\git`。无需管理员权限,不写入 Windows 安装程序注册表,不干扰系统上的其他任何内容。
5. **将仓库克隆**到 `%LOCALAPPDATA%\hermes\hermes-agent` 并在其中创建 virtualenv。
6. **分层 `uv pip install`** — 先尝试 `.[all]`,如果 `git+https` 依赖在 GitHub 限速时失败,则逐步回退到更小的集合(`[messaging,dashboard,ext]``[messaging]``.`)。防止"单次失败导致裸安装"的故障模式。