From c7917a30d63799ab23b5af8d1271a057c09ce079 Mon Sep 17 00:00:00 2001 From: 13ernkastel Date: Thu, 2 Apr 2026 02:04:30 +0800 Subject: [PATCH 1/5] docs: clarify OpenShell compatibility guidance --- README.md | 9 +++++++++ bin/lib/onboard.js | 12 ++++++++++++ docs/about/how-it-works.md | 6 +++--- docs/get-started/quickstart.md | 9 +++++++++ docs/reference/commands.md | 5 +++++ test/onboard.test.js | 12 ++++++++++++ 6 files changed, 50 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fedb3b31f..0723b8495 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,15 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc | Container runtime | Supported runtime installed and running | | [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed | +#### OpenShell Compatibility + +NemoClaw 0.1.0 was validated with OpenShell 0.0.7. +During onboarding, NemoClaw derives the OpenShell gateway image from the installed `openshell` CLI version, so upgrading OpenShell independently can rebuild the sandbox with a different runtime layout than the current NemoClaw release expects. + +| NemoClaw | OpenShell | Notes | +|----------|-----------|-------| +| 0.1.0 | 0.0.7 | Use `nemoclaw onboard` to create or recreate NemoClaw-managed sandboxes. Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then re-onboard. | + #### Container Runtimes | Platform | Supported runtimes | Notes | diff --git a/bin/lib/onboard.js b/bin/lib/onboard.js index e6e2c0925..e61a01abf 100644 --- a/bin/lib/onboard.js +++ b/bin/lib/onboard.js @@ -519,6 +519,14 @@ function getStableGatewayImageRef(versionOutput = null) { return `ghcr.io/nvidia/openshell/cluster:${version}`; } +function getOpenshellCompatibilityNotice(version = null) { + const suffix = version ? ` (${version})` : ""; + return [ + ` OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI${suffix}.`, + " Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.", + ]; +} + function getOpenshellBinary() { if (OPENSHELL_BIN) return OPENSHELL_BIN; const resolved = resolveOpenshell(); @@ -2148,6 +2156,9 @@ async function startGatewayWithOptions(_gpu, { exitOnFailure = true } = {}) { if (gatewayEnv.OPENSHELL_CLUSTER_IMAGE) { console.log(` Using pinned OpenShell gateway image: ${gatewayEnv.OPENSHELL_CLUSTER_IMAGE}`); } + for (const line of getOpenshellCompatibilityNotice(gatewayEnv.IMAGE_TAG || null)) { + console.log(line); + } // Retry gateway start with exponential backoff. On some hosts (Horde VMs, // first-run environments) the embedded k3s needs more time than OpenShell's @@ -4006,6 +4017,7 @@ module.exports = { getGatewayStartEnv, getGatewayReuseState, getSandboxInferenceConfig, + getOpenshellCompatibilityNotice, getInstalledOpenshellVersion, getRequestedModelHint, getRequestedProviderHint, diff --git a/docs/about/how-it-works.md b/docs/about/how-it-works.md index dabcb0f3a..9cb14edfa 100644 --- a/docs/about/how-it-works.md +++ b/docs/about/how-it-works.md @@ -91,9 +91,9 @@ Respect CLI boundaries Supply chain safety : Blueprint artifacts are immutable, versioned, and digest-verified before execution. -OpenShell-native for new installs -: For users without an existing OpenClaw installation, NemoClaw recommends `openshell sandbox create` directly - rather than forcing a plugin-driven bootstrap. +OpenShell-backed lifecycle +: NemoClaw orchestrates OpenShell resources under the hood, but `nemoclaw onboard` + is the supported operator entry point for creating or recreating NemoClaw-managed sandboxes. Reproducible setup : Running setup again recreates the sandbox from the same blueprint and policy definitions. diff --git a/docs/get-started/quickstart.md b/docs/get-started/quickstart.md index 7a5c765b8..94ca5e331 100644 --- a/docs/get-started/quickstart.md +++ b/docs/get-started/quickstart.md @@ -53,6 +53,15 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc | Container runtime | Supported runtime installed and running | | [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed | +:::{warning} OpenShell compatibility +NemoClaw 0.1.0 was validated with OpenShell 0.0.7. +During onboarding, NemoClaw derives the OpenShell gateway image from the installed `openshell` CLI version, so upgrading OpenShell independently can rebuild the sandbox with a different runtime layout than the current NemoClaw release expects. +::: + +| NemoClaw | OpenShell | Notes | +|----------|-----------|-------| +| 0.1.0 | 0.0.7 | Use `nemoclaw onboard` to create or recreate NemoClaw-managed sandboxes. Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then re-onboard. | + ### Container Runtimes | Platform | Supported runtimes | Notes | diff --git a/docs/reference/commands.md b/docs/reference/commands.md index f83f7796f..c7d1211e1 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -64,6 +64,11 @@ Use this command for new installs and for recreating a sandbox after changes to $ nemoclaw onboard ``` +:::{warning} +For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox. +NemoClaw derives the gateway image from the installed `openshell` CLI version, so `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` can replace the runtime with a different OpenShell release than NemoClaw 0.1.0 was validated against. +::: + The wizard prompts for a provider first, then collects the provider credential if needed. Supported non-experimental choices include NVIDIA Endpoints, OpenAI, Anthropic, Google Gemini, and compatible OpenAI or Anthropic endpoints. Credentials are stored in `~/.nemoclaw/credentials.json`. diff --git a/test/onboard.test.js b/test/onboard.test.js index 267696119..2d1c84eee 100644 --- a/test/onboard.test.js +++ b/test/onboard.test.js @@ -14,6 +14,7 @@ import { getGatewayReuseState, getPortConflictServiceHints, getFutureShellPathHint, + getOpenshellCompatibilityNotice, getSandboxInferenceConfig, getInstalledOpenshellVersion, getRequestedModelHint, @@ -254,6 +255,17 @@ describe("onboard helpers", () => { expect(getStableGatewayImageRef("bogus")).toBe(null); }); + it("prints a compatibility note when the gateway version is pinned", () => { + expect(getOpenshellCompatibilityNotice("0.0.7")).toEqual([ + " OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI (0.0.7).", + " Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.", + ]); + expect(getOpenshellCompatibilityNotice()).toEqual([ + " OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI.", + " Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.", + ]); + }); + it("treats the gateway as healthy only when nemoclaw is running and connected", () => { expect( isGatewayHealthy( From 1f1ae97035d005192ecb9dd4c350ce0dfd2e060b Mon Sep 17 00:00:00 2001 From: 13ernkastel Date: Thu, 2 Apr 2026 09:26:38 +0800 Subject: [PATCH 2/5] fix: detect incompatible openshell versions --- bin/lib/onboard.js | 58 +++++++++++++++++++++++++++++++++++++++++--- test/onboard.test.js | 22 +++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/bin/lib/onboard.js b/bin/lib/onboard.js index 1d1f788ec..f55504630 100644 --- a/bin/lib/onboard.js +++ b/bin/lib/onboard.js @@ -10,6 +10,7 @@ const os = require("os"); const path = require("path"); const { spawn, spawnSync } = require("child_process"); const pRetry = require("p-retry"); +const { version: NEMOCLAW_VERSION } = require("../../package.json"); /** Parse a numeric env var, returning `fallback` when unset or non-finite. */ function envInt(name, fallback) { @@ -85,6 +86,46 @@ function cleanupTempDir(filePath, expectedPrefix) { } const EXPERIMENTAL = process.env.NEMOCLAW_EXPERIMENTAL === "1"; +const OPENSHELL_COMPATIBILITY = Object.freeze({ + "0.1.0": Object.freeze(["0.0.7"]), +}); + +function parseSemver(version) { + const match = String(version || "") + .trim() + .match(/^(\d+)\.(\d+)\.(\d+)$/); + if (!match) return null; + return match.slice(1).map((part) => Number(part)); +} + +function isExactSemverMatch(version, supportedVersion) { + const actual = parseSemver(version); + const supported = parseSemver(supportedVersion); + return ( + Array.isArray(actual) && + Array.isArray(supported) && + actual[0] === supported[0] && + actual[1] === supported[1] && + actual[2] === supported[2] + ); +} + +function getSupportedOpenshellVersions(nemoclawVersion = NEMOCLAW_VERSION) { + return OPENSHELL_COMPATIBILITY[nemoclawVersion] || []; +} + +function isSupportedOpenshellVersion(version, supportedVersions = getSupportedOpenshellVersions()) { + if (!version || supportedVersions.length === 0) return false; + return supportedVersions.some((supportedVersion) => + isExactSemverMatch(version, supportedVersion), + ); +} + +function formatSupportedOpenshellVersions(supportedVersions) { + if (supportedVersions.length === 0) return null; + if (supportedVersions.length === 1) return supportedVersions[0]; + return `${supportedVersions.slice(0, -1).join(", ")}, or ${supportedVersions.at(-1)}`; +} const USE_COLOR = !process.env.NO_COLOR && !!process.stdout.isTTY; const DIM = USE_COLOR ? "\x1b[2m" : ""; const RESET = USE_COLOR ? "\x1b[0m" : ""; @@ -444,6 +485,15 @@ function getStableGatewayImageRef(versionOutput = null) { } function getOpenshellCompatibilityNotice(version = null) { + const supportedVersions = getSupportedOpenshellVersions(); + const supportedLabel = formatSupportedOpenshellVersions(supportedVersions); + if (version && supportedLabel && !isSupportedOpenshellVersion(version, supportedVersions)) { + return [ + ` OpenShell compatibility warning: NemoClaw ${NEMOCLAW_VERSION} was validated with OpenShell ${supportedLabel}, but found ${version}.`, + ` Install OpenShell ${supportedLabel}, then rerun \`nemoclaw onboard\` before creating or reusing NemoClaw-managed sandboxes.`, + " Avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you are also updating the NemoClaw compatibility baseline.", + ]; + } const suffix = version ? ` (${version})` : ""; return [ ` OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI${suffix}.`, @@ -1900,6 +1950,10 @@ async function preflight() { async function startGatewayWithOptions(_gpu, { exitOnFailure = true } = {}) { step(2, 7, "Starting OpenShell gateway"); + const gatewayEnv = getGatewayStartEnv(); + for (const line of getOpenshellCompatibilityNotice(gatewayEnv.IMAGE_TAG || null)) { + console.log(line); + } const gatewayStatus = runCaptureOpenshell(["status"], { ignoreError: true }); const gwInfo = runCaptureOpenshell(["gateway", "info", "-g", GATEWAY_NAME], { @@ -1923,13 +1977,9 @@ async function startGatewayWithOptions(_gpu, { exitOnFailure = true } = {}) { // sandbox itself does not need direct GPU access. Passing --gpu causes // FailedPrecondition errors when the gateway's k3s device plugin cannot // allocate GPUs. See: https://build.nvidia.com/spark/nemoclaw/instructions - const gatewayEnv = getGatewayStartEnv(); if (gatewayEnv.OPENSHELL_CLUSTER_IMAGE) { console.log(` Using pinned OpenShell gateway image: ${gatewayEnv.OPENSHELL_CLUSTER_IMAGE}`); } - for (const line of getOpenshellCompatibilityNotice(gatewayEnv.IMAGE_TAG || null)) { - console.log(line); - } // Retry gateway start with exponential backoff. On some hosts (Horde VMs, // first-run environments) the embedded k3s needs more time than OpenShell's diff --git a/test/onboard.test.js b/test/onboard.test.js index 2d1c84eee..8b28f759f 100644 --- a/test/onboard.test.js +++ b/test/onboard.test.js @@ -35,6 +35,8 @@ import { writeSandboxConfigSyncFile, } from "../bin/lib/onboard"; +const ROOT = path.resolve(import.meta.dirname, ".."); + describe("onboard helpers", () => { it("classifies sandbox create timeout failures and tracks upload progress", () => { expect( @@ -266,6 +268,26 @@ describe("onboard helpers", () => { ]); }); + it("warns when the installed OpenShell version falls outside the supported baseline", () => { + expect(getOpenshellCompatibilityNotice("0.0.8")).toEqual([ + " OpenShell compatibility warning: NemoClaw 0.1.0 was validated with OpenShell 0.0.7, but found 0.0.8.", + " Install OpenShell 0.0.7, then rerun `nemoclaw onboard` before creating or reusing NemoClaw-managed sandboxes.", + " Avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you are also updating the NemoClaw compatibility baseline.", + ]); + }); + + it("prints the compatibility notice before the healthy gateway reuse early return", () => { + const content = fs.readFileSync(path.join(ROOT, "bin/lib/onboard.js"), "utf-8"); + const startGwBlock = content.match(/async function startGatewayWithOptions[\s\S]*?^}/m); + expect(startGwBlock).toBeTruthy(); + + const noticeIndex = startGwBlock[0].indexOf("getOpenshellCompatibilityNotice("); + const healthyCheckIndex = startGwBlock[0].indexOf("if (isGatewayHealthy("); + expect(noticeIndex).toBeGreaterThanOrEqual(0); + expect(healthyCheckIndex).toBeGreaterThanOrEqual(0); + expect(noticeIndex).toBeLessThan(healthyCheckIndex); + }); + it("treats the gateway as healthy only when nemoclaw is running and connected", () => { expect( isGatewayHealthy( From d55083c73337ce127f8918854411556c62c0afb3 Mon Sep 17 00:00:00 2001 From: 13ernkastel Date: Tue, 7 Apr 2026 00:16:41 +0800 Subject: [PATCH 3/5] fix: address PR #1263 review feedback --- .../skills/nemoclaw-deploy-remote/SKILL.md | 35 +++-- .agents/skills/nemoclaw-get-started/SKILL.md | 4 + .../references/how-it-works.md | 6 +- .../references/architecture.md | 2 + .../nemoclaw-reference/references/commands.md | 34 +++-- .../references/troubleshooting.md | 19 ++- README.md | 2 +- bin/lib/onboard.js | 58 ++++++-- docs/get-started/quickstart.md | 2 +- docs/reference/commands.md | 2 +- test/onboard.test.js | 127 +++++++++++++++--- 11 files changed, 231 insertions(+), 60 deletions(-) diff --git a/.agents/skills/nemoclaw-deploy-remote/SKILL.md b/.agents/skills/nemoclaw-deploy-remote/SKILL.md index 08ab0bd53..86dc1db57 100644 --- a/.agents/skills/nemoclaw-deploy-remote/SKILL.md +++ b/.agents/skills/nemoclaw-deploy-remote/SKILL.md @@ -1,26 +1,26 @@ --- name: nemoclaw-deploy-remote -description: Provisions a remote GPU VM with NemoClaw using Brev deployment. Use when deploying to a cloud GPU, setting up a remote NemoClaw instance, or configuring Brev. Describes security hardening measures applied to the NemoClaw sandbox container image. Use when reviewing container security, Docker capabilities, process limits, or sandbox hardening controls. Forwards messages between Telegram and the sandboxed OpenClaw agent. Use when setting up a Telegram bot bridge, connecting a chat interface, or configuring Telegram integration. +description: Explains how to run NemoClaw on a remote GPU instance, including the deprecated Brev compatibility path and the preferred installer plus onboard flow. Describes security hardening measures applied to the NemoClaw sandbox container image. Use when reviewing container security, Docker capabilities, process limits, or sandbox hardening controls. Forwards messages between Telegram and the sandboxed OpenClaw agent. Use when setting up a Telegram bot bridge, connecting a chat interface, or configuring Telegram integration. --- # NemoClaw Deploy Remote -Provisions a remote GPU VM with NemoClaw using Brev deployment. Use when deploying to a cloud GPU, setting up a remote NemoClaw instance, or configuring Brev. +Explains how to run NemoClaw on a remote GPU instance, including the deprecated Brev compatibility path and the preferred installer plus onboard flow. ## Prerequisites - The [Brev CLI](https://brev.nvidia.com) installed and authenticated. -- An NVIDIA API key from [build.nvidia.com](https://build.nvidia.com). -- NemoClaw installed locally. Follow the Quickstart (see the `nemoclaw-get-started` skill) install steps. +- A provider credential for the inference backend you want to use during onboarding. +- NemoClaw installed locally if you plan to use the deprecated `nemoclaw deploy` wrapper. Otherwise, install NemoClaw directly on the remote host after provisioning it. - A running NemoClaw sandbox, either local or remote. - A Telegram bot token from [BotFather](https://t.me/BotFather). Run NemoClaw on a remote GPU instance through [Brev](https://brev.nvidia.com). -The deploy command provisions the VM, installs dependencies, and connects you to a running sandbox. +The preferred path is to provision the VM, run the standard NemoClaw installer on that host, and then run `nemoclaw onboard`. ## Step 1: Quick Start -If your Brev instance is already up and you want to try NemoClaw immediately, start with the sandbox chat flow: +If your Brev instance is already up and has already been onboarded with a sandbox, start with the standard sandbox chat flow: ```console $ nemoclaw my-assistant connect @@ -28,14 +28,16 @@ $ openclaw tui ``` This gets you into the sandbox shell first and opens the OpenClaw chat UI right away. +If the VM is fresh, run the standard installer on that host and then run `nemoclaw onboard` before trying `nemoclaw my-assistant connect`. -If you are connecting from your local machine and still need to provision the remote VM, use `nemoclaw deploy ` as described below. +If you are connecting from your local machine and still need to provision the remote VM, you can still use `nemoclaw deploy ` as the legacy compatibility path described below. ## Step 2: Deploy the Instance -> **Warning:** The `nemoclaw deploy` command is experimental and may not work as expected. +> **Warning:** The `nemoclaw deploy` command is deprecated. +> Prefer provisioning the remote host separately, then running the standard NemoClaw installer and `nemoclaw onboard` on that host. -Create a Brev instance and run the NemoClaw setup: +Create a Brev instance and run the legacy compatibility flow: ```console $ nemoclaw deploy @@ -43,17 +45,19 @@ $ nemoclaw deploy Replace `` with a name for your remote instance, for example `my-gpu-box`. -The deploy script performs the following steps on the VM: +The legacy compatibility flow performs the following steps on the VM: 1. Installs Docker and the NVIDIA Container Toolkit if a GPU is present. 2. Installs the OpenShell CLI. 3. Runs `nemoclaw onboard` (the setup wizard) to create the gateway, register providers, and launch the sandbox. -4. Starts auxiliary services, such as the Telegram bridge and cloudflared tunnel. +4. Starts auxiliary services, such as the Telegram bridge and cloudflared tunnel, when those tools are available. + +By default, the compatibility wrapper asks Brev to provision on `gcp`. Override this with `NEMOCLAW_BREV_PROVIDER` if you need a different Brev cloud provider. ## Step 3: Connect to the Remote Sandbox After deployment finishes, the deploy command opens an interactive shell inside the remote sandbox. -To reconnect after closing the session, run the deploy command again: +To reconnect after closing the session, run the command again: ```console $ nemoclaw deploy @@ -92,8 +96,11 @@ For SSH port-forwarding, the origin is typically `http://127.0.0.1:18789` (the default), so no extra configuration is needed. > **Note:** On Brev, set `CHAT_UI_URL` in the launchable environment configuration so it is -> available when the setup script builds the sandbox image. If `CHAT_UI_URL` is -> not set on a headless host, `brev-setup.sh` prints a warning. +> available when the installer builds the sandbox image. If `CHAT_UI_URL` is not +> set on a headless host, the compatibility wrapper prints a warning. +> **Warning:** `NEMOCLAW_DISABLE_DEVICE_AUTH` is also evaluated at image build time. +> If you disable device auth for a remote deployment, any device that can reach the dashboard origin can connect without pairing. +> Avoid this on internet-reachable or shared-network deployments. ## Step 7: GPU Configuration diff --git a/.agents/skills/nemoclaw-get-started/SKILL.md b/.agents/skills/nemoclaw-get-started/SKILL.md index 254016160..51a8d6487 100644 --- a/.agents/skills/nemoclaw-get-started/SKILL.md +++ b/.agents/skills/nemoclaw-get-started/SKILL.md @@ -32,6 +32,10 @@ curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash If you use nvm or fnm to manage Node.js, the installer may not update your current shell's PATH. If `nemoclaw` is not found after install, run `source ~/.bashrc` (or `source ~/.zshrc` for zsh) or open a new terminal. +> **Note:** The onboard flow builds the sandbox image with `NEMOCLAW_DISABLE_DEVICE_AUTH=1` so the dashboard is immediately usable during setup. +> This is a build-time setting baked into the sandbox image, not a runtime knob. +> If you export `NEMOCLAW_DISABLE_DEVICE_AUTH` after onboarding finishes, it has no effect on an existing sandbox. + When the install completes, a summary confirms the running environment: ```text diff --git a/.agents/skills/nemoclaw-overview/references/how-it-works.md b/.agents/skills/nemoclaw-overview/references/how-it-works.md index 535aeff12..fc7e6b140 100644 --- a/.agents/skills/nemoclaw-overview/references/how-it-works.md +++ b/.agents/skills/nemoclaw-overview/references/how-it-works.md @@ -71,9 +71,9 @@ Respect CLI boundaries Supply chain safety : Blueprint artifacts are immutable, versioned, and digest-verified before execution. -OpenShell-native for new installs -: For users without an existing OpenClaw installation, NemoClaw recommends `openshell sandbox create` directly - rather than forcing a plugin-driven bootstrap. +OpenShell-backed lifecycle +: NemoClaw orchestrates OpenShell resources under the hood, but `nemoclaw onboard` + is the supported operator entry point for creating or recreating NemoClaw-managed sandboxes. Reproducible setup : Running setup again recreates the sandbox from the same blueprint and policy definitions. diff --git a/.agents/skills/nemoclaw-reference/references/architecture.md b/.agents/skills/nemoclaw-reference/references/architecture.md index 1fcfb8435..8cdb0b53c 100644 --- a/.agents/skills/nemoclaw-reference/references/architecture.md +++ b/.agents/skills/nemoclaw-reference/references/architecture.md @@ -167,5 +167,7 @@ The following environment variables configure optional services and local access | `TELEGRAM_BOT_TOKEN` | Bot token for the Telegram bridge. | | `ALLOWED_CHAT_IDS` | Comma-separated list of Telegram chat IDs allowed to message the agent. | | `CHAT_UI_URL` | URL for the optional chat UI endpoint. | +| `NEMOCLAW_DISABLE_DEVICE_AUTH` | Build-time-only toggle that disables gateway device pairing when set to `1` before the sandbox image is created. | For normal setup and reconfiguration, prefer `nemoclaw onboard` over editing these files by hand. +Do not treat `NEMOCLAW_DISABLE_DEVICE_AUTH` as a runtime setting for an already-created sandbox. diff --git a/.agents/skills/nemoclaw-reference/references/commands.md b/.agents/skills/nemoclaw-reference/references/commands.md index 15dbe9068..325a1d6e3 100644 --- a/.agents/skills/nemoclaw-reference/references/commands.md +++ b/.agents/skills/nemoclaw-reference/references/commands.md @@ -44,11 +44,19 @@ Use this command for new installs and for recreating a sandbox after changes to $ nemoclaw onboard ``` +> **Warning:** For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox. +> NemoClaw derives the gateway image from the installed `openshell` CLI version, so `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` can replace the runtime with a different OpenShell release than NemoClaw 0.1.0 supports. + The wizard prompts for a provider first, then collects the provider credential if needed. Supported non-experimental choices include NVIDIA Endpoints, OpenAI, Anthropic, Google Gemini, and compatible OpenAI or Anthropic endpoints. Credentials are stored in `~/.nemoclaw/credentials.json`. The legacy `nemoclaw setup` command is deprecated; use `nemoclaw onboard` instead. +If you enable Brave Search during onboarding, NemoClaw currently stores the Brave API key in the sandbox's OpenClaw configuration. +That means the OpenClaw agent can read the key. +NemoClaw explores an OpenShell-hosted credential path first, but the current OpenClaw Brave runtime does not consume that path end to end yet. +Treat Brave Search as an explicit opt-in and use a dedicated low-privilege Brave key. + For non-interactive onboarding, you must explicitly accept the third-party software notice: ```console @@ -61,12 +69,21 @@ or: $ NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 nemoclaw onboard --non-interactive ``` +To enable Brave Search in non-interactive mode, set: + +```console +$ BRAVE_API_KEY=... \ + nemoclaw onboard --non-interactive +``` + +`BRAVE_API_KEY` enables Brave Search in non-interactive mode and also enables `web_fetch`. + The wizard prompts for a sandbox name. Names must follow RFC 1123 subdomain rules: lowercase alphanumeric characters and hyphens only, and must start and end with an alphanumeric character. Uppercase letters are automatically lowercased. Before creating the gateway, the wizard runs preflight checks. -On systems with cgroup v2 (Ubuntu 24.04, DGX Spark, WSL2), it verifies that Docker is configured with `"default-cgroupns-mode": "host"` and provides fix instructions if the setting is missing. +It verifies that Docker is reachable, warns on unsupported runtimes such as Podman, and prints host remediation guidance when prerequisites are missing. ### `nemoclaw list` @@ -78,10 +95,11 @@ $ nemoclaw list ### `nemoclaw deploy` -> **Warning:** The `nemoclaw deploy` command is experimental and may not work as expected. +> **Warning:** The `nemoclaw deploy` command is deprecated. +> Prefer provisioning the remote host separately, then running the standard NemoClaw installer and `nemoclaw onboard` on that host. Deploy NemoClaw to a remote GPU instance through [Brev](https://brev.nvidia.com). -The deploy script installs Docker, NVIDIA Container Toolkit if a GPU is present, and OpenShell on the VM, then runs `nemoclaw onboard` and connects to the sandbox. +This command remains as a compatibility wrapper for the older Brev-specific bootstrap flow. ```console $ nemoclaw deploy @@ -181,13 +199,13 @@ $ nemoclaw status ### `nemoclaw setup-spark` -Set up NemoClaw on DGX Spark. -This command applies cgroup v2 and Docker fixes required for Ubuntu 24.04. -Run with `sudo` on the Spark host. -After the fixes complete, the script prompts you to run `nemoclaw onboard` to continue setup. +> **Warning:** The `nemoclaw setup-spark` command is deprecated. +> Use the standard installer and run `nemoclaw onboard` instead, because current OpenShell releases handle the older DGX Spark cgroup behavior. + +This command remains as a compatibility alias to `nemoclaw onboard`. ```console -$ sudo nemoclaw setup-spark +$ nemoclaw setup-spark ``` ### `nemoclaw debug` diff --git a/.agents/skills/nemoclaw-reference/references/troubleshooting.md b/.agents/skills/nemoclaw-reference/references/troubleshooting.md index b0677a813..e94c6fd9a 100644 --- a/.agents/skills/nemoclaw-reference/references/troubleshooting.md +++ b/.agents/skills/nemoclaw-reference/references/troubleshooting.md @@ -95,16 +95,18 @@ Then retry onboarding. ### Cgroup v2 errors during onboard -On Ubuntu 24.04, DGX Spark, and WSL2, Docker may not be configured for cgroup v2 delegation. -The onboard preflight check detects this and fails with a clear error message. +Older NemoClaw releases relied on a Docker cgroup workaround on Ubuntu 24.04, DGX Spark, and WSL2. +Current OpenShell releases handle that behavior themselves, so NemoClaw no longer requires a Spark-specific setup step. -Run the Spark setup script to fix the Docker cgroup configuration, then retry onboarding: +If onboarding reports that Docker is missing or unreachable, fix Docker first and retry onboarding: ```console -$ sudo nemoclaw setup-spark $ nemoclaw onboard ``` +If you are using Podman, NemoClaw warns and continues, but OpenShell officially documents Docker-based runtimes only. +If onboarding or sandbox lifecycle fails, switch to Docker Desktop, Colima, or Docker Engine and rerun onboarding. + ### Invalid sandbox name Sandbox names must follow RFC 1123 subdomain rules: lowercase alphanumeric characters and hyphens only, and must start and end with an alphanumeric character. @@ -219,6 +221,15 @@ $ nemoclaw status If the endpoint is correct but requests still fail, check for network policy rules that may block the connection. Then verify the credential and base URL for the provider you selected during onboarding. +### `NEMOCLAW_DISABLE_DEVICE_AUTH=1` does not change an existing sandbox + +This is expected behavior. +`NEMOCLAW_DISABLE_DEVICE_AUTH` is a build-time setting used when NemoClaw creates the sandbox image. +Changing or exporting it later does not rewrite the baked `openclaw.json` inside an existing sandbox. + +If you need a different device-auth setting, rerun onboarding so NemoClaw rebuilds the sandbox image with the desired configuration. +For the security trade-offs, refer to Security Best Practices (see the `nemoclaw-security-best` skill). + ### Agent cannot reach an external host OpenShell blocks outbound connections to hosts not listed in the network policy. diff --git a/README.md b/README.md index e9d70edc3..39aa91f0f 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc #### OpenShell Compatibility -NemoClaw 0.1.0 was validated with OpenShell 0.0.7. +NemoClaw 0.1.0 supports OpenShell 0.0.7. During onboarding, NemoClaw derives the OpenShell gateway image from the installed `openshell` CLI version, so upgrading OpenShell independently can rebuild the sandbox with a different runtime layout than the current NemoClaw release expects. | NemoClaw | OpenShell | Notes | diff --git a/bin/lib/onboard.js b/bin/lib/onboard.js index 5a3bbbb4d..bfcd7c719 100644 --- a/bin/lib/onboard.js +++ b/bin/lib/onboard.js @@ -97,11 +97,13 @@ const EXPERIMENTAL = process.env.NEMOCLAW_EXPERIMENTAL === "1"; const OPENSHELL_COMPATIBILITY = Object.freeze({ "0.1.0": Object.freeze(["0.0.7"]), }); +const OPENSHELL_DIRECT_COMMANDS = + "`openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create`"; function parseSemver(version) { const match = String(version || "") .trim() - .match(/^(\d+)\.(\d+)\.(\d+)$/); + .match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/); if (!match) return null; return match.slice(1).map((part) => Number(part)); } @@ -119,7 +121,8 @@ function isExactSemverMatch(version, supportedVersion) { } function getSupportedOpenshellVersions(nemoclawVersion = NEMOCLAW_VERSION) { - return OPENSHELL_COMPATIBILITY[nemoclawVersion] || []; + const normalizedVersion = parseSemver(nemoclawVersion)?.join("."); + return (normalizedVersion && OPENSHELL_COMPATIBILITY[normalizedVersion]) || []; } function isSupportedOpenshellVersion(version, supportedVersions = getSupportedOpenshellVersions()) { @@ -132,6 +135,7 @@ function isSupportedOpenshellVersion(version, supportedVersions = getSupportedOp function formatSupportedOpenshellVersions(supportedVersions) { if (supportedVersions.length === 0) return null; if (supportedVersions.length === 1) return supportedVersions[0]; + if (supportedVersions.length === 2) return supportedVersions.join(" or "); return `${supportedVersions.slice(0, -1).join(", ")}, or ${supportedVersions.at(-1)}`; } const USE_COLOR = !process.env.NO_COLOR && !!process.stdout.isTTY; @@ -428,23 +432,51 @@ function getStableGatewayImageRef(versionOutput = null) { return `ghcr.io/nvidia/openshell/cluster:${version}`; } -function getOpenshellCompatibilityNotice(version = null) { - const supportedVersions = getSupportedOpenshellVersions(); +function getOpenshellCompatibilityLevel(version = null, nemoclawVersion = NEMOCLAW_VERSION) { + const supportedVersions = getSupportedOpenshellVersions(nemoclawVersion); const supportedLabel = formatSupportedOpenshellVersions(supportedVersions); - if (version && supportedLabel && !isSupportedOpenshellVersion(version, supportedVersions)) { + if (!supportedLabel) return "warn"; + if (!version) return "warn"; + if (!isSupportedOpenshellVersion(version, supportedVersions)) return "warn"; + return "info"; +} + +function getOpenshellCompatibilityNotice(version = null, nemoclawVersion = NEMOCLAW_VERSION) { + const supportedVersions = getSupportedOpenshellVersions(nemoclawVersion); + const supportedLabel = formatSupportedOpenshellVersions(supportedVersions); + if (!supportedLabel) { return [ - ` OpenShell compatibility warning: NemoClaw ${NEMOCLAW_VERSION} was validated with OpenShell ${supportedLabel}, but found ${version}.`, + ` OpenShell compatibility warning: NemoClaw ${nemoclawVersion} does not declare a supported OpenShell baseline.`, + " Update the OPENSHELL_COMPATIBILITY map before onboarding against a new OpenShell release.", + ` Avoid ${OPENSHELL_DIRECT_COMMANDS} directly until the compatibility baseline is updated.`, + ]; + } + if (!version) { + return [ + " OpenShell compatibility warning: Could not detect the installed openshell CLI version.", + " Verify `openshell -V` works, then rerun `nemoclaw onboard` before creating or reusing NemoClaw-managed sandboxes.", + ` Avoid ${OPENSHELL_DIRECT_COMMANDS} directly until the compatibility check succeeds.`, + ]; + } + if (!isSupportedOpenshellVersion(version, supportedVersions)) { + return [ + ` OpenShell compatibility warning: NemoClaw ${nemoclawVersion} supports OpenShell ${supportedLabel}, but found ${version}.`, ` Install OpenShell ${supportedLabel}, then rerun \`nemoclaw onboard\` before creating or reusing NemoClaw-managed sandboxes.`, - " Avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you are also updating the NemoClaw compatibility baseline.", + ` Avoid ${OPENSHELL_DIRECT_COMMANDS} directly unless you are also updating the NemoClaw compatibility baseline.`, ]; } - const suffix = version ? ` (${version})` : ""; return [ - ` OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI${suffix}.`, - " Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.", + ` OpenShell compatibility: OpenShell ${version} is supported for NemoClaw ${nemoclawVersion}. Use \`nemoclaw onboard\` to recreate NemoClaw-managed sandboxes, and avoid ${OPENSHELL_DIRECT_COMMANDS} directly.`, ]; } +function printOpenshellCompatibilityNotice(version = null) { + const printer = getOpenshellCompatibilityLevel(version) === "warn" ? console.warn : console.log; + for (const line of getOpenshellCompatibilityNotice(version)) { + printer(line); + } +} + function getOpenshellBinary() { if (OPENSHELL_BIN) return OPENSHELL_BIN; const resolved = resolveOpenshell(); @@ -1751,9 +1783,7 @@ async function preflight() { async function startGatewayWithOptions(_gpu, { exitOnFailure = true } = {}) { step(2, 8, "Starting OpenShell gateway"); const gatewayEnv = getGatewayStartEnv(); - for (const line of getOpenshellCompatibilityNotice(gatewayEnv.IMAGE_TAG || null)) { - console.log(line); - } + printOpenshellCompatibilityNotice(gatewayEnv.IMAGE_TAG || null); const gatewayStatus = runCaptureOpenshell(["status"], { ignoreError: true }); const gwInfo = runCaptureOpenshell(["gateway", "info", "-g", GATEWAY_NAME], { @@ -4033,6 +4063,7 @@ module.exports = { copyBuildContextDir, classifySandboxCreateFailure, createSandbox, + formatSupportedOpenshellVersions, formatEnvAssignment, getFutureShellPathHint, getGatewayStartEnv, @@ -4040,6 +4071,7 @@ module.exports = { getNavigationChoice, getSandboxInferenceConfig, getOpenshellCompatibilityNotice, + getSupportedOpenshellVersions, getInstalledOpenshellVersion, getRequestedModelHint, getRequestedProviderHint, diff --git a/docs/get-started/quickstart.md b/docs/get-started/quickstart.md index d5f25bb62..40cf10288 100644 --- a/docs/get-started/quickstart.md +++ b/docs/get-started/quickstart.md @@ -56,7 +56,7 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc | [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed | :::{warning} OpenShell compatibility -NemoClaw 0.1.0 was validated with OpenShell 0.0.7. +NemoClaw 0.1.0 supports OpenShell 0.0.7. During onboarding, NemoClaw derives the OpenShell gateway image from the installed `openshell` CLI version, so upgrading OpenShell independently can rebuild the sandbox with a different runtime layout than the current NemoClaw release expects. ::: diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 26b492f1f..6faf7b20f 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -68,7 +68,7 @@ $ nemoclaw onboard :::{warning} For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox. -NemoClaw derives the gateway image from the installed `openshell` CLI version, so `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` can replace the runtime with a different OpenShell release than NemoClaw 0.1.0 was validated against. +NemoClaw derives the gateway image from the installed `openshell` CLI version, so `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` can replace the runtime with a different OpenShell release than NemoClaw 0.1.0 supports. ::: The wizard prompts for a provider first, then collects the provider credential if needed. diff --git a/test/onboard.test.js b/test/onboard.test.js index 09031c920..abe4bfbae 100644 --- a/test/onboard.test.js +++ b/test/onboard.test.js @@ -13,6 +13,7 @@ import { buildSandboxConfigSyncScript, classifySandboxCreateFailure, compactText, + formatSupportedOpenshellVersions, formatEnvAssignment, getNavigationChoice, getGatewayReuseState, @@ -28,6 +29,7 @@ import { getResumeSandboxConflict, getSandboxStateFromOutputs, getStableGatewayImageRef, + getSupportedOpenshellVersions, isGatewayHealthy, classifyValidationFailure, isLoopbackHostname, @@ -46,6 +48,76 @@ import { buildWebSearchDockerConfig } from "../dist/lib/web-search"; const ROOT = path.resolve(import.meta.dirname, ".."); +function runGatewayReuseWithMockOpenshell(versionOutput) { + const mockDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openshell-mock-")); + const openshellPath = path.join(mockDir, "openshell"); + const onboardPath = path.join(ROOT, "bin/lib/onboard.js"); + const childScript = ` +const { startGatewayForRecovery } = require(${JSON.stringify(onboardPath)}); +Promise.resolve(startGatewayForRecovery(false)) + .then(() => process.exit(0)) + .catch((error) => { + console.error(error && error.stack ? error.stack : String(error)); + process.exit(1); + }); +`; + + fs.writeFileSync( + openshellPath, + `#!/bin/sh +set -eu + +if [ "\${1:-}" = "-V" ]; then + printf '%s\\n' ${JSON.stringify(versionOutput)} + exit 0 +fi + +if [ "\${1:-}" = "status" ]; then + cat <<'EOF' +Gateway status: Connected +Gateway: nemoclaw +EOF + exit 0 +fi + +if [ "\${1:-}" = "gateway" ] && [ "\${2:-}" = "info" ]; then + cat <<'EOF' +Gateway Info + + Gateway: nemoclaw + Gateway endpoint: https://127.0.0.1:8080 +EOF + exit 0 +fi + +if [ "\${1:-}" = "gateway" ] && [ "\${2:-}" = "select" ]; then + exit 0 +fi + +printf 'unexpected openshell args: %s\\n' "$*" >&2 +exit 1 +`, + { mode: 0o755 }, + ); + + try { + const envPath = process.env.PATH ? `${mockDir}:${process.env.PATH}` : mockDir; + const result = spawnSync(process.execPath, ["-e", childScript], { + cwd: ROOT, + env: { + ...process.env, + NO_COLOR: "1", + PATH: envPath, + }, + encoding: "utf8", + }); + expect(result.status).toBe(0); + return result; + } finally { + fs.rmSync(mockDir, { recursive: true, force: true }); + } +} + describe("onboard helpers", () => { it("classifies sandbox create timeout failures and tracks upload progress", () => { expect( @@ -317,35 +389,60 @@ describe("onboard helpers", () => { expect(getStableGatewayImageRef("bogus")).toBe(null); }); - it("prints a compatibility note when the gateway version is pinned", () => { + it("tracks the current NemoClaw version in the compatibility map", () => { + const { version } = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")); + expect(getSupportedOpenshellVersions(version).length).toBeGreaterThan(0); + expect(getSupportedOpenshellVersions(`${version}-beta.1`)).toEqual( + getSupportedOpenshellVersions(version), + ); + }); + + it("formats supported OpenShell versions with correct grammar", () => { + expect(formatSupportedOpenshellVersions(["0.0.7"])).toBe("0.0.7"); + expect(formatSupportedOpenshellVersions(["0.0.7", "0.0.8"])).toBe("0.0.7 or 0.0.8"); + expect(formatSupportedOpenshellVersions(["0.0.7", "0.0.8", "0.0.9"])).toBe( + "0.0.7, 0.0.8, or 0.0.9", + ); + }); + + it("prints a concise compatibility note when the gateway version is supported", () => { expect(getOpenshellCompatibilityNotice("0.0.7")).toEqual([ - " OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI (0.0.7).", - " Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.", + " OpenShell compatibility: OpenShell 0.0.7 is supported for NemoClaw 0.1.0. Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.", ]); + }); + + it("warns when the installed OpenShell version cannot be detected", () => { expect(getOpenshellCompatibilityNotice()).toEqual([ - " OpenShell compatibility: NemoClaw derives the gateway image from the installed openshell CLI.", - " Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.", + " OpenShell compatibility warning: Could not detect the installed openshell CLI version.", + " Verify `openshell -V` works, then rerun `nemoclaw onboard` before creating or reusing NemoClaw-managed sandboxes.", + " Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly until the compatibility check succeeds.", ]); }); it("warns when the installed OpenShell version falls outside the supported baseline", () => { expect(getOpenshellCompatibilityNotice("0.0.8")).toEqual([ - " OpenShell compatibility warning: NemoClaw 0.1.0 was validated with OpenShell 0.0.7, but found 0.0.8.", + " OpenShell compatibility warning: NemoClaw 0.1.0 supports OpenShell 0.0.7, but found 0.0.8.", " Install OpenShell 0.0.7, then rerun `nemoclaw onboard` before creating or reusing NemoClaw-managed sandboxes.", - " Avoid `openshell self-update`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you are also updating the NemoClaw compatibility baseline.", + " Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you are also updating the NemoClaw compatibility baseline.", ]); }); - it("prints the compatibility notice before the healthy gateway reuse early return", () => { - const content = fs.readFileSync(path.join(ROOT, "bin/lib/onboard.js"), "utf-8"); - const startGwBlock = content.match(/async function startGatewayWithOptions[\s\S]*?^}/m); - expect(startGwBlock).toBeTruthy(); + it("prints compatibility guidance before reusing a healthy gateway", () => { + const result = runGatewayReuseWithMockOpenshell("openshell 0.0.7"); + expect(result.stderr).toBe(""); - const noticeIndex = startGwBlock[0].indexOf("getOpenshellCompatibilityNotice("); - const healthyCheckIndex = startGwBlock[0].indexOf("if (isGatewayHealthy("); + const noticeIndex = result.stdout.indexOf("OpenShell compatibility:"); + const reuseIndex = result.stdout.indexOf("✓ Reusing existing gateway"); expect(noticeIndex).toBeGreaterThanOrEqual(0); - expect(healthyCheckIndex).toBeGreaterThanOrEqual(0); - expect(noticeIndex).toBeLessThan(healthyCheckIndex); + expect(reuseIndex).toBeGreaterThanOrEqual(0); + expect(noticeIndex).toBeLessThan(reuseIndex); + }); + + it("sends compatibility warnings to stderr for unsupported OpenShell versions", () => { + const result = runGatewayReuseWithMockOpenshell("openshell 0.0.8"); + expect(result.stdout).toContain("✓ Reusing existing gateway"); + expect(result.stderr).toContain("OpenShell compatibility warning:"); + expect(result.stderr).toContain("npm update -g openshell"); }); it("treats the gateway as healthy only when nemoclaw is running and connected", () => { From a8a94301d22b714b3672a9d3ed8788beb7b1fa40 Mon Sep 17 00:00:00 2001 From: 13ernkastel Date: Tue, 7 Apr 2026 00:23:17 +0800 Subject: [PATCH 4/5] chore: refresh DCO check Signed-off-by: 13ernkastel From 9f41f29ea07ef9351bd2e024e3633233190bfa91 Mon Sep 17 00:00:00 2001 From: 13ernkastel Date: Tue, 7 Apr 2026 08:43:20 +0800 Subject: [PATCH 5/5] docs: split version-agnostic OpenShell lifecycle guidance Signed-off-by: 13ernkastel --- .../nemoclaw-reference/references/commands.md | 2 +- README.md | 10 +- bin/lib/onboard.js | 96 +------------ docs/get-started/quickstart.md | 10 +- docs/reference/commands.md | 2 +- test/onboard.test.js | 131 ------------------ 6 files changed, 9 insertions(+), 242 deletions(-) diff --git a/.agents/skills/nemoclaw-reference/references/commands.md b/.agents/skills/nemoclaw-reference/references/commands.md index 325a1d6e3..19bc39b90 100644 --- a/.agents/skills/nemoclaw-reference/references/commands.md +++ b/.agents/skills/nemoclaw-reference/references/commands.md @@ -45,7 +45,7 @@ $ nemoclaw onboard ``` > **Warning:** For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox. -> NemoClaw derives the gateway image from the installed `openshell` CLI version, so `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` can replace the runtime with a different OpenShell release than NemoClaw 0.1.0 supports. +> Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then rerun `nemoclaw onboard`. The wizard prompts for a provider first, then collects the provider credential if needed. Supported non-experimental choices include NVIDIA Endpoints, OpenAI, Anthropic, Google Gemini, and compatible OpenAI or Anthropic endpoints. diff --git a/README.md b/README.md index 39aa91f0f..651bbcba7 100644 --- a/README.md +++ b/README.md @@ -52,14 +52,10 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc | Container runtime | Supported runtime installed and running | | [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed | -#### OpenShell Compatibility +#### OpenShell Lifecycle -NemoClaw 0.1.0 supports OpenShell 0.0.7. -During onboarding, NemoClaw derives the OpenShell gateway image from the installed `openshell` CLI version, so upgrading OpenShell independently can rebuild the sandbox with a different runtime layout than the current NemoClaw release expects. - -| NemoClaw | OpenShell | Notes | -|----------|-----------|-------| -| 0.1.0 | 0.0.7 | Use `nemoclaw onboard` to create or recreate NemoClaw-managed sandboxes. Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then re-onboard. | +For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox. +Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then rerun `nemoclaw onboard`. #### Container Runtimes diff --git a/bin/lib/onboard.js b/bin/lib/onboard.js index bfcd7c719..65d839bab 100644 --- a/bin/lib/onboard.js +++ b/bin/lib/onboard.js @@ -10,7 +10,6 @@ const os = require("os"); const path = require("path"); const { spawn, spawnSync } = require("child_process"); const pRetry = require("p-retry"); -const { version: NEMOCLAW_VERSION } = require("../../package.json"); /** Parse a numeric env var, returning `fallback` when unset or non-finite. */ function envInt(name, fallback) { @@ -94,50 +93,6 @@ function cleanupTempDir(filePath, expectedPrefix) { } const EXPERIMENTAL = process.env.NEMOCLAW_EXPERIMENTAL === "1"; -const OPENSHELL_COMPATIBILITY = Object.freeze({ - "0.1.0": Object.freeze(["0.0.7"]), -}); -const OPENSHELL_DIRECT_COMMANDS = - "`openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create`"; - -function parseSemver(version) { - const match = String(version || "") - .trim() - .match(/^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/); - if (!match) return null; - return match.slice(1).map((part) => Number(part)); -} - -function isExactSemverMatch(version, supportedVersion) { - const actual = parseSemver(version); - const supported = parseSemver(supportedVersion); - return ( - Array.isArray(actual) && - Array.isArray(supported) && - actual[0] === supported[0] && - actual[1] === supported[1] && - actual[2] === supported[2] - ); -} - -function getSupportedOpenshellVersions(nemoclawVersion = NEMOCLAW_VERSION) { - const normalizedVersion = parseSemver(nemoclawVersion)?.join("."); - return (normalizedVersion && OPENSHELL_COMPATIBILITY[normalizedVersion]) || []; -} - -function isSupportedOpenshellVersion(version, supportedVersions = getSupportedOpenshellVersions()) { - if (!version || supportedVersions.length === 0) return false; - return supportedVersions.some((supportedVersion) => - isExactSemverMatch(version, supportedVersion), - ); -} - -function formatSupportedOpenshellVersions(supportedVersions) { - if (supportedVersions.length === 0) return null; - if (supportedVersions.length === 1) return supportedVersions[0]; - if (supportedVersions.length === 2) return supportedVersions.join(" or "); - return `${supportedVersions.slice(0, -1).join(", ")}, or ${supportedVersions.at(-1)}`; -} const USE_COLOR = !process.env.NO_COLOR && !!process.stdout.isTTY; const DIM = USE_COLOR ? "\x1b[2m" : ""; const RESET = USE_COLOR ? "\x1b[0m" : ""; @@ -432,51 +387,6 @@ function getStableGatewayImageRef(versionOutput = null) { return `ghcr.io/nvidia/openshell/cluster:${version}`; } -function getOpenshellCompatibilityLevel(version = null, nemoclawVersion = NEMOCLAW_VERSION) { - const supportedVersions = getSupportedOpenshellVersions(nemoclawVersion); - const supportedLabel = formatSupportedOpenshellVersions(supportedVersions); - if (!supportedLabel) return "warn"; - if (!version) return "warn"; - if (!isSupportedOpenshellVersion(version, supportedVersions)) return "warn"; - return "info"; -} - -function getOpenshellCompatibilityNotice(version = null, nemoclawVersion = NEMOCLAW_VERSION) { - const supportedVersions = getSupportedOpenshellVersions(nemoclawVersion); - const supportedLabel = formatSupportedOpenshellVersions(supportedVersions); - if (!supportedLabel) { - return [ - ` OpenShell compatibility warning: NemoClaw ${nemoclawVersion} does not declare a supported OpenShell baseline.`, - " Update the OPENSHELL_COMPATIBILITY map before onboarding against a new OpenShell release.", - ` Avoid ${OPENSHELL_DIRECT_COMMANDS} directly until the compatibility baseline is updated.`, - ]; - } - if (!version) { - return [ - " OpenShell compatibility warning: Could not detect the installed openshell CLI version.", - " Verify `openshell -V` works, then rerun `nemoclaw onboard` before creating or reusing NemoClaw-managed sandboxes.", - ` Avoid ${OPENSHELL_DIRECT_COMMANDS} directly until the compatibility check succeeds.`, - ]; - } - if (!isSupportedOpenshellVersion(version, supportedVersions)) { - return [ - ` OpenShell compatibility warning: NemoClaw ${nemoclawVersion} supports OpenShell ${supportedLabel}, but found ${version}.`, - ` Install OpenShell ${supportedLabel}, then rerun \`nemoclaw onboard\` before creating or reusing NemoClaw-managed sandboxes.`, - ` Avoid ${OPENSHELL_DIRECT_COMMANDS} directly unless you are also updating the NemoClaw compatibility baseline.`, - ]; - } - return [ - ` OpenShell compatibility: OpenShell ${version} is supported for NemoClaw ${nemoclawVersion}. Use \`nemoclaw onboard\` to recreate NemoClaw-managed sandboxes, and avoid ${OPENSHELL_DIRECT_COMMANDS} directly.`, - ]; -} - -function printOpenshellCompatibilityNotice(version = null) { - const printer = getOpenshellCompatibilityLevel(version) === "warn" ? console.warn : console.log; - for (const line of getOpenshellCompatibilityNotice(version)) { - printer(line); - } -} - function getOpenshellBinary() { if (OPENSHELL_BIN) return OPENSHELL_BIN; const resolved = resolveOpenshell(); @@ -1782,8 +1692,6 @@ async function preflight() { async function startGatewayWithOptions(_gpu, { exitOnFailure = true } = {}) { step(2, 8, "Starting OpenShell gateway"); - const gatewayEnv = getGatewayStartEnv(); - printOpenshellCompatibilityNotice(gatewayEnv.IMAGE_TAG || null); const gatewayStatus = runCaptureOpenshell(["status"], { ignoreError: true }); const gwInfo = runCaptureOpenshell(["gateway", "info", "-g", GATEWAY_NAME], { @@ -1830,6 +1738,7 @@ async function startGatewayWithOptions(_gpu, { exitOnFailure = true } = {}) { // sandbox itself does not need direct GPU access. Passing --gpu causes // FailedPrecondition errors when the gateway's k3s device plugin cannot // allocate GPUs. See: https://build.nvidia.com/spark/nemoclaw/instructions + const gatewayEnv = getGatewayStartEnv(); if (gatewayEnv.OPENSHELL_CLUSTER_IMAGE) { console.log(` Using pinned OpenShell gateway image: ${gatewayEnv.OPENSHELL_CLUSTER_IMAGE}`); } @@ -4063,15 +3972,12 @@ module.exports = { copyBuildContextDir, classifySandboxCreateFailure, createSandbox, - formatSupportedOpenshellVersions, formatEnvAssignment, getFutureShellPathHint, getGatewayStartEnv, getGatewayReuseState, getNavigationChoice, getSandboxInferenceConfig, - getOpenshellCompatibilityNotice, - getSupportedOpenshellVersions, getInstalledOpenshellVersion, getRequestedModelHint, getRequestedProviderHint, diff --git a/docs/get-started/quickstart.md b/docs/get-started/quickstart.md index 40cf10288..e314dbf1b 100644 --- a/docs/get-started/quickstart.md +++ b/docs/get-started/quickstart.md @@ -55,15 +55,11 @@ The sandbox image is approximately 2.4 GB compressed. During image push, the Doc | Container runtime | Supported runtime installed and running | | [OpenShell](https://github.com/NVIDIA/OpenShell) | Installed | -:::{warning} OpenShell compatibility -NemoClaw 0.1.0 supports OpenShell 0.0.7. -During onboarding, NemoClaw derives the OpenShell gateway image from the installed `openshell` CLI version, so upgrading OpenShell independently can rebuild the sandbox with a different runtime layout than the current NemoClaw release expects. +:::{warning} OpenShell lifecycle +For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox. +Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then rerun `nemoclaw onboard`. ::: -| NemoClaw | OpenShell | Notes | -|----------|-----------|-------| -| 0.1.0 | 0.0.7 | Use `nemoclaw onboard` to create or recreate NemoClaw-managed sandboxes. Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then re-onboard. | - ### Container Runtimes | Platform | Supported runtimes | Notes | diff --git a/docs/reference/commands.md b/docs/reference/commands.md index 6faf7b20f..bc5d5bc66 100644 --- a/docs/reference/commands.md +++ b/docs/reference/commands.md @@ -68,7 +68,7 @@ $ nemoclaw onboard :::{warning} For NemoClaw-managed environments, use `nemoclaw onboard` when you need to create or recreate the OpenShell gateway or sandbox. -NemoClaw derives the gateway image from the installed `openshell` CLI version, so `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` can replace the runtime with a different OpenShell release than NemoClaw 0.1.0 supports. +Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you intend to manage OpenShell separately and then rerun `nemoclaw onboard`. ::: The wizard prompts for a provider first, then collects the provider credential if needed. diff --git a/test/onboard.test.js b/test/onboard.test.js index abe4bfbae..62f37d6af 100644 --- a/test/onboard.test.js +++ b/test/onboard.test.js @@ -13,13 +13,11 @@ import { buildSandboxConfigSyncScript, classifySandboxCreateFailure, compactText, - formatSupportedOpenshellVersions, formatEnvAssignment, getNavigationChoice, getGatewayReuseState, getPortConflictServiceHints, getFutureShellPathHint, - getOpenshellCompatibilityNotice, getSandboxInferenceConfig, getInstalledOpenshellVersion, getRequestedModelHint, @@ -29,7 +27,6 @@ import { getResumeSandboxConflict, getSandboxStateFromOutputs, getStableGatewayImageRef, - getSupportedOpenshellVersions, isGatewayHealthy, classifyValidationFailure, isLoopbackHostname, @@ -46,78 +43,6 @@ import { import { stageOptimizedSandboxBuildContext } from "../bin/lib/sandbox-build-context"; import { buildWebSearchDockerConfig } from "../dist/lib/web-search"; -const ROOT = path.resolve(import.meta.dirname, ".."); - -function runGatewayReuseWithMockOpenshell(versionOutput) { - const mockDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-openshell-mock-")); - const openshellPath = path.join(mockDir, "openshell"); - const onboardPath = path.join(ROOT, "bin/lib/onboard.js"); - const childScript = ` -const { startGatewayForRecovery } = require(${JSON.stringify(onboardPath)}); -Promise.resolve(startGatewayForRecovery(false)) - .then(() => process.exit(0)) - .catch((error) => { - console.error(error && error.stack ? error.stack : String(error)); - process.exit(1); - }); -`; - - fs.writeFileSync( - openshellPath, - `#!/bin/sh -set -eu - -if [ "\${1:-}" = "-V" ]; then - printf '%s\\n' ${JSON.stringify(versionOutput)} - exit 0 -fi - -if [ "\${1:-}" = "status" ]; then - cat <<'EOF' -Gateway status: Connected -Gateway: nemoclaw -EOF - exit 0 -fi - -if [ "\${1:-}" = "gateway" ] && [ "\${2:-}" = "info" ]; then - cat <<'EOF' -Gateway Info - - Gateway: nemoclaw - Gateway endpoint: https://127.0.0.1:8080 -EOF - exit 0 -fi - -if [ "\${1:-}" = "gateway" ] && [ "\${2:-}" = "select" ]; then - exit 0 -fi - -printf 'unexpected openshell args: %s\\n' "$*" >&2 -exit 1 -`, - { mode: 0o755 }, - ); - - try { - const envPath = process.env.PATH ? `${mockDir}:${process.env.PATH}` : mockDir; - const result = spawnSync(process.execPath, ["-e", childScript], { - cwd: ROOT, - env: { - ...process.env, - NO_COLOR: "1", - PATH: envPath, - }, - encoding: "utf8", - }); - expect(result.status).toBe(0); - return result; - } finally { - fs.rmSync(mockDir, { recursive: true, force: true }); - } -} - describe("onboard helpers", () => { it("classifies sandbox create timeout failures and tracks upload progress", () => { expect( @@ -389,62 +314,6 @@ describe("onboard helpers", () => { expect(getStableGatewayImageRef("bogus")).toBe(null); }); - it("tracks the current NemoClaw version in the compatibility map", () => { - const { version } = JSON.parse(fs.readFileSync(path.join(ROOT, "package.json"), "utf8")); - expect(getSupportedOpenshellVersions(version).length).toBeGreaterThan(0); - expect(getSupportedOpenshellVersions(`${version}-beta.1`)).toEqual( - getSupportedOpenshellVersions(version), - ); - }); - - it("formats supported OpenShell versions with correct grammar", () => { - expect(formatSupportedOpenshellVersions(["0.0.7"])).toBe("0.0.7"); - expect(formatSupportedOpenshellVersions(["0.0.7", "0.0.8"])).toBe("0.0.7 or 0.0.8"); - expect(formatSupportedOpenshellVersions(["0.0.7", "0.0.8", "0.0.9"])).toBe( - "0.0.7, 0.0.8, or 0.0.9", - ); - }); - - it("prints a concise compatibility note when the gateway version is supported", () => { - expect(getOpenshellCompatibilityNotice("0.0.7")).toEqual([ - " OpenShell compatibility: OpenShell 0.0.7 is supported for NemoClaw 0.1.0. Use `nemoclaw onboard` to recreate NemoClaw-managed sandboxes, and avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly.", - ]); - }); - - it("warns when the installed OpenShell version cannot be detected", () => { - expect(getOpenshellCompatibilityNotice()).toEqual([ - " OpenShell compatibility warning: Could not detect the installed openshell CLI version.", - " Verify `openshell -V` works, then rerun `nemoclaw onboard` before creating or reusing NemoClaw-managed sandboxes.", - " Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly until the compatibility check succeeds.", - ]); - }); - - it("warns when the installed OpenShell version falls outside the supported baseline", () => { - expect(getOpenshellCompatibilityNotice("0.0.8")).toEqual([ - " OpenShell compatibility warning: NemoClaw 0.1.0 supports OpenShell 0.0.7, but found 0.0.8.", - " Install OpenShell 0.0.7, then rerun `nemoclaw onboard` before creating or reusing NemoClaw-managed sandboxes.", - " Avoid `openshell self-update`, `npm update -g openshell`, `openshell gateway start --recreate`, or `openshell sandbox create` directly unless you are also updating the NemoClaw compatibility baseline.", - ]); - }); - - it("prints compatibility guidance before reusing a healthy gateway", () => { - const result = runGatewayReuseWithMockOpenshell("openshell 0.0.7"); - expect(result.stderr).toBe(""); - - const noticeIndex = result.stdout.indexOf("OpenShell compatibility:"); - const reuseIndex = result.stdout.indexOf("✓ Reusing existing gateway"); - expect(noticeIndex).toBeGreaterThanOrEqual(0); - expect(reuseIndex).toBeGreaterThanOrEqual(0); - expect(noticeIndex).toBeLessThan(reuseIndex); - }); - - it("sends compatibility warnings to stderr for unsupported OpenShell versions", () => { - const result = runGatewayReuseWithMockOpenshell("openshell 0.0.8"); - expect(result.stdout).toContain("✓ Reusing existing gateway"); - expect(result.stderr).toContain("OpenShell compatibility warning:"); - expect(result.stderr).toContain("npm update -g openshell"); - }); - it("treats the gateway as healthy only when nemoclaw is running and connected", () => { expect( isGatewayHealthy(