diff --git a/ci/platform-matrix.json b/ci/platform-matrix.json index 7ad3b65a4a0..9f9c2ffcd16 100644 --- a/ci/platform-matrix.json +++ b/ci/platform-matrix.json @@ -47,7 +47,7 @@ "status": "tested", "prd_priority": "P1", "ci_tested": true, - "notes": "Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local Ollama inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw)." + "notes": "Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw)." }, { "name": "Windows WSL2", diff --git a/ci/test-file-size-budget.json b/ci/test-file-size-budget.json index c3c785f74a4..5056f6c38f9 100644 --- a/ci/test-file-size-budget.json +++ b/ci/test-file-size-budget.json @@ -7,7 +7,7 @@ "src/lib/onboard/preflight.test.ts": 1905, "test/channels-add-preset.test.ts": 1871, "test/generate-openclaw-config.test.ts": 1984, - "test/install-preflight.test.ts": 4207, + "test/install-preflight.test.ts": 4006, "test/nemoclaw-start.test.ts": 5043, "test/onboard-messaging.test.ts": 2062, "test/onboard-selection.test.ts": 6888, diff --git a/docs/get-started/prerequisites.mdx b/docs/get-started/prerequisites.mdx index 3d9f9c97792..f26536ecb12 100644 --- a/docs/get-started/prerequisites.mdx +++ b/docs/get-started/prerequisites.mdx @@ -78,7 +78,7 @@ The table comes from [`ci/platform-matrix.json`](https://github.com/NVIDIA/NemoC |----|-------------------|--------|-------| | Linux | Docker | Tested | Primary tested path. Ubuntu 24.04 is the validated distro in production source (`DEFAULT_COMPAT_IMAGE` in `src/lib/onboard/docker-driver-gateway-launch.ts:10` and the preflight tests pin 24.04 only); the installer's package-manager probes assume apt-get. Other distros (Ubuntu 22.04, Fedora, Rocky, Alma, NixOS, Arch) may work but are not validated. | | macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | Start the container runtime (Colima or Docker Desktop) before running the installer. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. | -| DGX Spark | Docker | Tested | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local Ollama inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). | +| DGX Spark | Docker | Tested | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). | | Windows WSL2 | Docker Desktop (WSL backend) | Tested with limitations | Requires WSL2 with Docker Desktop backend. | {/* platform-matrix:end */} diff --git a/docs/get-started/quickstart.mdx b/docs/get-started/quickstart.mdx index d9f20248fb8..c2dab865895 100644 --- a/docs/get-started/quickstart.mdx +++ b/docs/get-started/quickstart.mdx @@ -75,6 +75,7 @@ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash On DGX Spark, DGX Station, and Windows WSL, an interactive installer offers express install after you accept the third-party software notice. Express install switches onboarding to non-interactive mode, allows `sudo` password prompts for required host changes, and selects the managed local inference path for that platform. +On DGX Spark and DGX Station, express install selects managed vLLM; DGX Spark uses `qwen3.6-35b-a3b-nvfp4` by default. Unless `NEMOCLAW_POLICY_TIER` is set, it applies sandbox policy in `suggested` mode with the `balanced` tier by default, using the base sandbox policy plus supported package, model, web-search, local-inference, and read-only weather presets. On DGX Spark, express install uses `my-spark-assistant` as the sandbox name unless `NEMOCLAW_SANDBOX_NAME` is already set. On WSL, express install selects the Windows-host Ollama setup path. diff --git a/docs/reference/platform-support.mdx b/docs/reference/platform-support.mdx index 16fabdc1ea5..ad66a3a2a74 100644 --- a/docs/reference/platform-support.mdx +++ b/docs/reference/platform-support.mdx @@ -79,7 +79,7 @@ For the onboarding-time supported set without deferred rows, see [Prerequisites] |----|-------------------|--------|--------------|----|-------| | Linux | Docker | Tested | P0 | Yes | Primary tested path. Ubuntu 24.04 is the validated distro in production source (`DEFAULT_COMPAT_IMAGE` in `src/lib/onboard/docker-driver-gateway-launch.ts:10` and the preflight tests pin 24.04 only); the installer's package-manager probes assume apt-get. Other distros (Ubuntu 22.04, Fedora, Rocky, Alma, NixOS, Arch) may work but are not validated. | | macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | P0 | Yes | Start the container runtime (Colima or Docker Desktop) before running the installer. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. | -| DGX Spark | Docker | Tested | P1 | Yes | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local Ollama inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). | +| DGX Spark | Docker | Tested | P1 | Yes | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). | | Windows WSL2 | Docker Desktop (WSL backend) | Tested with limitations | P1 | No | Requires WSL2 with Docker Desktop backend. | | DGX Station | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Onboard path not yet validated end-to-end on the hardware; vLLM has a placeholder default model defined for this host class (`Qwen/Qwen3.6-27B-FP8`) that will move out of `deferred` once the hardware run is signed off. | | NVIDIA RTX (consumer and Pro workstation GPUs) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Covers RTX consumer cards and RTX Pro workstation cards on Linux hosts that meet the generic-Linux-GPU requirements (NVIDIA Container Toolkit + CDI present). The provider menu emits managed vLLM behind `NEMOCLAW_EXPERIMENTAL=1` or `NEMOCLAW_PROVIDER=install-vllm` for this host class today; the end-to-end onboard path on this hardware is not yet validated in CI. | diff --git a/docs/reference/troubleshooting.mdx b/docs/reference/troubleshooting.mdx index 6ceac31cc0d..a892e8803af 100644 --- a/docs/reference/troubleshooting.mdx +++ b/docs/reference/troubleshooting.mdx @@ -1332,7 +1332,7 @@ Use `--follow` to stream logs in real time while debugging. ## DGX Spark -For an end-to-end Ollama walkthrough on DGX Spark, refer to the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). +For an end-to-end walkthrough with local inference on DGX Spark, refer to the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). ### CoreDNS CrashLoop after onboarding diff --git a/scripts/install.sh b/scripts/install.sh index 7b75655197d..5ef720a9ca0 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -2429,17 +2429,24 @@ detect_express_platform() { describe_express_install() { local platform="$1" local inference_summary="" + local inference_disclosure="" local sandbox_summary="" local tier="${NEMOCLAW_POLICY_TIER:-balanced}" local policy_summary="" case "$platform" in "DGX Spark") - inference_summary="managed local Ollama with model qwen3.6:35b" + if [ -n "${NEMOCLAW_VLLM_MODEL:-}" ]; then + inference_summary="managed local vLLM with model ${NEMOCLAW_VLLM_MODEL}" + else + inference_summary="managed local vLLM using the DGX Spark profile default model" + fi + inference_disclosure="Managed vLLM pulls the configured vLLM image/model and runs a local vLLM inference container." sandbox_summary="${NEMOCLAW_SANDBOX_NAME:-my-spark-assistant}" ;; "DGX Station") inference_summary="managed local vLLM" + inference_disclosure="Managed vLLM pulls the configured vLLM image/model and runs a local vLLM inference container." sandbox_summary="${NEMOCLAW_SANDBOX_NAME:-my-assistant}" ;; "Windows WSL") @@ -2471,6 +2478,9 @@ describe_express_install() { esac printf " Express install will configure %s.\n" "$inference_summary" + if [ -n "$inference_disclosure" ]; then + printf " %s\n" "$inference_disclosure" + fi printf " Sandbox name: %s.\n" "$sandbox_summary" printf " It runs onboarding non-interactively, but still prompts for sudo when host setup needs it.\n" printf " Sandbox policy: suggested mode, tier '%s'. This uses the %s.\n" "$tier" "$policy_summary" @@ -2532,8 +2542,10 @@ maybe_offer_express_install() { case "$platform" in "DGX Spark") export NEMOCLAW_SANDBOX_NAME="${NEMOCLAW_SANDBOX_NAME:-my-spark-assistant}" - export NEMOCLAW_PROVIDER=install-ollama - export NEMOCLAW_MODEL=qwen3.6:35b + export NEMOCLAW_PROVIDER=install-vllm + if [ -n "${NEMOCLAW_VLLM_MODEL:-}" ]; then + export NEMOCLAW_VLLM_MODEL + fi ;; "DGX Station") export NEMOCLAW_PROVIDER=install-vllm diff --git a/test/e2e-scenario/support-tests/e2e-live-project-config.test.ts b/test/e2e-scenario/support-tests/e2e-live-project-config.test.ts index 0c9dc5acd8b..49050baeba0 100644 --- a/test/e2e-scenario/support-tests/e2e-live-project-config.test.ts +++ b/test/e2e-scenario/support-tests/e2e-live-project-config.test.ts @@ -27,6 +27,7 @@ interface RootConfig { } const INSTALLER_INTEGRATION_TESTS = [ + "test/install-express-prompt.test.ts", "test/install-preflight.test.ts", "test/install-preflight-docker-bootstrap.test.ts", "test/install-openshell-version-check.test.ts", diff --git a/test/install-express-prompt.test.ts b/test/install-express-prompt.test.ts new file mode 100644 index 00000000000..1606ddc5507 --- /dev/null +++ b/test/install-express-prompt.test.ts @@ -0,0 +1,233 @@ +// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { INSTALLER_PAYLOAD, TEST_SYSTEM_PATH } from "./helpers/installer-sourced-env"; + +describe("installer express install prompt (sourced)", () => { + function runExpressPromptWithTty( + answer: string, + stdinMode: "pipe" | "tty", + platform = "DGX Spark", + extraEnv: Record = {}, + ) { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-prompt-")); + const python = + spawnSync("bash", ["--noprofile", "--norc", "-c", "command -v python3"], { + encoding: "utf-8", + }).stdout.trim() || "python3"; + const ptyRunner = ` +import os +import pty +import select +import signal +import sys +import time + +installer = sys.argv[1] +answer = sys.argv[2].encode() +stdin_mode = sys.argv[3] +platform = sys.argv[4] +script = r''' +source "$INSTALLER_UNDER_TEST" >/dev/null +detect_express_platform() { printf "$EXPRESS_PLATFORM"; } +NON_INTERACTIVE="" +NEMOCLAW_PROVIDER="" +NEMOCLAW_NO_EXPRESS="" +maybe_offer_express_install +printf "RESULT NON_INTERACTIVE=%s SUDO_MODE=%s PROVIDER=%s MODEL=%s VLLM_MODEL=%s POLICY=%s YES=%s SANDBOX=%s\\n" \\ + "\${NON_INTERACTIVE:-}" "\${NEMOCLAW_NON_INTERACTIVE_SUDO_MODE:-}" "\${NEMOCLAW_PROVIDER:-}" "\${NEMOCLAW_MODEL:-}" \\ + "\${NEMOCLAW_VLLM_MODEL:-}" "\${NEMOCLAW_POLICY_MODE:-}" "\${NEMOCLAW_YES:-}" "\${NEMOCLAW_SANDBOX_NAME:-}" +''' +env = dict(os.environ) +env["INSTALLER_UNDER_TEST"] = installer +env["EXPRESS_PLATFORM"] = platform +pid, fd = pty.fork() +if pid == 0: + if stdin_mode == "pipe": + devnull = os.open(os.devnull, os.O_RDONLY) + os.dup2(devnull, 0) + os.close(devnull) + os.execvpe("bash", ["bash", "-c", script], env) + +output = bytearray() +os.set_blocking(fd, False) +sent = False +exit_code = 124 +deadline = time.time() + 10 +while True: + ready, _, _ = select.select([fd], [], [], 0.1) + if ready: + try: + chunk = os.read(fd, 4096) + except BlockingIOError: + chunk = b"" + except OSError: + chunk = b"" + if chunk: + output.extend(chunk) + if (not sent) and b"[Y/n]" in output: + os.write(fd, answer) + sent = True + waited = os.waitpid(pid, os.WNOHANG) + if waited[0] == pid: + exit_code = os.waitstatus_to_exitcode(waited[1]) + break + if time.time() > deadline: + try: + os.kill(pid, signal.SIGKILL) + except ProcessLookupError: + pass + try: + os.waitpid(pid, 0) + except ChildProcessError: + pass + break + +try: + os.close(fd) +except OSError: + pass +sys.stdout.buffer.write(output) +sys.exit(exit_code) +`; + return spawnSync(python, ["-c", ptyRunner, INSTALLER_PAYLOAD, answer, stdinMode, platform], { + cwd: tmp, + encoding: "utf-8", + timeout: 15_000, + killSignal: "SIGKILL", + env: { + HOME: tmp, + PATH: TEST_SYSTEM_PATH, + ...extraEnv, + }, + }); + } + + it("offers express install when curl-piped stdin still has a controlling TTY", () => { + const result = runExpressPromptWithTty("y\n", "pipe"); + const output = `${result.stdout}${result.stderr}`; + expect(result.status, output).toBe(0); + expect(output).toMatch(/Detected DGX Spark/); + expect(output).toMatch( + /Express install will configure managed local vLLM using the DGX Spark profile default model/, + ); + expect(output).toMatch( + /Managed vLLM pulls the configured vLLM image\/model and runs a local vLLM inference container/, + ); + expect(output).toMatch(/Sandbox name: my-spark-assistant/); + expect(output).toMatch(/Sandbox policy: suggested mode, tier 'balanced'/); + expect(output).toMatch(/Run express install/); + expect(output).toMatch(/Using express install for DGX Spark/); + expect(output).toMatch( + /RESULT NON_INTERACTIVE=1 SUDO_MODE=prompt PROVIDER=install-vllm MODEL= VLLM_MODEL= POLICY=suggested YES=1 SANDBOX=my-spark-assistant/, + ); + }); + + it("preserves a preset Spark vLLM model in the prompt and exported env", () => { + const result = runExpressPromptWithTty("y\n", "pipe", "DGX Spark", { + NEMOCLAW_VLLM_MODEL: "custom-qwen3.6", + }); + const output = `${result.stdout}${result.stderr}`; + expect(result.status, output).toBe(0); + expect(output).toMatch(/Detected DGX Spark/); + expect(output).toMatch( + /Express install will configure managed local vLLM with model custom-qwen3\.6/, + ); + expect(output).toMatch( + /Managed vLLM pulls the configured vLLM image\/model and runs a local vLLM inference container/, + ); + expect(output).toMatch( + /RESULT NON_INTERACTIVE=1 SUDO_MODE=prompt PROVIDER=install-vllm MODEL= VLLM_MODEL=custom-qwen3\.6 POLICY=suggested YES=1 SANDBOX=my-spark-assistant/, + ); + }); + + it("detects Windows WSL as an express install platform", () => { + const result = spawnSync( + "bash", + [ + "-c", + ` +source "$INSTALLER_UNDER_TEST" >/dev/null +detect_express_platform +`, + ], + { + cwd: path.join(import.meta.dirname, ".."), + encoding: "utf-8", + env: { + HOME: fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-wsl-detect-")), + PATH: TEST_SYSTEM_PATH, + INSTALLER_UNDER_TEST: INSTALLER_PAYLOAD, + WSL_DISTRO_NAME: "Ubuntu", + }, + }, + ); + + expect(result.status, `${result.stdout}${result.stderr}`).toBe(0); + expect(result.stdout).toBe("Windows WSL"); + }); + + it("maps Windows WSL express install to Windows-host Ollama", () => { + const result = runExpressPromptWithTty("\n", "pipe", "Windows WSL"); + const output = `${result.stdout}${result.stderr}`; + expect(result.status, output).toBe(0); + expect(output).toMatch(/Detected Windows WSL/); + expect(output).toMatch( + /Express install will configure Windows-host Ollama through host\.docker\.internal/, + ); + expect(output).toMatch(/Sandbox policy: suggested mode, tier 'balanced'/); + expect(output).toMatch(/Run express install/); + expect(output).toMatch(/Using express install for Windows WSL/); + expect(output).toMatch( + /RESULT NON_INTERACTIVE=1 SUDO_MODE=prompt PROVIDER=install-windows-ollama MODEL= VLLM_MODEL= POLICY=suggested YES=1 SANDBOX=/, + ); + }); + + it.skipIf(process.platform === "darwin")( + "skips express install without a controlling TTY", + () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-no-tty-")); + const result = spawnSync( + "setsid", + [ + "bash", + "-c", + ` +source "$INSTALLER_UNDER_TEST" >/dev/null +detect_express_platform() { printf "DGX Spark"; } +NON_INTERACTIVE="" +NEMOCLAW_PROVIDER="" +NEMOCLAW_NO_EXPRESS="" +maybe_offer_express_install +printf "RESULT NON_INTERACTIVE=%s SUDO_MODE=%s PROVIDER=%s MODEL=%s VLLM_MODEL=%s POLICY=%s YES=%s SANDBOX=%s\\n" \\ + "\${NON_INTERACTIVE:-}" "\${NEMOCLAW_NON_INTERACTIVE_SUDO_MODE:-}" "\${NEMOCLAW_PROVIDER:-}" "\${NEMOCLAW_MODEL:-}" \\ + "\${NEMOCLAW_VLLM_MODEL:-}" "\${NEMOCLAW_POLICY_MODE:-}" "\${NEMOCLAW_YES:-}" "\${NEMOCLAW_SANDBOX_NAME:-}" +`, + ], + { + cwd: tmp, + encoding: "utf-8", + input: "", + env: { + HOME: tmp, + PATH: TEST_SYSTEM_PATH, + INSTALLER_UNDER_TEST: INSTALLER_PAYLOAD, + }, + }, + ); + const output = `${result.stdout}${result.stderr}`; + expect(result.status, output).toBe(0); + expect(output).toMatch(/Detected DGX Spark/); + expect(output).toMatch(/Skipping express prompt \(no TTY\)/); + expect(output).not.toMatch(/Run express install/); + expect(output).toMatch( + /RESULT NON_INTERACTIVE= SUDO_MODE= PROVIDER= MODEL= VLLM_MODEL= POLICY= YES= SANDBOX=/, + ); + }, + ); +}); diff --git a/test/install-preflight.test.ts b/test/install-preflight.test.ts index ab15bac406f..a2504274080 100644 --- a/test/install-preflight.test.ts +++ b/test/install-preflight.test.ts @@ -3174,207 +3174,6 @@ exit 0`, }); }); -describe("installer express install prompt (sourced)", () => { - function runExpressPromptWithTty( - answer: string, - stdinMode: "pipe" | "tty", - platform = "DGX Spark", - ) { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-prompt-")); - const python = - spawnSync("bash", ["--noprofile", "--norc", "-c", "command -v python3"], { - encoding: "utf-8", - }).stdout.trim() || "python3"; - const ptyRunner = ` -import os -import pty -import select -import signal -import sys -import time - -installer = sys.argv[1] -answer = sys.argv[2].encode() -stdin_mode = sys.argv[3] -platform = sys.argv[4] -script = r''' -source "$INSTALLER_UNDER_TEST" >/dev/null -detect_express_platform() { printf "$EXPRESS_PLATFORM"; } -NON_INTERACTIVE="" -NEMOCLAW_PROVIDER="" -NEMOCLAW_NO_EXPRESS="" -maybe_offer_express_install -printf "RESULT NON_INTERACTIVE=%s SUDO_MODE=%s PROVIDER=%s MODEL=%s POLICY=%s YES=%s SANDBOX=%s\\n" \\ - "\${NON_INTERACTIVE:-}" "\${NEMOCLAW_NON_INTERACTIVE_SUDO_MODE:-}" "\${NEMOCLAW_PROVIDER:-}" "\${NEMOCLAW_MODEL:-}" \\ - "\${NEMOCLAW_POLICY_MODE:-}" "\${NEMOCLAW_YES:-}" "\${NEMOCLAW_SANDBOX_NAME:-}" -''' -env = dict(os.environ) -env["INSTALLER_UNDER_TEST"] = installer -env["EXPRESS_PLATFORM"] = platform -pid, fd = pty.fork() -if pid == 0: - if stdin_mode == "pipe": - devnull = os.open(os.devnull, os.O_RDONLY) - os.dup2(devnull, 0) - os.close(devnull) - os.execvpe("bash", ["bash", "-c", script], env) - -output = bytearray() -os.set_blocking(fd, False) -sent = False -exit_code = 124 -deadline = time.time() + 10 -while True: - ready, _, _ = select.select([fd], [], [], 0.1) - if ready: - try: - chunk = os.read(fd, 4096) - except BlockingIOError: - chunk = b"" - except OSError: - chunk = b"" - if chunk: - output.extend(chunk) - if (not sent) and b"[Y/n]" in output: - os.write(fd, answer) - sent = True - waited = os.waitpid(pid, os.WNOHANG) - if waited[0] == pid: - exit_code = os.waitstatus_to_exitcode(waited[1]) - break - if time.time() > deadline: - try: - os.kill(pid, signal.SIGKILL) - except ProcessLookupError: - pass - try: - os.waitpid(pid, 0) - except ChildProcessError: - pass - break - -try: - os.close(fd) -except OSError: - pass -sys.stdout.buffer.write(output) -sys.exit(exit_code) -`; - return spawnSync(python, ["-c", ptyRunner, INSTALLER_PAYLOAD, answer, stdinMode, platform], { - cwd: tmp, - encoding: "utf-8", - timeout: 15_000, - killSignal: "SIGKILL", - env: { - HOME: tmp, - PATH: TEST_SYSTEM_PATH, - }, - }); - } - - it("offers express install when curl-piped stdin still has a controlling TTY", () => { - const result = runExpressPromptWithTty("y\n", "pipe"); - const output = `${result.stdout}${result.stderr}`; - expect(result.status, output).toBe(0); - expect(output).toMatch(/Detected DGX Spark/); - expect(output).toMatch( - /Express install will configure managed local Ollama with model qwen3\.6:35b/, - ); - expect(output).toMatch(/Sandbox name: my-spark-assistant/); - expect(output).toMatch(/Sandbox policy: suggested mode, tier 'balanced'/); - expect(output).toMatch(/Run express install/); - expect(output).toMatch(/Using express install for DGX Spark/); - expect(output).toMatch( - /RESULT NON_INTERACTIVE=1 SUDO_MODE=prompt PROVIDER=install-ollama MODEL=qwen3\.6:35b POLICY=suggested YES=1 SANDBOX=my-spark-assistant/, - ); - }); - - it("detects Windows WSL as an express install platform", () => { - const result = spawnSync( - "bash", - [ - "-c", - ` -source "$INSTALLER_UNDER_TEST" >/dev/null -detect_express_platform -`, - ], - { - cwd: path.join(import.meta.dirname, ".."), - encoding: "utf-8", - env: { - HOME: fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-wsl-detect-")), - PATH: TEST_SYSTEM_PATH, - INSTALLER_UNDER_TEST: INSTALLER_PAYLOAD, - WSL_DISTRO_NAME: "Ubuntu", - }, - }, - ); - - expect(result.status, `${result.stdout}${result.stderr}`).toBe(0); - expect(result.stdout).toBe("Windows WSL"); - }); - - it("maps Windows WSL express install to Windows-host Ollama", () => { - const result = runExpressPromptWithTty("\n", "pipe", "Windows WSL"); - const output = `${result.stdout}${result.stderr}`; - expect(result.status, output).toBe(0); - expect(output).toMatch(/Detected Windows WSL/); - expect(output).toMatch( - /Express install will configure Windows-host Ollama through host\.docker\.internal/, - ); - expect(output).toMatch(/Sandbox policy: suggested mode, tier 'balanced'/); - expect(output).toMatch(/Run express install/); - expect(output).toMatch(/Using express install for Windows WSL/); - expect(output).toMatch( - /RESULT NON_INTERACTIVE=1 SUDO_MODE=prompt PROVIDER=install-windows-ollama MODEL= POLICY=suggested YES=1 SANDBOX=/, - ); - }); - - it("skips express install without a controlling TTY", () => { - if (process.platform === "darwin") { - return; - } - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-express-no-tty-")); - const result = spawnSync( - "setsid", - [ - "bash", - "-c", - ` -source "$INSTALLER_UNDER_TEST" >/dev/null -detect_express_platform() { printf "DGX Spark"; } -NON_INTERACTIVE="" -NEMOCLAW_PROVIDER="" -NEMOCLAW_NO_EXPRESS="" -maybe_offer_express_install -printf "RESULT NON_INTERACTIVE=%s SUDO_MODE=%s PROVIDER=%s MODEL=%s POLICY=%s YES=%s SANDBOX=%s\\n" \\ - "\${NON_INTERACTIVE:-}" "\${NEMOCLAW_NON_INTERACTIVE_SUDO_MODE:-}" "\${NEMOCLAW_PROVIDER:-}" "\${NEMOCLAW_MODEL:-}" \\ - "\${NEMOCLAW_POLICY_MODE:-}" "\${NEMOCLAW_YES:-}" "\${NEMOCLAW_SANDBOX_NAME:-}" -`, - ], - { - cwd: tmp, - encoding: "utf-8", - input: "", - env: { - HOME: tmp, - PATH: TEST_SYSTEM_PATH, - INSTALLER_UNDER_TEST: INSTALLER_PAYLOAD, - }, - }, - ); - const output = `${result.stdout}${result.stderr}`; - expect(result.status, output).toBe(0); - expect(output).toMatch(/Detected DGX Spark/); - expect(output).toMatch(/Skipping express prompt \(no TTY\)/); - expect(output).not.toMatch(/Run express install/); - expect(output).toMatch( - /RESULT NON_INTERACTIVE= SUDO_MODE= PROVIDER= MODEL= POLICY= YES= SANDBOX=/, - ); - }); -}); - // --------------------------------------------------------------------------- // scripts/install.sh (curl-pipe installer) release-tag resolution // --------------------------------------------------------------------------- diff --git a/test/pr-workflow-contract.test.ts b/test/pr-workflow-contract.test.ts index 12327c81228..ab05afac934 100644 --- a/test/pr-workflow-contract.test.ts +++ b/test/pr-workflow-contract.test.ts @@ -459,6 +459,7 @@ describe("pull request and main workflow contracts", () => { expect(vitestConfig).toContain('name: "e2e-vitest-support"'); expect(vitestConfig).toContain('include: ["test/**/*.test.{js,ts}", "src/**/*.test.ts"]'); expect(vitestConfig).toContain('"test/e2e/**"'); + expect(vitestConfig).toContain('"test/install-express-prompt.test.ts"'); expect(vitestConfig).toContain('"test/install-preflight.test.ts"'); expect(vitestConfig).toContain('"test/install-openshell-version-check.test.ts"'); }); diff --git a/vitest.config.ts b/vitest.config.ts index e463c496fd7..6f6b819ad2f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -43,6 +43,7 @@ export default defineConfig({ // and require Docker + a real onboard to pass. Excluding here keeps // the cli project (and pre-commit `Test (cli)`) green locally. "test/e2e-scenario/live/**", + "test/install-express-prompt.test.ts", "test/install-preflight.test.ts", "test/install-preflight-docker-bootstrap.test.ts", "test/install-openshell-version-check.test.ts", @@ -54,6 +55,7 @@ export default defineConfig({ name: "installer-integration", include: runInstallerIntegration ? [ + "test/install-express-prompt.test.ts", "test/install-preflight.test.ts", "test/install-preflight-docker-bootstrap.test.ts", "test/install-openshell-version-check.test.ts",