diff --git a/.github/workflows/deploy-site.yml b/.github/workflows/deploy-site.yml index 069de887eb810..588d7707ea76b 100644 --- a/.github/workflows/deploy-site.yml +++ b/.github/workflows/deploy-site.yml @@ -65,7 +65,7 @@ jobs: - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version: 26 cache: npm cache-dependency-path: website/package-lock.json diff --git a/.github/workflows/docs-site-checks.yml b/.github/workflows/docs-site-checks.yml index 63766184dba8c..cf775f89e0320 100644 --- a/.github/workflows/docs-site-checks.yml +++ b/.github/workflows/docs-site-checks.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version: 26 cache: npm cache-dependency-path: website/package-lock.json diff --git a/.github/workflows/e2e-desktop.yml b/.github/workflows/e2e-desktop.yml index ed0023399dca4..b951c60eadb28 100644 --- a/.github/workflows/e2e-desktop.yml +++ b/.github/workflows/e2e-desktop.yml @@ -39,7 +39,7 @@ jobs: # ── Node ─────────────────────────────────────────────────────────── - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version: 26 cache: npm - name: grab npm 12 diff --git a/.github/workflows/js-autofix.yml b/.github/workflows/js-autofix.yml index aa04d1b778dab..d0fa3513e86c0 100644 --- a/.github/workflows/js-autofix.yml +++ b/.github/workflows/js-autofix.yml @@ -67,7 +67,7 @@ jobs: - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version: 26 cache: npm - name: grab npm 12 diff --git a/.github/workflows/js-tests.yml b/.github/workflows/js-tests.yml index c6f7376313114..9119e0c7a9b9f 100644 --- a/.github/workflows/js-tests.yml +++ b/.github/workflows/js-tests.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version: 26 cache: npm - name: grab npm 12 @@ -66,7 +66,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 + node-version: 26 cache: npm - name: grab npm 12 diff --git a/Dockerfile b/Dockerfile index 0bb092ae85e39..ad52c8102753b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,14 +41,14 @@ RUN apt-get -o Acquire::Retries=3 update && \ make install 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 -# 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 +# upstream node:26 image instead (Hermes pins its toolchain to Node 26 +# everywhere). 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:26-bookworm-slim@sha256:9e6f9357d371591e32ab6f2d8a26d63bdd0d17c29eee3f4f3e7e454d9634bf73 AS node_source FROM debian:13.4 # Disable Python stdout buffering to ensure logs are printed immediately. diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 518402303711f..479219e9eb41b 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -8,7 +8,7 @@ "type": "module", "main": "dist/electron-main.mjs", "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=26.0.0" }, "scripts": { "clean": "npm run clean:e2e && npm run clean:renderer && npm run clean:electron", diff --git a/hermes_constants.py b/hermes_constants.py index 69e9d3d13e1b9..e0368657510ec 100644 --- a/hermes_constants.py +++ b/hermes_constants.py @@ -317,7 +317,7 @@ def _candidate_node_command_names(command: str) -> list[str]: return [f"{base}.cmd", f"{base}.exe", base] -_HERMES_NODE_TARGET_MAJOR = int(os.environ.get("HERMES_NODE_TARGET_MAJOR", "22")) +_HERMES_NODE_TARGET_MAJOR = int(os.environ.get("HERMES_NODE_TARGET_MAJOR", "26")) _managed_node_heal_attempted = False _NODE_BOOTSTRAP_SCRIPT = Path(__file__).resolve().parent / "scripts" / "lib" / "node-bootstrap.sh" diff --git a/nix/checks.nix b/nix/checks.nix index 9e69d4c0f8326..1cf34212597ab 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -256,8 +256,8 @@ json.dump(sorted(leaf_paths(DEFAULT_CONFIG)), sys.stdout, indent=2) echo "ok" > $out/result ''; - # Verify HERMES_NODE is set in wrapper and points to Node 20+ - # (string-width uses the /v regex flag which requires Node 20+) + # Verify HERMES_NODE is set in wrapper and points to Node 26+ + # (Hermes pins its toolchain to Node 26 everywhere) hermes-node = pkgs.runCommand "hermes-node-version" { } '' set -e echo "=== Checking HERMES_NODE in wrapper ===" @@ -270,9 +270,9 @@ json.dump(sorted(leaf_paths(DEFAULT_CONFIG)), sys.stdout, indent=2) echo "PASS: HERMES_NODE executable at $HERMES_NODE" NODE_MAJOR=$("$HERMES_NODE" --version | sed 's/^v//' | cut -d. -f1) - test "$NODE_MAJOR" -ge 20 || \ - (echo "FAIL: Node v$NODE_MAJOR < 20, TUI needs /v regex flag support"; exit 1) - echo "PASS: Node v$NODE_MAJOR >= 20" + test "$NODE_MAJOR" -ge 26 || \ + (echo "FAIL: Node v$NODE_MAJOR < 26, Hermes requires Node 26"; exit 1) + echo "PASS: Node v$NODE_MAJOR >= 26" echo "=== All HERMES_NODE checks passed ===" mkdir -p $out diff --git a/package-lock.json b/package-lock.json index d6e44fc4cd371..337d5ef1e0d6e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "typescript-eslint": "8.64.0" }, "engines": { - "node": ">=20.0.0", + "node": ">=26.0.0", "npm": ">=12.0.0" } }, @@ -168,7 +168,7 @@ "wait-on": "9.0.10" }, "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=26.0.0" } }, "apps/desktop/node_modules/ignore": { diff --git a/package.json b/package.json index 7ba15ff549abb..a22d0f1b8e668 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "brace-expansion": "5.0.8" }, "engines": { - "node": ">=20.0.0", + "node": ">=26.0.0", "npm": ">=12.0.0" }, "allowScripts": { diff --git a/scripts/install.ps1 b/scripts/install.ps1 index 577cf18e41d89..a4eefc72beadf 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -150,7 +150,7 @@ $PythonVersion = "3.11" # interpreters, so this list also matches a pre-existing system Python. Single # source of truth shared by Test-Python's fallback and Resolve-AvailablePythonVersion. $PythonFallbackVersions = @("3.12", "3.13", "3.10") -$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 @@ -1089,11 +1089,10 @@ function Set-GitBashEnvVar { Write-Info "If needed, set HERMES_GIT_BASH_PATH manually to your bash.exe path." } -# 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` -# crashes with a SyntaxError that surfaces only as the opaque "Build desktop -# app ... exit code 1" install failure. Returns $true when a `node --version` -# string clears that floor. +# Hermes requires Node 26 across every install: the desktop build's toolchain +# floor is pinned there and the managed runtime, heal, and upgrade paths all +# provision latest-v26.x. Returns $true when a `node --version` string clears +# that floor. function Test-NodeVersionOk { param([string]$Version) try { @@ -1101,9 +1100,7 @@ function Test-NodeVersionOk { } catch { return $false } - if ($v.Major -eq 20 -and $v.Minor -ge 19) { return $true } - if ($v.Major -ge 22 -and ($v.Major -gt 22 -or $v.Minor -ge 12)) { return $true } - return $false + return ($v.Major -ge 26) } function Test-Node { @@ -1117,7 +1114,7 @@ function Test-Node { $script:HasNode = $true return $true } - Write-Warn "Node.js $version is too old for the desktop build (need ^20.19 or >=22.12)" + Write-Warn "Node.js $version is too old (Hermes requires Node >=26)" } # Prefer a Hermes-managed Node from a previous run over a too-old system one. @@ -1208,7 +1205,7 @@ function Test-Node { # even after a "successful" install. The OpenJS manifest does # publish an arm64 installer, so this is safe. $wingetArgs = @( - 'install','OpenJS.NodeJS.LTS','--silent', + 'install','OpenJS.NodeJS','--silent', '--accept-package-agreements','--accept-source-agreements' ) if ((Get-WindowsArch) -eq 'arm64') { @@ -2923,7 +2920,7 @@ function Install-Desktop { # Always re-resolve Node here. Stages run in separate PowerShell processes, # so $script:HasNode from Stage-Node isn't visible; more importantly Test-Node - # enforces the build floor (^20.19 || >=22.12) and prepends the Hermes-managed + # enforces the build floor (Node >=26) and prepends the Hermes-managed # Node to PATH, so the build never runs on a too-old system Node -- the cause # of the opaque "Build desktop app ... exit code 1" failure (Vite crashes on # old Node). diff --git a/scripts/install.sh b/scripts/install.sh index 071081b045622..38198eab463b0 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -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, @@ -780,21 +780,16 @@ check_git() { exit 1 } -# 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` -# 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- -# managed Node $NODE_VERSION LTS. +# Hermes requires Node 26 across every install: the desktop build's toolchain +# floor is pinned there and the managed runtime, heal, and upgrade paths all +# provision latest-v26.x. Returns 0 when the given `node --version` string +# clears that floor; anything below it is replaced with the Hermes-managed +# Node $NODE_VERSION. node_satisfies_build() { local ver="${1#v}" local major="${ver%%.*}" - local minor="${ver#*.}"; minor="${minor%%.*}" case "$major" in ''|*[!0-9]*) return 1 ;; esac - case "$minor" in ''|*[!0-9]*) minor=0 ;; esac - if [ "$major" -eq 20 ] && [ "$minor" -ge 19 ]; then return 0; fi - if [ "$major" -ge 22 ] && { [ "$major" -gt 22 ] || [ "$minor" -ge 12 ]; }; then return 0; fi - return 1 + [ "$major" -ge 26 ] } check_node() { @@ -820,7 +815,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 (Hermes requires Node >=26) — installing Hermes-managed Node $NODE_VERSION..." elif [ "$DISTRO" = "termux" ]; then log_info "Node.js not found — installing Node.js via pkg..." else @@ -869,7 +864,7 @@ install_node() { ;; esac - # Resolve the latest v22.x.x tarball name from the index page + # Resolve the latest v${NODE_VERSION}.x.x tarball name from the index page local index_url="https://nodejs.org/dist/latest-v${NODE_VERSION}.x/" local tarball_name tarball_name=$(curl -fsSL "$index_url" \ @@ -2928,7 +2923,7 @@ install_desktop() { # with no app and a confusing "couldn't find a built desktop" at launch. # Always re-resolve Node here. Stages run in separate processes, so we can't # trust an earlier check; more importantly check_node now enforces the build - # floor (^20.19 || >=22.12) and prepends the Hermes-managed Node to PATH, so + # floor (Node >=26) and prepends the Hermes-managed Node to PATH, so # the build never runs on a too-old system Node — the cause of the opaque # "Build desktop app … exit code 1" failure (Vite crashes on old Node). check_node diff --git a/scripts/lib/node-bootstrap.sh b/scripts/lib/node-bootstrap.sh index 194f87ae51317..3a5fd7fbc6fe6 100644 --- a/scripts/lib/node-bootstrap.sh +++ b/scripts/lib/node-bootstrap.sh @@ -18,13 +18,13 @@ # 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_NODE_MIN_VERSION (default: 26) — accepted on PATH +# HERMES_NODE_TARGET_MAJOR (default: 26) — installed when we install # HERMES_HOME (default: $HOME/.hermes) # ============================================================================ -HERMES_NODE_MIN_VERSION="${HERMES_NODE_MIN_VERSION:-20}" -HERMES_NODE_TARGET_MAJOR="${HERMES_NODE_TARGET_MAJOR:-22}" +HERMES_NODE_MIN_VERSION="${HERMES_NODE_MIN_VERSION:-26}" +HERMES_NODE_TARGET_MAJOR="${HERMES_NODE_TARGET_MAJOR:-26}" HERMES_HOME="${HERMES_HOME:-$HOME/.hermes}" HERMES_NODE_AVAILABLE=false diff --git a/website/docs/developer-guide/contributing.md b/website/docs/developer-guide/contributing.md index 5f5d7fd049072..b3ca5de929f2f 100644 --- a/website/docs/developer-guide/contributing.md +++ b/website/docs/developer-guide/contributing.md @@ -36,7 +36,7 @@ We value contributions in this order: | **Git** | With the `git-lfs` extension installed | | **Python 3.11–3.13** | uv will install it if missing | | **uv** | Fast Python package manager ([install](https://docs.astral.sh/uv/)) | -| **Node.js 20+** | Optional — needed for browser tools and WhatsApp bridge (matches root `package.json` engines) | +| **Node.js 26+** | Optional — needed for browser tools and WhatsApp bridge (matches root `package.json` engines) | ### Install with the standard installer diff --git a/website/docs/getting-started/nix-setup.md b/website/docs/getting-started/nix-setup.md index 17d34883c9e66..2919d14acfb86 100644 --- a/website/docs/getting-started/nix-setup.md +++ b/website/docs/getting-started/nix-setup.md @@ -772,7 +772,7 @@ nix develop # Shell provides: # - Python 3.12 + uv (deps installed into .venv on first entry) -# - Node.js 22, ripgrep, git, openssh, ffmpeg on PATH +# - Node.js 26, ripgrep, git, openssh, ffmpeg on PATH # - Stamp-file optimization: re-entry is near-instant if deps haven't changed hermes setup diff --git a/website/docs/user-guide/docker.md b/website/docs/user-guide/docker.md index c072a01804efb..fd0c047d4837c 100644 --- a/website/docs/user-guide/docker.md +++ b/website/docs/user-guide/docker.md @@ -472,7 +472,7 @@ docker run -d \ The official image is based on `debian:13.4` and includes: - Python 3.13 with dependencies synced from the lockfile via `uv sync --frozen --no-install-project` for the baked extras (`all`, `messaging`, Anthropic/Bedrock/Azure identity, Hindsight, Matrix), followed by a no-dependency editable install of Hermes itself. -- Node.js 22 + npm (for browser automation, WhatsApp bridge, TUI/Desktop bundles, and workspace build tooling) +- Node.js 26 + npm (for browser automation, WhatsApp bridge, TUI/Desktop bundles, and workspace build tooling) - Playwright with Chromium (`npx playwright install --with-deps chromium --only-shell`) - ripgrep, ffmpeg, git, and `xz-utils` as system utilities - **`docker-cli`** — so agents running inside the container can drive the host's Docker daemon (bind-mount `/var/run/docker.sock` to opt in) for `docker build`, `docker run`, container inspection, etc. diff --git a/website/docs/user-guide/features/acp.md b/website/docs/user-guide/features/acp.md index 9562fba464e82..1424c427f0418 100644 --- a/website/docs/user-guide/features/acp.md +++ b/website/docs/user-guide/features/acp.md @@ -89,7 +89,7 @@ This is the standalone command. The terminal-auth flow (`hermes acp --setup`) al What it does: -- Installs Node.js 22 LTS into `~/.hermes/node/` if missing +- Installs Node.js 26 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 diff --git a/website/docs/user-guide/windows-native.md b/website/docs/user-guide/windows-native.md index 91ca51045719a..ac8d32770a04a 100644 --- a/website/docs/user-guide/windows-native.md +++ b/website/docs/user-guide/windows-native.md @@ -56,7 +56,7 @@ On first launch (and on demand when a missing tool is detected), Hermes runs a s | Dependency | Why Hermes needs it | |---|---| | **PortableGit** | Provides `bash.exe` for the terminal tool and `git` for in-session clones. Provisioned at install time, not by `dep_ensure`. | -| **Node.js 22** | Required for the browser tool (`agent-browser`), the TUI's web bridge, and the WhatsApp bridge. | +| **Node.js 26** | Required for the browser tool (`agent-browser`), the TUI's web bridge, and the WhatsApp bridge. | | **ffmpeg** | Audio format conversion for TTS / voice messages. | | **ripgrep** | Fast file search — falls back to `grep` if unavailable. | | **npm packages** | `agent-browser`, Playwright Chromium, and any per-toolset Node deps are installed once at first browser-tool use. | @@ -69,7 +69,7 @@ Top-to-bottom, in order: 1. **Bootstraps `uv`** — Astral's fast Python manager. Installed to `%USERPROFILE%\.local\bin`. 2. **Installs Python 3.11** via `uv`. No existing Python needed. -3. **Installs Node.js 22** (winget if available, else a portable Node tarball unpacked under `%LOCALAPPDATA%\hermes\node`). Used for the browser tool and the WhatsApp bridge. +3. **Installs Node.js 26** (winget if available, else a portable Node tarball unpacked under `%LOCALAPPDATA%\hermes\node`). Used for the browser tool and the WhatsApp bridge. 4. **Installs portable Git** — if `git` is already on PATH the installer uses it; otherwise it downloads a trimmed, self-contained **PortableGit** (~45 MB, from the official `git-for-windows` release) to `%LOCALAPPDATA%\hermes\git`. No admin, no Windows installer registry, no interference with anything else on the box. 5. **Clones the repo** to `%LOCALAPPDATA%\hermes\hermes-agent` and creates a virtualenv inside it. 6. **Tiered `uv pip install`** — tries `.[all]` first, falls back to progressively smaller sets (`[messaging,dashboard,ext]` → `[messaging]` → `.`) if a `git+https` dep flakes on rate-limited GitHub. Prevents "single flake drops you to a bare install" failure mode. @@ -306,7 +306,7 @@ You set it in the current process only; close and reopen the shell, or set it at Chromium is auto-installed on first run. If the install failed (rate-limited GitHub, Playwright CDN hiccup), run `hermes doctor` — it will surface the missing Chromium and print the exact `npx playwright install chromium` command to fix it. **`agent-browser` fails with a weird Node version error.** -The installer provisions Node 22 at `%LOCALAPPDATA%\hermes\node` but your PATH may have an older system Node 18 first. Either move Hermes's node dir earlier on PATH, or delete the system install if you don't use Node elsewhere. +The installer provisions Node 26 at `%LOCALAPPDATA%\hermes\node` but your PATH may have an older system Node 18 first. Either move Hermes's node dir earlier on PATH, or delete the system install if you don't use Node elsewhere. **Chinese / Japanese / Arabic characters show as `?` in the CLI.** The UTF-8 stdio shim didn't activate. Check that `HERMES_DISABLE_WINDOWS_UTF8` is NOT set (`Get-ChildItem env:HERMES_DISABLE_WINDOWS_UTF8`). If it's empty and you still see `?`, the console host (very old `cmd.exe`) may not support UTF-8 at all — switch to Windows Terminal.