diff --git a/docs/ci/local-proof.md b/docs/ci/local-proof.md index 51c9ac31725d..31c685a16e3f 100644 --- a/docs/ci/local-proof.md +++ b/docs/ci/local-proof.md @@ -278,13 +278,12 @@ is not generic compute offload. `.crabbox.yaml` defaults remote proof to `blacksmith-testbox`. Its configured workflow hydrates provider and agent credentials, so untrusted contributor or fork code must use secretless fork CI or sanitized direct AWS Crabbox instead. -The wrapper uses the bundled Crabbox plugin's binary manager. Testbox requires -Crabbox 0.67.0 for task-owned SSH teardown, which prevents persistent SSH masters -from keeping idle Testboxes alive. Other providers and cloud-worker profiles -retain their 0.56.0 minimum, including supported offline configured binaries. -Missing or unsupported binaries use a verified managed 0.67.0 release. Testbox -selection upgrades an older candidate before lease work and refreshes its command -metadata. The original binary stays untouched. Provider readiness and broker authentication still determine +The wrapper uses the bundled Crabbox plugin's binary manager. All providers and +cloud-worker profiles require Crabbox 0.67.0 or newer. This includes task-owned +Testbox SSH teardown, which prevents persistent SSH masters from keeping idle +Testboxes alive. Missing or older binaries use a verified managed 0.67.0 release +before provider discovery or lease work. The original binary stays untouched. +Provider readiness and broker authentication still determine which configured backend can run the proof. The check workflow hydrates its pinned dispatch commit with a depth-1 checkout; the changed gate later reconstructs the exact merge base and synced final tree. diff --git a/docs/gateway/config-cloud-workers.md b/docs/gateway/config-cloud-workers.md index ab082f830355..d8f0cc54c9f2 100644 --- a/docs/gateway/config-cloud-workers.md +++ b/docs/gateway/config-cloud-workers.md @@ -69,7 +69,7 @@ The bundled `crabbox` provider provisions a disposable machine through the local - `settings.class`: optional Crabbox machine class passed to `--class`. Omission leaves selection to Crabbox unless the placement supplies `machineClass`; OpenClaw does not invent a default or hardware size. Explicit `null`, empty or whitespace strings, and nonstring values are invalid. Edit classless profiles through **Settings → Advanced**. - `settings.ttl` and `settings.idleTimeout` (required): positive Go duration strings passed to `--ttl` and `--idle-timeout` as provider-side failsafes. - `settings.warmImage`: prepares a project's committed checkout and node runtime for capture before enrollment, then starts later workers for that project and profile from the image. Without a prepared Git project, capture remains at eligible worker teardown. Pair with `suspendAfter` so suspended sessions can wake warm. Enabled by default when a configured or placement class is known and `setupEnv` is empty or omitted. Without an effective class, omission stays cold. A nonempty `setupEnv` keeps the default cold because forwarded host environment could leave setup-derived credentials in a shared image. Explicit `true` opts in but requires a known effective class before provider commands; explicit `false` always stays cold. The resolved class and original cold/checkpoint choice are recorded before allocation and remain fixed through retries and restart. Images incur provider snapshot storage charges and retain machine-level caches, including pristine Git seeds, alongside whatever `setup` wrote outside scrubbed worker state. Scrubbing has a three-minute timeout. Checkpoint creation waits within Crabbox's native-capture budget plus command, source-lifecycle, and child-settlement allowances; it does not extend the configured lease TTL or idle timeout. An uncertain project capture blocks enrollment on its source but still permits lease cleanup. See [Warm images](/gateway/cloud-workers#warm-images) for refresh, retention, and Doctor migration and recovery. -- `settings.binary`: optional absolute Crabbox executable path. Without it, OpenClaw checks the sibling Crabbox checkout, then executable entries on `PATH`. The plugin requires Crabbox 0.56.0 or newer for every target, including Daytona fixed-ID preparation, replay, and confirmed cleanup. If the selected binary is missing, outdated, or cannot report a supported version, the plugin downloads the supported release into its own versioned directory under `$OPENCLAW_STATE_DIR/tools/crabbox` (by default `~/.openclaw/tools/crabbox`). It verifies the official release checksum and executable version before using the copy. Existing binaries and profile settings are preserved. Later commands reuse the managed installation without another download. Damaged managed installations are replaced automatically; the previous directory is retained beside the replacement with a `.recovery-` suffix for inspection. `openclaw doctor --fix` installs the managed copy ahead of the first worker operation. An installation failure stops the operation before allocation and reports the cause. +- `settings.binary`: optional absolute Crabbox executable path. Without it, OpenClaw checks the sibling Crabbox checkout, then executable entries on `PATH`. The plugin requires Crabbox 0.67.0 or newer for every target, including Daytona fixed-ID preparation, replay, and confirmed cleanup. If the selected binary is missing, outdated, or cannot report a supported version, the plugin downloads the supported release into its own versioned directory under `$OPENCLAW_STATE_DIR/tools/crabbox` (by default `~/.openclaw/tools/crabbox`). It verifies the official release checksum and executable version before using the copy. Existing binaries and profile settings are preserved. Later commands reuse the managed installation without another download. Damaged managed installations are replaced automatically; the previous directory is retained beside the replacement with a `.recovery-` suffix for inspection. `openclaw doctor --fix` installs the managed copy ahead of the first worker operation. An installation failure stops the operation before allocation and reports the cause. - `readyWorkers`: non-negative integer target per eligible local project or repository and profile; defaults to `1`. Set `0` to disable this profile's reserves while keeping warm-image reuse. - `cloudWorkers.preparedPool.maxTotal`: non-negative integer Gateway-wide reserve cap; defaults to `4`. Preparing workers and unconfirmed cleanup count toward both limits. Set `0` to drain unused reserves and stop refill. Reserves incur running-machine charges and expire from successful project demand using the provider's existing idle policy. See [Ready workers](/gateway/cloud-workers/warm-images#ready-workers). diff --git a/docs/reference/test/remote-proof.md b/docs/reference/test/remote-proof.md index 678b1bcc65a8..c85996d4b0e9 100644 --- a/docs/reference/test/remote-proof.md +++ b/docs/reference/test/remote-proof.md @@ -32,7 +32,7 @@ immediately before delegation. Source-only edits can reuse the box while HEAD and preparation inputs remain unchanged; every run syncs the checkout. Older or missing receipts require stopping the owned lease and allocating a fresh one through the wrapper. `OPENCLAW_TESTBOX_ALLOW_STALE` cannot bypass -these checks. Testbox requires Crabbox 0.67.0; other providers retain 0.56.0. +these checks. All providers require Crabbox 0.67.0 or newer. The Testbox workflow registers a separate disposable checkout for native sync. The hydrated execution workspace stays at its original absolute path, so native diff --git a/extensions/crabbox/index.test.ts b/extensions/crabbox/index.test.ts index f4539dc2298b..c2e5886c8f7e 100644 --- a/extensions/crabbox/index.test.ts +++ b/extensions/crabbox/index.test.ts @@ -84,7 +84,7 @@ describe("Crabbox plugin generation lifecycle", () => { beforeEach(() => { vi.spyOn(managedBinary, "ensureManagedCrabboxBinary").mockImplementation(async (params) => ({ binary: params?.binary ?? "crabbox", - version: "0.55.0", + version: "999.0.0", })); }); afterEach(async () => { @@ -355,7 +355,7 @@ describe("Crabbox plugin generation lifecycle", () => { started.resolve(params.signal); await finish.promise; params.signal.throwIfAborted(); - return { binary: params.binary ?? "crabbox", version: "0.55.0" }; + return { binary: params.binary ?? "crabbox", version: "999.0.0" }; }); } const generation = registerCrabboxGeneration(); diff --git a/extensions/crabbox/src/crabbox-managed-binary.test.ts b/extensions/crabbox/src/crabbox-managed-binary.test.ts index 43394a1a4bee..93a914609dc5 100644 --- a/extensions/crabbox/src/crabbox-managed-binary.test.ts +++ b/extensions/crabbox/src/crabbox-managed-binary.test.ts @@ -10,11 +10,14 @@ import { useAutoCleanupTempDirTracker } from "openclaw/plugin-sdk/test-env"; import * as tar from "tar"; import { afterEach, describe, expect, it, vi } from "vitest"; import { ensureManagedCrabboxBinary } from "../cli-runtime-api.js"; -import { probeCrabboxVersion, resolveManagedCrabboxBinaryPath } from "./crabbox-managed-binary.js"; +import { + CRABBOX_MIN_VERSION, + probeCrabboxVersion, + resolveManagedCrabboxBinaryPath, +} from "./crabbox-managed-binary.js"; import type { CrabboxCommandRunner } from "./crabbox-worker-command.js"; const tempDirs = useAutoCleanupTempDirTracker(afterEach); -const managedVersion = "0.67.0"; afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); @@ -29,7 +32,7 @@ const runCommand: CrabboxCommandRunner = async ([binary]) => ({ termination: "exit", }); -async function fixture(version = "0.55.0") { +async function fixture(version = "0.66.0") { const root = tempDirs.make("crabbox-managed-"); const env = { ...process.env, OPENCLAW_STATE_DIR: path.join(root, "state") }; const candidate = path.join(root, "operator-crabbox"); @@ -38,8 +41,8 @@ async function fixture(version = "0.55.0") { const platform = process.platform === "win32" ? "windows" : process.platform; const arch = process.arch === "x64" ? "amd64" : process.arch; const extension = platform === "windows" ? "zip" : "tar.gz"; - const asset = `crabbox_${managedVersion}_${platform}_${arch}.${extension}`; - const files = { [path.basename(binary)]: managedVersion, "companion-helper": "keep me" }; + const asset = `crabbox_${CRABBOX_MIN_VERSION}_${platform}_${arch}.${extension}`; + const files = { [path.basename(binary)]: CRABBOX_MIN_VERSION, "companion-helper": "keep me" }; let archive: Buffer; if (platform === "windows") { const zip = new JSZip(); @@ -73,7 +76,7 @@ async function fixture(version = "0.55.0") { checksums, fetch, options: { binary: candidate, env, runCommand }, - installed: { binary, version: managedVersion }, + installed: { binary, version: CRABBOX_MIN_VERSION }, }; } @@ -83,7 +86,7 @@ describe("managed Crabbox", () => { const binary = path.join(root, "crabbox"); const env = { PATH: root, OPENCLAW_STATE_DIR: path.join(root, "state") }; const command = vi.spyOn(processRuntime, "runCommandWithTimeout").mockResolvedValue({ - stdout: "crabbox 0.67.0", + stdout: `crabbox ${CRABBOX_MIN_VERSION}`, stderr: "", code: 0, signal: null, @@ -93,7 +96,7 @@ describe("managed Crabbox", () => { await expect(ensureManagedCrabboxBinary({ binary, env, cwd: root })).resolves.toEqual({ binary, - version: "0.67.0", + version: CRABBOX_MIN_VERSION, }); expect(command).toHaveBeenCalledExactlyOnceWith( [binary, "--version"], @@ -101,22 +104,19 @@ describe("managed Crabbox", () => { ); }); - it.each(["0.56.0", "0.66.0", "1.0.0"])( - "keeps supported non-Testbox %s offline", - async (version) => { - const test = await fixture(version); - await expect(ensureManagedCrabboxBinary(test.options)).resolves.toEqual({ - binary: test.candidate, - version, - }); - expect(test.fetch).not.toHaveBeenCalled(); - await expect(fs.access(test.env.OPENCLAW_STATE_DIR)).rejects.toMatchObject({ - code: "ENOENT", - }); - }, - ); + it.each([CRABBOX_MIN_VERSION, "999.0.0"])("keeps supported %s offline", async (version) => { + const test = await fixture(version); + await expect(ensureManagedCrabboxBinary(test.options)).resolves.toEqual({ + binary: test.candidate, + version, + }); + expect(test.fetch).not.toHaveBeenCalled(); + await expect(fs.access(test.env.OPENCLAW_STATE_DIR)).rejects.toMatchObject({ + code: "ENOENT", + }); + }); - it("reuses the prior managed cache offline but never for Testbox", async () => { + it("upgrades an older managed cache without changing its files", async () => { const test = await fixture(); const prior = path.join( test.env.OPENCLAW_STATE_DIR, @@ -126,20 +126,16 @@ describe("managed Crabbox", () => { ); await fs.mkdir(path.dirname(prior), { recursive: true }); await fs.writeFile(prior, "0.56.0"); - await expect(ensureManagedCrabboxBinary(test.options)).resolves.toEqual({ - binary: prior, - version: "0.56.0", - }); - expect(test.fetch).not.toHaveBeenCalled(); - await expect( - ensureManagedCrabboxBinary({ ...test.options, minimumVersion: "0.67.0" }), - ).resolves.toEqual(test.installed); - expect(test.fetch).toHaveBeenCalled(); + await expect(ensureManagedCrabboxBinary(test.options)).resolves.toEqual(test.installed); + expect(test.fetch).toHaveBeenCalledTimes(2); expect(await fs.readFile(prior, "utf8")).toBe("0.56.0"); + test.fetch.mockRejectedValue(new Error("offline")); + await expect(ensureManagedCrabboxBinary(test.options)).resolves.toEqual(test.installed); + expect(test.fetch).toHaveBeenCalledTimes(2); }); it("keeps a supported configured binary through startup contention", async () => { - const test = await fixture("0.56.0"); + const test = await fixture(CRABBOX_MIN_VERSION); const started = createDeferred(); const delayedRunner: CrabboxCommandRunner = async (argv, options) => { const result = await runCommand(argv, options); @@ -177,14 +173,16 @@ describe("managed Crabbox", () => { await started.promise; await vi.advanceTimersByTimeAsync(7_000); vi.useRealTimers(); - await expect(result).resolves.toEqual({ value: { binary: test.candidate, version: "0.56.0" } }); + await expect(result).resolves.toEqual({ + value: { binary: test.candidate, version: CRABBOX_MIN_VERSION }, + }); expect(test.fetch).not.toHaveBeenCalled(); await expect(fs.access(test.env.OPENCLAW_STATE_DIR)).rejects.toMatchObject({ code: "ENOENT" }); }); - it("upgrades a candidate without native SSH teardown, preserves its distribution, and reuses it offline", async () => { + it("upgrades an old candidate, preserves its distribution, and reuses it offline", async () => { const test = await fixture("0.66.0"); - const params = { ...test.options, minimumVersion: "0.67.0" as const }; + const params = test.options; await expect(ensureManagedCrabboxBinary(params)).resolves.toEqual(test.installed); expect(await fs.readFile(test.candidate, "utf8")).toBe("0.66.0"); expect( @@ -225,7 +223,7 @@ describe("managed Crabbox", () => { }); await expect(ensureManagedCrabboxBinary(params)).rejects.toThrow("checksum mismatch"); expect(await fs.readdir(path.dirname(path.dirname(test.binary)))).toEqual([]); - expect(await fs.readFile(test.candidate, "utf8")).toBe("0.55.0"); + expect(await fs.readFile(test.candidate, "utf8")).toBe("0.66.0"); await expect(ensureManagedCrabboxBinary(params)).resolves.toEqual(test.installed); }); @@ -292,7 +290,7 @@ describe("managed Crabbox", () => { env: test.env, runCommand: staleRunner, }), - ).rejects.toThrow("does not satisfy 0.67.0"); + ).rejects.toThrow(`does not satisfy ${CRABBOX_MIN_VERSION}`); expect(await fs.readdir(path.dirname(path.dirname(test.binary)))).toEqual([]); }); @@ -365,14 +363,14 @@ describe("managed Crabbox", () => { vi.spyOn(fs, "rename").mockImplementation(async (source, destination) => { if (destination === path.dirname(test.binary) && typeof source === "string") { await fs.cp(source, destination, { recursive: true }); - await fs.writeFile(test.binary, "0.67.0"); + await fs.writeFile(test.binary, CRABBOX_MIN_VERSION); throw Object.assign(new Error("destination exists"), { code: "EEXIST" }); } await rename(source, destination); }); await expect(ensureManagedCrabboxBinary(test.options)).resolves.toEqual({ binary: test.binary, - version: "0.67.0", + version: CRABBOX_MIN_VERSION, }); expect( await fs.readFile(path.join(path.dirname(test.binary), "companion-helper"), "utf8"), @@ -390,8 +388,8 @@ describe("managed Crabbox", () => { } await fs.writeFile(path.join(destination, "operator-note"), "preserve this"); await expect(ensureManagedCrabboxBinary(test.options)).resolves.toEqual(test.installed); - expect(await fs.readFile(test.binary, "utf8")).toBe(managedVersion); - expect(await fs.readFile(test.candidate, "utf8")).toBe("0.55.0"); + expect(await fs.readFile(test.binary, "utf8")).toBe(CRABBOX_MIN_VERSION); + expect(await fs.readFile(test.candidate, "utf8")).toBe("0.66.0"); const parent = path.dirname(destination); const entries = await fs.readdir(parent); const backups = entries.filter((name) => @@ -449,7 +447,7 @@ describe("managed Crabbox", () => { }); const params = test.options; await expect(ensureManagedCrabboxBinary(params)).rejects.toThrow("cleanup blocked"); - expect(await fs.readFile(test.binary, "utf8")).toBe(managedVersion); + expect(await fs.readFile(test.binary, "utf8")).toBe(CRABBOX_MIN_VERSION); const parent = path.dirname(destination); const backup = (await fs.readdir(parent)).find((name) => name.startsWith(`${path.basename(destination)}.recovery-`), @@ -589,10 +587,10 @@ describe("managed Crabbox", () => { describe("Crabbox version admission", () => { it.each([ ["0.55.0", "outdated"], - ["0.56.0", "supported"], - ["0.56.0-rc.1", "outdated"], - ["0.66.0", "supported"], - ["0.67.0", "supported"], + ["0.56.0", "outdated"], + ["0.67.0-rc.1", "outdated"], + ["0.66.0", "outdated"], + [CRABBOX_MIN_VERSION, "supported"], ["0.67.0+build.1", "supported"], ["0.68.0-dev", "supported"], ["0.9007199254740993.0", "indeterminate"], @@ -603,11 +601,4 @@ describe("Crabbox version admission", () => { status, }); }); - - it.each(["0.56.0", "0.66.0", "0.67.0-rc.1"])("rejects %s for Testbox", async (version) => { - const test = await fixture(version); - await expect( - probeCrabboxVersion(test.candidate, runCommand, undefined, "0.67.0"), - ).resolves.toMatchObject({ status: "outdated" }); - }); }); diff --git a/extensions/crabbox/src/crabbox-managed-binary.ts b/extensions/crabbox/src/crabbox-managed-binary.ts index 4703afe08280..344dbd7bf29c 100644 --- a/extensions/crabbox/src/crabbox-managed-binary.ts +++ b/extensions/crabbox/src/crabbox-managed-binary.ts @@ -6,11 +6,8 @@ import { runCommandWithTimeout, type SpawnResult } from "openclaw/plugin-sdk/pro import { resolveStateDir } from "openclaw/plugin-sdk/state-paths"; import type { CrabboxCommandRunner } from "./crabbox-worker-command.js"; -export const CRABBOX_MIN_VERSION = "0.56.0"; -// Managed installs include the native Testbox SSH cleanup fix. Existing offline -// cloud workers retain their supported floor; provider callers can require more. -const MANAGED_VERSION = "0.67.0"; -const RELEASE_URL = `https://github.com/openclaw/crabbox/releases/download/v${MANAGED_VERSION}`; +export const CRABBOX_MIN_VERSION = "0.67.0"; +const RELEASE_URL = `https://github.com/openclaw/crabbox/releases/download/v${CRABBOX_MIN_VERSION}`; const MAX_ARCHIVE_BYTES = 128 * 1024 * 1024; // Gateway startup contention can delay an otherwise healthy executable probe. const VERSION_TIMEOUT_MS = 30_000; @@ -28,7 +25,6 @@ export async function probeCrabboxVersion( binary: string, runCommand: CrabboxCommandRunner = runCommandWithTimeout, signal?: AbortSignal, - minimumVersion = CRABBOX_MIN_VERSION, ): Promise { signal?.throwIfAborted(); let result: SpawnResult; @@ -65,7 +61,7 @@ export async function probeCrabboxVersion( if (current.some((part) => !Number.isSafeInteger(part))) { return { status: "indeterminate", reason: "version output was not recognized" }; } - const minimum = minimumVersion.split(".").map(Number); + const minimum = CRABBOX_MIN_VERSION.split(".").map(Number); const difference = current.findIndex((part, index) => part !== minimum[index]); const supported = difference === -1 ? !match[5] : current[difference]! > minimum[difference]!; return { status: supported ? "supported" : "outdated", version: match[1]! }; @@ -81,27 +77,23 @@ function releaseTarget() { return { directory: `${platform}-${arch}`, executable: platform === "windows" ? "crabbox.exe" : "crabbox", - asset: `crabbox_${MANAGED_VERSION}_${platform}_${arch}.${extension}`, + asset: `crabbox_${CRABBOX_MIN_VERSION}_${platform}_${arch}.${extension}`, tar: extension === "tar.gz", }; } -function managedBinaryPath(env: NodeJS.ProcessEnv, version: string): string { +export function resolveManagedCrabboxBinaryPath(env: NodeJS.ProcessEnv = process.env): string { const target = releaseTarget(); return path.join( resolveStateDir(env), "tools", "crabbox", - version, + CRABBOX_MIN_VERSION, target.directory, target.executable, ); } -export function resolveManagedCrabboxBinaryPath(env: NodeJS.ProcessEnv = process.env): string { - return managedBinaryPath(env, MANAGED_VERSION); -} - async function downloadReleaseFile( destination: Awaited>, name: string, @@ -166,19 +158,15 @@ async function probeInstallation( binary: string, runCommand: CrabboxCommandRunner, signal: AbortSignal | undefined, - minimumVersion = MANAGED_VERSION, ): Promise { const stat = await fs.lstat(binary).catch(() => undefined); if (!stat?.isFile()) { return undefined; } - const result = await probeCrabboxVersion(binary, runCommand, signal, minimumVersion); + const result = await probeCrabboxVersion(binary, runCommand, signal); return result.status === "supported" ? { binary, version: result.version } : undefined; } -// Detection and execution share this read-only selection. A release refresh -// must not strand supported offline workers in the previous managed cache; -// Testbox's higher requirement bypasses this default-floor lookup. export async function findManagedCrabboxBinary( params: { env?: NodeJS.ProcessEnv; @@ -189,14 +177,9 @@ export async function findManagedCrabboxBinary( const runCommand = params.runCommand ?? ((argv, options) => runCommandWithTimeout(argv, { ...options, baseEnv: params.env })); - for (const cachedVersion of [MANAGED_VERSION, CRABBOX_MIN_VERSION]) { - const binary = managedBinaryPath(params.env ?? process.env, cachedVersion); - if (await inspectInstallationDirectory(path.dirname(binary))) { - const cached = await probeInstallation(binary, runCommand, params.signal, cachedVersion); - if (cached) { - return cached; - } - } + const binary = resolveManagedCrabboxBinaryPath(params.env); + if (await inspectInstallationDirectory(path.dirname(binary))) { + return probeInstallation(binary, runCommand, params.signal); } return undefined; } @@ -349,7 +332,7 @@ async function installManagedBinary( const stagedBinary = path.join(payload, target.executable); const staged = await probeInstallation(stagedBinary, runCommand, signal); if (!staged) { - throw new Error(`Downloaded Crabbox executable does not satisfy ${MANAGED_VERSION}`); + throw new Error(`Downloaded Crabbox executable does not satisfy ${CRABBOX_MIN_VERSION}`); } return await publishInstallation({ binary, @@ -377,7 +360,6 @@ export async function ensureManagedCrabboxBinary( runCommand?: CrabboxCommandRunner; env?: NodeJS.ProcessEnv; signal?: AbortSignal; - minimumVersion?: typeof CRABBOX_MIN_VERSION | typeof MANAGED_VERSION; } = {}, ): Promise { const { signal } = params; @@ -387,24 +369,16 @@ export async function ensureManagedCrabboxBinary( runCommandWithTimeout(argv, { ...options, baseEnv: params.env, cwd: params.cwd })); const candidate = params.binary ?? "crabbox"; const binary = resolveManagedCrabboxBinaryPath(params.env); - const managedCandidate = path.resolve(params.cwd ?? ".", candidate) === binary; - if (managedCandidate) { + if (path.resolve(params.cwd ?? ".", candidate) === binary) { await inspectInstallationDirectory(path.dirname(binary)); } - const preferred = await probeCrabboxVersion( - candidate, - runCommand, - signal, - managedCandidate ? MANAGED_VERSION : params.minimumVersion, - ); + const preferred = await probeCrabboxVersion(candidate, runCommand, signal); if (preferred.status === "supported") { return { binary: candidate, version: preferred.version }; } - if ((params.minimumVersion ?? CRABBOX_MIN_VERSION) === CRABBOX_MIN_VERSION) { - const cached = await findManagedCrabboxBinary({ env: params.env, runCommand, signal }); - if (cached) { - return cached; - } + const cached = await findManagedCrabboxBinary({ env: params.env, runCommand, signal }); + if (cached) { + return cached; } const { toErrorObject } = await import("openclaw/plugin-sdk/error-runtime"); signal?.throwIfAborted(); diff --git a/extensions/crabbox/src/crabbox-worker-provider.test.ts b/extensions/crabbox/src/crabbox-worker-provider.test.ts index ef51ad35c76a..a246ede29cad 100644 --- a/extensions/crabbox/src/crabbox-worker-provider.test.ts +++ b/extensions/crabbox/src/crabbox-worker-provider.test.ts @@ -44,7 +44,7 @@ import { vi.mock("./crabbox-managed-binary.js", () => ({ ensureManagedCrabboxBinary: vi.fn(async ({ binary }: { binary: string }) => ({ binary, - version: "0.55.0", + version: "999.0.0", })), })); @@ -92,7 +92,7 @@ beforeEach(() => { .mockReset() .mockImplementation(async (params) => ({ binary: params?.binary ?? "crabbox", - version: "0.55.0", + version: "999.0.0", })); // Provider instances share durable state within a replay test, never across test cases. vi.stubEnv("OPENCLAW_STATE_DIR", tempDirs.make("openclaw-crabbox-provider-")); @@ -255,7 +255,7 @@ describe("Crabbox worker provider", () => { const managedBinary = path.resolve(path.sep, "managed", "crabbox"); vi.mocked(ensureManagedCrabboxBinary).mockResolvedValue({ binary: managedBinary, - version: "0.55.0", + version: "999.0.0", }); const runCommand = vi.fn(async (argv) => { if (argv[1] === "providers") { @@ -334,7 +334,7 @@ describe("Crabbox worker provider", () => { controller.abort(); acquisition.resolve({ binary: path.resolve(path.sep, "managed", "crabbox"), - version: "0.55.0", + version: "999.0.0", }); await rejected; diff --git a/extensions/crabbox/src/crabbox-worker-warm-image-maintenance.test.ts b/extensions/crabbox/src/crabbox-worker-warm-image-maintenance.test.ts index d9750e601f40..d583f84de6fb 100644 --- a/extensions/crabbox/src/crabbox-worker-warm-image-maintenance.test.ts +++ b/extensions/crabbox/src/crabbox-worker-warm-image-maintenance.test.ts @@ -46,7 +46,7 @@ describe("Crabbox idle image maintenance", () => { if (params?.binary === "/opt/b/crabbox") { throw new Error("fixture binary acquisition unavailable"); } - return { binary: params?.binary ?? "crabbox", version: "0.55.0" }; + return { binary: params?.binary ?? "crabbox", version: "999.0.0" }; }); const store = openWarmImageStore(); store.register("expired", expiredImage("chk_expired")); diff --git a/extensions/crabbox/src/crabbox-worker-warm-image.test-support.ts b/extensions/crabbox/src/crabbox-worker-warm-image.test-support.ts index 03e24d1af17a..90a16580b620 100644 --- a/extensions/crabbox/src/crabbox-worker-warm-image.test-support.ts +++ b/extensions/crabbox/src/crabbox-worker-warm-image.test-support.ts @@ -71,7 +71,7 @@ export function createWarmProvider( vi.stubEnv("OPENCLAW_STATE_DIR", stateDir); vi.spyOn(managedBinary, "ensureManagedCrabboxBinary").mockImplementation(async (params) => ({ binary: params?.binary ?? "crabbox", - version: "0.55.0", + version: "999.0.0", })); const calls: CommandCall[] = []; const warn = vi.fn(); diff --git a/extensions/crabbox/src/doctor.test.ts b/extensions/crabbox/src/doctor.test.ts index c98b6a833f1b..351c7a28664d 100644 --- a/extensions/crabbox/src/doctor.test.ts +++ b/extensions/crabbox/src/doctor.test.ts @@ -68,7 +68,7 @@ describe("Crabbox worker doctor", () => { it("accepts a supported configured executable without downloading", async () => { const probe = vi .spyOn(managedBinary, "probeCrabboxVersion") - .mockResolvedValue({ status: "supported", version: "0.56.0" }); + .mockResolvedValue({ status: "supported", version: managedBinary.CRABBOX_MIN_VERSION }); const install = vi.spyOn(managedBinary, "ensureManagedCrabboxBinary"); await expect(captureCrabboxDoctorCheck().detect(context())).resolves.toEqual([]); expect(probe).toHaveBeenCalledOnce(); @@ -87,7 +87,7 @@ describe("Crabbox worker doctor", () => { expect.objectContaining({ severity: "warning", target: "worker", - requirement: "Crabbox 0.56.0 or newer", + requirement: `Crabbox ${managedBinary.CRABBOX_MIN_VERSION} or newer`, fixHint: expect.stringContaining("openclaw doctor --fix"), }), ]); @@ -95,8 +95,8 @@ describe("Crabbox worker doctor", () => { }, ); - it.each(["0.56.0", "0.67.0"])( - "detects managed-only %s offline without repair", + it.each(["0.56.0", managedBinary.CRABBOX_MIN_VERSION])( + "detects whether managed-only %s needs an upgrade without downloading", async (version) => { const env = { OPENCLAW_STATE_DIR: tempDirs.make("crabbox-doctor-managed-"), PATH: "" }; const target = path.basename( @@ -125,10 +125,21 @@ describe("Crabbox worker doctor", () => { ctx.cfg.cloudWorkers!.profiles!.worker!.settings = { binary: "/nonexistent/crabbox" }; const check = captureCrabboxDoctorCheck(); const findings = await check.detect(ctx); - expect(findings).toEqual([]); - await expect(check.repair!(ctx, findings)).resolves.toMatchObject({ status: "skipped" }); - await expect(check.detect(ctx)).resolves.toEqual([]); - expect(command).toHaveBeenCalledWith([binary, "--version"], expect.anything()); + if (version === managedBinary.CRABBOX_MIN_VERSION) { + expect(findings).toEqual([]); + await expect(check.repair!(ctx, findings)).resolves.toMatchObject({ status: "skipped" }); + await expect(check.detect(ctx)).resolves.toEqual([]); + expect(command).toHaveBeenCalledWith([binary, "--version"], expect.anything()); + } else { + expect(findings).toEqual([ + expect.objectContaining({ + severity: "warning", + requirement: `Crabbox ${managedBinary.CRABBOX_MIN_VERSION} or newer`, + fixHint: expect.stringContaining("openclaw doctor --fix"), + }), + ]); + expect(command).not.toHaveBeenCalled(); + } expect(install).not.toHaveBeenCalled(); }, ); @@ -152,7 +163,7 @@ describe("Crabbox worker doctor", () => { .spyOn(managedBinary, "ensureManagedCrabboxBinary") .mockImplementation(async ({ binary } = {}) => ({ binary: binary ?? "crabbox", - version: "0.56.0", + version: managedBinary.CRABBOX_MIN_VERSION, })); const check = captureCrabboxDoctorCheck(); const findings = [{ checkId: CRABBOX_CLOUD_WORKER_PROFILE_CHECK_ID }] as never; diff --git a/extensions/qa-lab/src/mantis/crabbox-runtime.test.ts b/extensions/qa-lab/src/mantis/crabbox-runtime.test.ts index 863fa38e1f2a..8914a0a8db97 100644 --- a/extensions/qa-lab/src/mantis/crabbox-runtime.test.ts +++ b/extensions/qa-lab/src/mantis/crabbox-runtime.test.ts @@ -27,8 +27,8 @@ describe("Mantis Crabbox binary admission", () => { await fs.writeFile( executable, process.platform === "win32" - ? '@echo off\r\n> "%CRABBOX_PROBE_CWD_FILE%" echo %CD%\r\necho crabbox 0.56.0\r\n' - : '#!/bin/sh\npwd -P > "$CRABBOX_PROBE_CWD_FILE"\nprintf "crabbox 0.56.0\\n"\n', + ? '@echo off\r\n> "%CRABBOX_PROBE_CWD_FILE%" echo %CD%\r\necho crabbox 999.0.0\r\n' + : '#!/bin/sh\npwd -P > "$CRABBOX_PROBE_CWD_FILE"\nprintf "crabbox 999.0.0\\n"\n', { mode: 0o755 }, ); const download = vi diff --git a/extensions/qa-lab/src/mantis/desktop-browser-smoke.runtime.test.ts b/extensions/qa-lab/src/mantis/desktop-browser-smoke.runtime.test.ts index ceca6e8523fd..0dd2f60dc0f9 100644 --- a/extensions/qa-lab/src/mantis/desktop-browser-smoke.runtime.test.ts +++ b/extensions/qa-lab/src/mantis/desktop-browser-smoke.runtime.test.ts @@ -14,7 +14,7 @@ vi.mock("@openclaw/crabbox-provider/cli-runtime-api.js", async (importOriginal) ...actual, ensureManagedCrabboxBinary: vi.fn(async ({ binary }: { binary: string }) => ({ binary, - version: "0.55.0", + version: "999.0.0", })), }; }); @@ -49,7 +49,7 @@ describe("mantis desktop browser smoke runtime", () => { it("uses the managed binary to lease a desktop, run a browser, copy artifacts, and stop", async () => { vi.mocked(ensureManagedCrabboxBinary).mockResolvedValueOnce({ binary: "/tmp/crabbox", - version: "0.55.0", + version: "999.0.0", }); await fs.mkdir(path.join(repoRoot, "qa-artifacts"), { recursive: true }); await fs.writeFile(path.join(repoRoot, "qa-artifacts", "timeline.html"), "

Mantis

"); diff --git a/extensions/qa-lab/src/mantis/slack-desktop-smoke.artifacts.test.ts b/extensions/qa-lab/src/mantis/slack-desktop-smoke.artifacts.test.ts index c256eee25891..2f04b54a7216 100644 --- a/extensions/qa-lab/src/mantis/slack-desktop-smoke.artifacts.test.ts +++ b/extensions/qa-lab/src/mantis/slack-desktop-smoke.artifacts.test.ts @@ -16,7 +16,7 @@ vi.mock("@openclaw/crabbox-provider/cli-runtime-api.js", async (importOriginal) ...actual, ensureManagedCrabboxBinary: vi.fn(async ({ binary }: { binary: string }) => ({ binary, - version: "0.55.0", + version: "999.0.0", })), }; }); diff --git a/extensions/qa-lab/src/mantis/slack-desktop-smoke.runtime.test.ts b/extensions/qa-lab/src/mantis/slack-desktop-smoke.runtime.test.ts index e62549d5948a..d35225fa1c98 100644 --- a/extensions/qa-lab/src/mantis/slack-desktop-smoke.runtime.test.ts +++ b/extensions/qa-lab/src/mantis/slack-desktop-smoke.runtime.test.ts @@ -16,7 +16,7 @@ vi.mock("@openclaw/crabbox-provider/cli-runtime-api.js", async (importOriginal) ...actual, ensureManagedCrabboxBinary: vi.fn(async ({ binary }: { binary: string }) => ({ binary, - version: "0.55.0", + version: "999.0.0", })), }; }); diff --git a/extensions/qa-lab/src/mantis/visual-task.runtime.test.ts b/extensions/qa-lab/src/mantis/visual-task.runtime.test.ts index c2a9b0aa9c13..0d6f0aecad88 100644 --- a/extensions/qa-lab/src/mantis/visual-task.runtime.test.ts +++ b/extensions/qa-lab/src/mantis/visual-task.runtime.test.ts @@ -12,7 +12,7 @@ vi.mock("@openclaw/crabbox-provider/cli-runtime-api.js", async (importOriginal) ...actual, ensureManagedCrabboxBinary: vi.fn(async ({ binary }: { binary: string }) => ({ binary, - version: "0.55.0", + version: "999.0.0", })), }; }); diff --git a/scripts/crabbox-wrapper.mts b/scripts/crabbox-wrapper.mts index 7a433d1b1176..3ad897aa9afe 100755 --- a/scripts/crabbox-wrapper.mts +++ b/scripts/crabbox-wrapper.mts @@ -33,7 +33,6 @@ import { StringDecoder } from "node:string_decoder"; import { setImmediate as yieldToSignals } from "node:timers/promises"; import { fileURLToPath } from "node:url"; import { isRecord } from "@openclaw/normalization-core/record-coerce"; -import { gte as semverGte } from "semver"; import { ensureManagedCrabboxBinary, findCrabboxBinary, @@ -123,7 +122,7 @@ try { console.error(`[crabbox] ${error instanceof Error ? error.message : String(error)}`); process.exit(2); } -let { binary, version } = cli; +const { binary, version } = cli; const workloadCommand = isWorkloadRoutedCommand(args); const workloadOption = workloadCommand ? extractWrapperValueOption(args, "--workload") : undefined; const userArgStart = commandUserArgStart(args); @@ -3708,22 +3707,18 @@ async function applyRunTransforms( } const helpCommand = workloadCommand ? args.slice(0, userArgStart) : ["run"]; -function cliMetadata() { - const help = probeCrabboxHelp(binary, [...helpCommand, "--help"]); - const providers = parseProvidersFromHelp(help.text); - commandValueOptionsFromHelp = parseCommandValueOptionsFromHelp(help.text); - const displayBinary = binary === "crabbox" ? "crabbox" : relative(repoRoot, binary); +const help = probeCrabboxHelp(binary, [...helpCommand, "--help"]); +const providers = parseProvidersFromHelp(help.text); +commandValueOptionsFromHelp = parseCommandValueOptionsFromHelp(help.text); +const displayBinary = binary === "crabbox" ? "crabbox" : relative(repoRoot, binary); - if (help.status !== 0 || commandValueOptionsFromHelp.size === 0) { - console.error( - `[crabbox] bin=${displayBinary} version=${version} providers=${providers.join(",") || "unknown"}`, - ); - console.error("[crabbox] selected binary failed --help sanity checks"); - process.exit(2); - } - return { help, providers, displayBinary }; +if (help.status !== 0 || commandValueOptionsFromHelp.size === 0) { + console.error( + `[crabbox] bin=${displayBinary} version=${version} providers=${providers.join(",") || "unknown"}`, + ); + console.error("[crabbox] selected binary failed --help sanity checks"); + process.exit(2); } -let { help, providers, displayBinary } = cliMetadata(); // Classify help before removing the wrapper separator or preparing execution. // Only the leaf's option prefix counts; payloads and flag values stay gated. @@ -3754,27 +3749,7 @@ if (args[userArgStart] === "--") { args.splice(userArgStart, 1); } -let providerSelection = selectedProvider(args, providers); -if ( - !providerSelection.error && - canonicalProviderName(providerSelection.provider) === "blacksmith-testbox" && - !semverGte(version, "0.67.0") -) { - // Only Testbox needs the native SSH lifetime repair. Preserve supported - // offline binaries for other providers, and parse against the executable used. - try { - ({ binary, version } = await ensureManagedCrabboxBinary({ - binary, - minimumVersion: "0.67.0", - })); - resolvedCrabboxConfigCache = undefined; - ({ help, providers, displayBinary } = cliMetadata()); - providerSelection = selectedProvider(args, providers); - } catch (error) { - console.error(`[crabbox] ${error instanceof Error ? error.message : String(error)}`); - process.exit(2); - } -} +const providerSelection = selectedProvider(args, providers); if (providerSelection.error) { console.error(`[crabbox] ${providerSelection.error}`); if (providerSelection.readiness) { diff --git a/src/gateway/worker-environments/provider-crabbox-runtime-preflight.test.ts b/src/gateway/worker-environments/provider-crabbox-runtime-preflight.test.ts index b28b373a6147..8480760fae1f 100644 --- a/src/gateway/worker-environments/provider-crabbox-runtime-preflight.test.ts +++ b/src/gateway/worker-environments/provider-crabbox-runtime-preflight.test.ts @@ -93,7 +93,7 @@ describe("Crabbox runtime preflight cleanup", () => { .mockImplementation(async (argv) => { if (argv[1] === "--version") { expect(argv.slice(1)).toEqual(["--version"]); - return commandResult({ stdout: "0.56.0" }); + return commandResult({ stdout: "999.0.0" }); } if (argv[1] === "providers") { expect(argv.slice(1)).toEqual(["providers", "--json"]); @@ -247,7 +247,7 @@ describe("Crabbox runtime preflight cleanup", () => { vi.spyOn(processRuntime, "runCommandWithTimeout").mockImplementation(async (argv) => { if (argv[1] === "--version") { expect(argv.slice(1)).toEqual(["--version"]); - return commandResult({ stdout: "0.56.0" }); + return commandResult({ stdout: "999.0.0" }); } calls.push(argv); if (argv[1] === "providers") { @@ -388,7 +388,7 @@ describe("Crabbox runtime preflight cleanup", () => { .mockImplementation(async (argv) => { if (argv[1] === "--version") { expect(argv.slice(1)).toEqual(["--version"]); - return commandResult({ stdout: "0.56.0" }); + return commandResult({ stdout: "999.0.0" }); } expect(argv.slice(1)).toEqual(["providers", "--json"]); return commandResult({ stdout: "[]" }); diff --git a/test/plugins/crabbox-allocation-authority.gateway.test.ts b/test/plugins/crabbox-allocation-authority.gateway.test.ts index e64f551b6dde..1c0be6a34289 100644 --- a/test/plugins/crabbox-allocation-authority.gateway.test.ts +++ b/test/plugins/crabbox-allocation-authority.gateway.test.ts @@ -87,7 +87,7 @@ describe("Crabbox allocation through Gateway ownership", () => { .mockImplementation(async (argv) => { const value = (flag: string) => argv[argv.indexOf(flag) + 1]!; if (argv[1] === "--version") { - return result("crabbox 0.56.0\n"); + return result("crabbox 999.0.0\n"); } if (argv[1] === "config") { return result(JSON.stringify({ aws: { instanceProfile: "" } })); diff --git a/test/plugins/crabbox-service-replacement.test.ts b/test/plugins/crabbox-service-replacement.test.ts index 1f43e7d65a72..a7bcc9663465 100644 --- a/test/plugins/crabbox-service-replacement.test.ts +++ b/test/plugins/crabbox-service-replacement.test.ts @@ -58,7 +58,7 @@ describe("Crabbox service replacement", () => { return { stdout: argv[1] === "--version" - ? "0.56.0" + ? "999.0.0" : JSON.stringify({ id: "cbx_replacement", providerMetadata: { instanceProfileAttached: false }, diff --git a/test/scripts/crabbox-wrapper.test.ts b/test/scripts/crabbox-wrapper.test.ts index 25ddeda13db8..2ad361cefbf8 100644 --- a/test/scripts/crabbox-wrapper.test.ts +++ b/test/scripts/crabbox-wrapper.test.ts @@ -1598,7 +1598,7 @@ describe("scripts/crabbox-wrapper", () => { env: { GITHUB_PATH: githubPath, OPENCLAW_STATE_DIR: path.join(directory, "state"), - OPENCLAW_FAKE_CRABBOX_VERSION: "crabbox 0.56.0", + OPENCLAW_FAKE_CRABBOX_VERSION: "crabbox 999.0.0", OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog, }, }), @@ -1609,7 +1609,7 @@ describe("scripts/crabbox-wrapper", () => { makeFakeCrabbox(defaultProviderHelp), process.platform === "win32" ? "crabbox.cmd" : "crabbox", ); - expect(JSON.parse(result.stdout)).toEqual({ binary, version: "0.56.0" }); + expect(JSON.parse(result.stdout)).toEqual({ binary, version: "999.0.0" }); expect(readFileSync(githubPath, "utf8")).toBe(`${path.dirname(binary)}\n`); expect(readInvocations(invocationLog)).toEqual([["--version"]]); expect(existsSync(path.join(directory, "state"))).toBe(false); @@ -1628,20 +1628,24 @@ describe("scripts/crabbox-wrapper", () => { expect(result.stderr).toContain("selected=aws"); }); - it.skipIf(process.platform === "win32")( - "upgrades only Testbox and refreshes native metadata", - () => { - const root = invocationLogTempDirs.make("openclaw-testbox-version-"); + it.skipIf(process.platform === "win32").each(["aws", "blacksmith-testbox"])( + "upgrades an old binary before metadata and lease commands for %s", + (provider) => { + const root = invocationLogTempDirs.make("openclaw-crabbox-version-"); const stateDir = path.join(root, "state"); const platform = process.platform; const arch = process.arch === "x64" ? "amd64" : process.arch; const managed = path.join(stateDir, "tools/crabbox/0.67.0", `${platform}-${arch}`, "crabbox"); mkdirSync(path.dirname(managed), { recursive: true }); - // Use the logging Node fake so both metadata probes are observable. + // Keep candidate and managed commands distinguishable at the executable boundary. const fake = path.join(makeFakeCrabbox(defaultProviderHelp), "crabbox-node"); const candidate = path.join(root, "bin", "crabbox"); + const candidateLog = makeInvocationLog(); mkdirSync(path.dirname(candidate)); - writeShellCommand(candidate, `exec node ${shellQuote(fake)} "$@"`); + writeShellCommand( + candidate, + `OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG=${shellQuote(candidateLog)} exec node ${shellQuote(fake)} "$@"`, + ); writeShellCommand( managed, `unset OPENCLAW_FAKE_CRABBOX_VERSION\nexec node ${shellQuote(fake)} "$@"`, @@ -1655,19 +1659,15 @@ describe("scripts/crabbox-wrapper", () => { OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: log, }, }; - const offline = runDefaultWrapper(["run", "--provider", "aws", "--", "true"], options); - expect(offline.status, offline.stderr).toBe(0); - expect(offline.stderr).toContain("version=0.56.0 provider=aws"); - writeFileSync(log, ""); - const testbox = runDefaultWrapper( - ["run", "--provider", "blacksmith-testbox", "--", "true"], - options, - ); - expect(testbox.status, testbox.stderr).toBe(0); - expect(testbox.stderr).toContain("version=0.67.0 provider=blacksmith-testbox"); + const result = runDefaultWrapper(["run", "--provider", provider, "--", "true"], options); + expect(result.status, result.stderr).toBe(0); + expect(result.stderr).toContain(`version=0.67.0 provider=${provider}`); + expect(readInvocations(candidateLog)).toEqual([["--version"]]); const calls = readInvocations(log); - expect(calls.filter((args) => args[0] === "run" && args[1] === "--help")).toHaveLength(2); - expect(calls.filter((args) => args[0] === "config" && args[1] === "show")).toHaveLength(2); + expect(calls[0]).toEqual(["--version"]); + expect(calls.filter((args) => args[0] === "run" && args[1] === "--help")).toHaveLength(1); + expect(calls.filter((args) => args[0] === "config" && args[1] === "show")).toHaveLength(1); + expect(calls.filter((args) => args[0] === "run" && args[1] !== "--help")).toHaveLength(1); }, ); @@ -1851,7 +1851,7 @@ describe("scripts/crabbox-wrapper", () => { { env: { OPENCLAW_FAKE_CRABBOX_INVOCATION_LOG: invocationLog, - OPENCLAW_FAKE_CRABBOX_VERSION: "crabbox 0.56.0", + OPENCLAW_FAKE_CRABBOX_VERSION: "crabbox 999.0.0", OPENCLAW_FAKE_CRABBOX_UNREADY_PROVIDERS: "azure", OPENCLAW_FAKE_CRABBOX_WHOAMI_STATUS: "1", }, @@ -1862,7 +1862,7 @@ describe("scripts/crabbox-wrapper", () => { expect(result.stderr).toContain("selected=aws chain=azure,aws"); const invocations = readInvocations(invocationLog); expect(invocations.filter(([command]) => command === "--version")).toEqual([["--version"]]); - expect(result.stderr).toContain("version=0.56.0"); + expect(result.stderr).toContain("version=999.0.0"); expect(invocations.filter(([command]) => command === "doctor").map((args) => args[2])).toEqual([ "azure", "aws", diff --git a/test/scripts/direct-run-entrypoints.test.ts b/test/scripts/direct-run-entrypoints.test.ts index af5647f17310..1d3d1739ec20 100644 --- a/test/scripts/direct-run-entrypoints.test.ts +++ b/test/scripts/direct-run-entrypoints.test.ts @@ -492,7 +492,7 @@ process.exitCode = child.status ?? 1; it("runs the checked-out Crabbox wrapper through its managed child", async () => { await withShimFixture("scripts/crabbox-wrapper.mjs", async ({ fixtureRoot, runNode }) => { - const fixtureVersion = "0.56.0"; + const fixtureVersion = "999.0.0"; const binDir = path.join(fixtureRoot, "fake bin"); const home = path.join(fixtureRoot, "home"); const state = path.join(fixtureRoot, "state"); diff --git a/ui/src/e2e/new-session-page.cloud-os.e2e.test.ts b/ui/src/e2e/new-session-page.cloud-os.e2e.test.ts index 657eb78ccf22..405173825739 100644 --- a/ui/src/e2e/new-session-page.cloud-os.e2e.test.ts +++ b/ui/src/e2e/new-session-page.cloud-os.e2e.test.ts @@ -23,7 +23,7 @@ suite.define(() => { providerId: "crabbox", machines: [{ id: "standard", label: "Standard", cpu: 32, memoryGb: 64, default: true }], }; - const disabledReason = "Upgrade Crabbox to 0.53.1 or newer, then restart the Gateway."; + const disabledReason = "Upgrade Crabbox to enable this operating system."; const gateway = await installMockGateway(page, { operatorScopes: ["operator.admin", "operator.read", "operator.write"], workspaceGit: true, diff --git a/ui/src/pages/new-session/where-chip.test.ts b/ui/src/pages/new-session/where-chip.test.ts index 5add523d6c04..f276d27f4a98 100644 --- a/ui/src/pages/new-session/where-chip.test.ts +++ b/ui/src/pages/new-session/where-chip.test.ts @@ -609,7 +609,7 @@ describe("Where chip", () => { ); it("hides unavailable operating systems from cloud configuration", () => { - const reason = "Upgrade Crabbox to 0.53.1 or newer, then restart the Gateway."; + const reason = "Upgrade Crabbox to enable this operating system."; const container = renderPicker(true, undefined, { cloudProfileId: "aws", cloudProfiles: readDraftCloudProfiles([