Files
buzz/desktop/src-tauri/src/managed_agents/managed_node_paths.rs
Tyler 98a7b13348 fix(node): bump Buzz-supplied Node runtimes past OpenClaw's >=24.15.0 floor (#3218)
## Problem

User report:

```
Buzz Node mismatch: Buzz supplies Node 24.14.0; OpenClaw requires >=24.15.0. All 10 ACP workers immediately crash.
```

Buzz supplies Node to agent processes from two places, and both were
below OpenClaw's floor:

| Supply path | Was | Now |
|---|---|---|
| hermit dev env (`bin/.node-*.pkg`) — the 24.14.0 in the report |
24.14.0 | **24.15.0** (newest hermit publishes; satisfies `>=24.15.0`) |
| Desktop managed runtime (`managed_node.rs` / `managed_node_paths.rs`)
| v24.11.0 | **v24.18.0** (current latest v24) |

The managed runtime sits **first** on the worker PATH
(`managed_agents/runtime/path.rs`), so a user-installed newer Node can't
mask a stale managed one — the pin itself has to move.

## Verification

- SHA-256 digests for all six platform artifacts taken from
`https://nodejs.org/dist/v24.18.0/SHASUMS256.txt`; darwin-arm64
independently re-verified by downloading the tarball (hash match),
extracting, and running `bin/node --version` → `v24.18.0`.
- All artifacts within `MANAGED_NODE_MAX_BYTES` (largest linux-x64 at 57
MB < 90 MB cap); tar.gz layout keeps the `node-vX-platform/bin/node`
shape `verify_node_tree` expects.
- `cargo test --lib` in `desktop/src-tauri`: **1801 passed, 0 failed**
at this commit; `cargo fmt --check` + `cargo clippy --lib -D warnings`
clean.
- Existing readiness check (`node --version == MANAGED_NODE_VERSION`)
makes upgrade automatic: installed v24.11.0 trees fail readiness and the
installer stages v24.18.0 atomically (rename with `.old` rollback —
existing logic, unchanged).

Note: CI `node-version: 24.14.1` pins in
`release.yml`/`windows-canary.yml` are build-env only (already `>=`
nothing OpenClaw touches) and left alone to keep this minimal.

Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
2026-07-27 22:18:02 +00:00

102 lines
2.8 KiB
Rust

use std::path::PathBuf;
pub(crate) fn buzz_managed_npm_prefix() -> Option<PathBuf> {
dirs::data_dir().map(|dir| dir.join("Buzz").join("node-tools"))
}
const BUZZ_MANAGED_NODE_VERSION: &str = "v24.18.0";
pub(crate) fn buzz_managed_node_root() -> Option<PathBuf> {
dirs::data_dir().map(|dir| dir.join("Buzz").join("runtimes").join("node"))
}
pub(crate) fn buzz_managed_node_bin_dir() -> Option<PathBuf> {
let (platform, bin_subdir): (&str, Option<&str>) =
match (std::env::consts::OS, std::env::consts::ARCH) {
("macos", "aarch64") => ("darwin-arm64", Some("bin")),
("macos", "x86_64") => ("darwin-x64", Some("bin")),
("linux", "x86_64") => ("linux-x64", Some("bin")),
("linux", "aarch64") => ("linux-arm64", Some("bin")),
// Windows zips have node.exe + npm.cmd at the archive root — no bin/ subdir
("windows", "x86_64") => ("win-x64", None),
("windows", "aarch64") => ("win-arm64", None),
_ => return None,
};
buzz_managed_node_root().map(|root| {
let dir = root.join(BUZZ_MANAGED_NODE_VERSION).join(platform);
match bin_subdir {
Some(sub) => dir.join(sub),
None => dir,
}
})
}
pub(crate) fn buzz_managed_node_bin_path() -> Option<PathBuf> {
buzz_managed_node_bin_dir().map(|bin| {
#[cfg(windows)]
{
bin.join("node.exe")
}
#[cfg(not(windows))]
{
bin.join("node")
}
})
}
pub(crate) fn buzz_managed_npm_bin_dir() -> Option<PathBuf> {
buzz_managed_npm_prefix().map(|prefix| {
#[cfg(windows)]
{
prefix
}
#[cfg(not(windows))]
{
prefix.join("bin")
}
})
}
pub(crate) fn buzz_managed_command_path(command: &str, basename: &str) -> Option<PathBuf> {
if command.contains(std::path::MAIN_SEPARATOR)
|| !matches!(
command,
"codex-acp" | "claude-agent-acp" | "claude-code-acp" | "node" | "npm"
)
{
return None;
}
let mut dirs = Vec::new();
if let Some(managed_bin) = buzz_managed_npm_bin_dir() {
dirs.push(managed_bin);
}
if let Some(managed_node_bin) = buzz_managed_node_bin_dir() {
dirs.push(managed_node_bin);
}
dirs.into_iter()
.map(|dir| dir.join(basename))
.find(|candidate| is_executable_file(candidate))
}
fn is_executable_file(path: &std::path::Path) -> bool {
let Ok(metadata) = path.metadata() else {
return false;
};
if !metadata.is_file() {
return false;
}
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
metadata.permissions().mode() & 0o111 != 0
}
#[cfg(not(unix))]
{
true
}
}