@@ -20,9 +20,8 @@ RUN rustup toolchain install 1.94.1 --profile minimal \
# libraries (libgit2-sys, zstd-sys, libsqlite3-sys). No audio/GUI -dev
# packages required — eval-cli runs headless with those features disabled.
#
-# cargo-zigbuild cross-compiles against a specific glibc version (2.31 =
-# Debian Bullseye / Ubuntu Focal) so the resulting binary is portable to
-# any Linux distro with glibc >= 2.31.
+# cargo-zigbuild cross-compiles against musl libc, producing a fully
+# static binary that runs on any Linux distro (glibc or musl / Alpine).
RUN apt-get update && apt-get install -y --no-install-recommends \
cmake \
build-essential \
@@ -43,8 +42,8 @@ RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/app/target \
cargo zigbuild --release --package eval_cli \
- --target x86_64-unknown-linux-gnu.2.31 && \
- cp /app/target/x86_64-unknown-linux-gnu/release/eval-cli /eval-cli && \
+ --target x86_64-unknown-linux-musl && \
+ cp /app/target/x86_64-unknown-linux-musl/release/eval-cli /eval-cli && \
strip /eval-cli
FROM scratch
@@ -84,110 +84,37 @@ class ZedAgent(BaseInstalledAgent):
return workdir
async def install(self, environment: BaseEnvironment) -> None:
+ # Detect the package manager and install base dependencies.
+ # Supports Debian/Ubuntu (apt-get), Alpine (apk), and
+ # Fedora/RHEL/CentOS (dnf/yum).
await self.exec_as_root(
environment,
command=(
- "apt-get update && "
- "apt-get install -y --no-install-recommends "
- "ca-certificates "
- "curl "
- "git"
- ),
- env={"DEBIAN_FRONTEND": "noninteractive"},
- )
-
- await self.exec_as_root(
- environment,
- command=(
- "curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && "
- "apt-get install -y --no-install-recommends nodejs"
- ),
- env={"DEBIAN_FRONTEND": "noninteractive"},
- )
-
- # Pre-install default LSPs so Zed doesn't have to download them at
- # runtime. Each gets its own subdirectory under $ZED_DATA_DIR/languages.
- await self.exec_as_agent(
- environment,
- command=(
- "set -euo pipefail; "
- 'ZED_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zed"; '
- # basedpyright (Python - default type checker)
- 'BASEDPYRIGHT_DIR="$ZED_DATA_DIR/languages/basedpyright"; '
- 'mkdir -p "$BASEDPYRIGHT_DIR"; '
- 'npm install --prefix "$BASEDPYRIGHT_DIR" --save-exact basedpyright; '
- # typescript-language-server (TypeScript/JS - default LSP)
- 'TSSERVER_DIR="$ZED_DATA_DIR/languages/typescript-language-server"; '
- 'mkdir -p "$TSSERVER_DIR"; '
- 'npm install --prefix "$TSSERVER_DIR" --save-exact typescript typescript-language-server; '
- # vtsls (VS Code TypeScript language features)
- 'VTSLS_DIR="$ZED_DATA_DIR/languages/vtsls"; '
- 'mkdir -p "$VTSLS_DIR"; '
- 'npm install --prefix "$VTSLS_DIR" --save-exact @vtsls/language-server typescript; '
- # tailwindcss-language-server
- 'TAILWIND_DIR="$ZED_DATA_DIR/languages/tailwindcss-language-server"; '
- 'mkdir -p "$TAILWIND_DIR"; '
- 'npm install --prefix "$TAILWIND_DIR" --save-exact @tailwindcss/language-server'
- ),
- )
-
- # eslint LSP (downloaded from zed-industries/vscode-eslint GitHub release,
- # then compiled — this mirrors what Zed does at runtime).
- await self.exec_as_agent(
- environment,
- command=(
- "set -euo pipefail; "
- 'ZED_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zed"; '
- 'ESLINT_DIR="$ZED_DATA_DIR/languages/eslint/vscode-eslint-2.4.4"; '
- 'mkdir -p "$ESLINT_DIR"; '
- 'curl -fsSL "https://github.com/zed-industries/vscode-eslint/archive/refs/tags/release/2.4.4.tar.gz" '
- '| tar -xz -C "$ESLINT_DIR"; '
- 'mv "$ESLINT_DIR"/vscode-eslint-release-2.4.4 "$ESLINT_DIR/vscode-eslint"; '
- 'cd "$ESLINT_DIR/vscode-eslint" && npm install && npm run compile'
- ),
- )
-
- # gopls (Go - default LSP). Only install when Go is present in the
- # container (i.e. Go-related SWE-bench tasks).
- await self.exec_as_agent(
- environment,
- command=(
- "if command -v go >/dev/null 2>&1; then "
- "go install golang.org/x/tools/gopls@latest; "
+ "if command -v apt-get >/dev/null 2>&1; then "
+ " apt-get update && "
+ " apt-get install -y --no-install-recommends ca-certificates curl git; "
+ "elif command -v apk >/dev/null 2>&1; then "
+ " apk add --no-cache ca-certificates curl git bash coreutils gcompat libstdc++; "
+ "elif command -v dnf >/dev/null 2>&1; then "
+ " dnf install -y ca-certificates curl git; "
+ "elif command -v yum >/dev/null 2>&1; then "
+ " yum install -y ca-certificates curl git; "
+ "else "
+ " echo 'WARNING: No supported package manager found (apt-get, apk, dnf, yum)' >&2; "
"fi"
),
+ env={"DEBIAN_FRONTEND": "noninteractive"},
)
- await self.exec_as_agent(
- environment,
- command=(
- "curl -LsSf https://astral.sh/uv/install.sh | sh && "
- '. "$HOME/.local/bin/env"'
- ),
- )
-
- agent_home_result = await self.exec_as_agent(
- environment,
- command='printf %s "$HOME"',
- )
- agent_home = agent_home_result.stdout.strip()
- if not agent_home:
- raise RuntimeError("Could not determine agent home directory")
-
- await self.exec_as_root(
- environment,
- command=(
- f"ln -sf {shlex.quote(agent_home + '/.local/bin/uv')} /usr/local/bin/uv && "
- f"ln -sf {shlex.quote(agent_home + '/.local/bin/uvx')} /usr/local/bin/uvx"
- ),
- )
+ # ── Non-essential tooling ─────────────────────────────────────
+ # Everything below here (Node.js, LSPs, uv/ruff) is nice-to-have.
+ # If any step fails (e.g. musl incompatibility, network issues),
+ # log a warning and continue — the agent can still work without
+ # pre-installed language servers.
- # Install a modern ruff so `ruff server` works without --preview.
- # This also makes it available as a CLI tool for the agent.
- await self.exec_as_agent(
- environment,
- command=('export PATH="$HOME/.local/bin:$PATH" && uv tool install ruff'),
- )
+ await self._install_node(environment)
+ await self._install_lsps(environment)
+ await self._install_uv_and_ruff(environment)
if self._binary_path:
binary = Path(self._binary_path)
@@ -224,6 +151,183 @@ class ZedAgent(BaseInstalledAgent):
"or set download_url=/EVAL_CLI_DOWNLOAD_URL."
)
+ async def _install_node(self, environment: BaseEnvironment) -> None:
+ """Install Node.js from official binary tarballs.
+
+ Uses the musl build on Alpine and the glibc build elsewhere.
+ Skips if node is already on PATH.
+ """
+ try:
+ await self.exec_as_root(
+ environment,
+ command=(
+ "if command -v node >/dev/null 2>&1; then "
+ ' echo "Node.js already available: $(node --version)"; '
+ "else "
+ " NODE_VER=v22.14.0; "
+ " ARCH=$(uname -m); "
+ ' case "$ARCH" in '
+ " x86_64) NODE_ARCH=x64 ;; "
+ " aarch64) NODE_ARCH=arm64 ;; "
+ ' *) echo "WARNING: unsupported arch $ARCH for Node.js" >&2; exit 0 ;; '
+ " esac; "
+ " if ldd /bin/sh 2>&1 | grep -qi musl; then "
+ ' NODE_URL="https://unofficial-builds.nodejs.org/download/release/${NODE_VER}/node-${NODE_VER}-linux-${NODE_ARCH}-musl.tar.gz"; '
+ " else "
+ ' NODE_URL="https://nodejs.org/dist/${NODE_VER}/node-${NODE_VER}-linux-${NODE_ARCH}.tar.gz"; '
+ " fi; "
+ ' echo "Downloading Node.js from $NODE_URL"; '
+ ' curl -fsSL "$NODE_URL" | tar -xz -C /usr/local --strip-components=1; '
+ ' echo "Installed Node.js $(node --version)"; '
+ "fi"
+ ),
+ )
+ except Exception as exc:
+ self.logger.warning("Node.js installation failed (non-fatal): %s", exc)
+
+ async def _install_lsps(self, environment: BaseEnvironment) -> None:
+ """Pre-install language servers so Zed doesn't download them at runtime.
+
+ Each LSP is installed independently so one failure doesn't block the rest.
+ """
+ # npm-based LSPs — skip all if npm is not available.
+ try:
+ await self.exec_as_agent(
+ environment,
+ command="command -v npm >/dev/null 2>&1",
+ )
+ except Exception:
+ self.logger.warning("npm not available — skipping npm-based LSP installs")
+ return
+
+ lsp_installs = [
+ (
+ "basedpyright",
+ 'DIR="$ZED_DATA_DIR/languages/basedpyright"; '
+ 'mkdir -p "$DIR" && npm install --prefix "$DIR" --save-exact basedpyright',
+ ),
+ (
+ "typescript-language-server",
+ 'DIR="$ZED_DATA_DIR/languages/typescript-language-server"; '
+ 'mkdir -p "$DIR" && npm install --prefix "$DIR" --save-exact typescript typescript-language-server',
+ ),
+ (
+ "vtsls",
+ 'DIR="$ZED_DATA_DIR/languages/vtsls"; '
+ 'mkdir -p "$DIR" && npm install --prefix "$DIR" --save-exact @vtsls/language-server typescript',
+ ),
+ (
+ "tailwindcss-language-server",
+ 'DIR="$ZED_DATA_DIR/languages/tailwindcss-language-server"; '
+ 'mkdir -p "$DIR" && npm install --prefix "$DIR" --save-exact @tailwindcss/language-server',
+ ),
+ ]
+
+ for name, cmd in lsp_installs:
+ try:
+ await self.exec_as_agent(
+ environment,
+ command=(
+ 'ZED_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zed"; '
+ + cmd
+ ),
+ )
+ except Exception as exc:
+ self.logger.warning(
+ "LSP install '%s' failed (non-fatal): %s", name, exc
+ )
+
+ # eslint — downloaded from GitHub and compiled separately.
+ try:
+ await self.exec_as_agent(
+ environment,
+ command=(
+ "set -euo pipefail; "
+ 'ZED_DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/zed"; '
+ 'ESLINT_DIR="$ZED_DATA_DIR/languages/eslint/vscode-eslint-2.4.4"; '
+ 'mkdir -p "$ESLINT_DIR"; '
+ 'curl -fsSL "https://github.com/zed-industries/vscode-eslint/archive/refs/tags/release/2.4.4.tar.gz" '
+ '| tar -xz -C "$ESLINT_DIR"; '
+ 'mv "$ESLINT_DIR"/vscode-eslint-release-2.4.4 "$ESLINT_DIR/vscode-eslint"; '
+ 'cd "$ESLINT_DIR/vscode-eslint" && npm install && npm run compile'
+ ),
+ )
+ except Exception as exc:
+ self.logger.warning("eslint LSP install failed (non-fatal): %s", exc)
+
+ # gopls — only when Go is present. Guarded by a 120s timeout so slow
+ # compilation can never eat the full setup budget.
+ gopls_script = (
+ "if command -v go >/dev/null 2>&1; then "
+ "if go install golang.org/x/tools/gopls@latest 2>/dev/null; then "
+ "echo 'Installed gopls@latest'; "
+ "else "
+ ' MY_GO=$(go env GOVERSION | sed "s/^go//"); '
+ " for v in $(curl -fsSL "
+ "https://proxy.golang.org/golang.org/x/tools/gopls/@v/list 2>/dev/null"
+ " | grep -E '^v[0-9]+\\.[0-9]+\\.[0-9]+$' | sort -rV | head -5); do "
+ " NEED=$(curl -fsSL "
+ '"https://proxy.golang.org/golang.org/x/tools/gopls/@v/${v}.mod"'
+ " 2>/dev/null | awk '/^go /{print $2; exit}'); "
+ ' if [ -n "$NEED" ] '
+ ' && [ "$(printf \'%s\\n%s\\n\' "$NEED" "$MY_GO" '
+ ' | sort -V | head -1)" = "$NEED" ]; then '
+ ' echo "Installing gopls $v (compatible with Go $MY_GO)"; '
+ ' go install "golang.org/x/tools/gopls@$v" && break; '
+ " fi; "
+ " done; "
+ "fi; "
+ "fi"
+ )
+ try:
+ await self.exec_as_agent(
+ environment,
+ command=(
+ "timeout 120 bash -c "
+ + shlex.quote(gopls_script)
+ + " || echo 'WARNING: gopls installation timed out or failed -- skipping'"
+ ),
+ )
+ except Exception as exc:
+ self.logger.warning("gopls install failed (non-fatal): %s", exc)
+
+ async def _install_uv_and_ruff(self, environment: BaseEnvironment) -> None:
+ """Install uv and ruff for Python tooling."""
+ try:
+ await self.exec_as_agent(
+ environment,
+ command=(
+ "curl -LsSf https://astral.sh/uv/install.sh | sh && "
+ '. "$HOME/.local/bin/env"'
+ ),
+ )
+
+ agent_home_result = await self.exec_as_agent(
+ environment,
+ command='printf %s "$HOME"',
+ )
+ agent_home = agent_home_result.stdout.strip()
+ if not agent_home:
+ self.logger.warning(
+ "Could not determine agent home directory — skipping uv symlinks"
+ )
+ return
+
+ await self.exec_as_root(
+ environment,
+ command=(
+ f"ln -sf {shlex.quote(agent_home + '/.local/bin/uv')} /usr/local/bin/uv && "
+ f"ln -sf {shlex.quote(agent_home + '/.local/bin/uvx')} /usr/local/bin/uvx"
+ ),
+ )
+
+ await self.exec_as_agent(
+ environment,
+ command='export PATH="$HOME/.local/bin:$PATH" && uv tool install ruff',
+ )
+ except Exception as exc:
+ self.logger.warning("uv/ruff installation failed (non-fatal): %s", exc)
+
def populate_context_post_run(self, context: AgentContext) -> None:
result_data = None
for json_file in self.logs_dir.rglob("result.json"):
@@ -315,7 +419,9 @@ class ZedAgent(BaseInstalledAgent):
await self.exec_as_agent(
environment,
command=(
- " ".join(parts) + " 2>&1 | stdbuf -oL tee /logs/agent/eval-cli.txt"
+ " ".join(parts) + " 2>&1 | if command -v stdbuf >/dev/null 2>&1;"
+ " then stdbuf -oL tee /logs/agent/eval-cli.txt;"
+ " else tee /logs/agent/eval-cli.txt; fi"
),
env=env,
)