Skip to content
Merged
10 changes: 0 additions & 10 deletions src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2577,16 +2577,6 @@ async function createSandboxWithBaseImageResolution(
process.removeListener("exit", cleanupBuildCtx);
}

if (manageDashboard) {
console.log(" Waiting for NemoClaw dashboard to become ready...");
sandboxReadinessTracing.waitForDashboardReadyWithTrace({
sandboxName,
port: effectiveDashboardPort,
runCaptureOpenshell,
sleep: sleepSeconds,
});
}

if (effectiveSandboxGpuConfig.sandboxGpuEnabled) {
await dockerGpuLocalInference.verifyGpuSandboxLocalInferenceAndCommitAfterReady(
effectiveSandboxGpuConfig,
Expand Down
32 changes: 31 additions & 1 deletion src/lib/onboard/provider-selection-failure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function report(overrides: Partial<Parameters<typeof reportProviderSelectionFail

reportProviderSelectionFailure({
reason: { kind: "requested-provider-unavailable", providerKey: "missing" },
availableProviderKeys: ["build"],
isWindowsHostOllama: false,
rejectWindowsHostOllama: (providerKey, windowsHostSelected) => {
rejected.push({ providerKey, windowsHostSelected });
Expand Down Expand Up @@ -85,14 +86,43 @@ describe("reportProviderSelectionFailure", () => {
]);
});

it("reports unavailable requested providers", () => {
it("tells unavailable requested providers how to recover", () => {
const { errors, rejected } = report({
reason: { kind: "requested-provider-unavailable", providerKey: "missing-provider" },
});

assert.deepEqual(rejected, []);
assert.deepEqual(errors, [
" Requested provider 'missing-provider' is not available in this environment.",
" Re-run without NEMOCLAW_PROVIDER to choose an available provider, or install and start the requested provider before retrying.",
]);
});

it("routes a missing vLLM server to the available managed runtime", () => {
const { errors, rejected } = report({
reason: { kind: "requested-provider-unavailable", providerKey: "vllm" },
availableProviderKeys: ["build", "install-vllm"],
});

assert.deepEqual(rejected, []);
assert.deepEqual(errors, [
" Requested provider 'vllm' is not available in this environment.",
" NEMOCLAW_PROVIDER=vllm requires an already-running local vLLM server, but none was detected.",
" Re-run with NEMOCLAW_PROVIDER=install-vllm to install and start the managed vLLM runtime.",
]);
});

it("does not recommend managed vLLM when the host has no managed runtime option", () => {
const { errors, rejected } = report({
reason: { kind: "requested-provider-unavailable", providerKey: "vllm" },
availableProviderKeys: ["build"],
});

assert.deepEqual(rejected, []);
assert.deepEqual(errors, [
" Requested provider 'vllm' is not available in this environment.",
" NEMOCLAW_PROVIDER=vllm requires an already-running local vLLM server, but none was detected.",
" Start a compatible local vLLM server and retry, or choose another provider.",
]);
});
});
15 changes: 15 additions & 0 deletions src/lib/onboard/provider-selection-failure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { ProviderSelectionFailureReason } from "./provider-selection";

export interface ReportProviderSelectionFailureInput {
reason: ProviderSelectionFailureReason;
availableProviderKeys: readonly string[];
isWindowsHostOllama: boolean;
rejectWindowsHostOllama(providerKey: string, windowsHostSelected: boolean): boolean;
writeError(message: string): void;
Expand Down Expand Up @@ -44,6 +45,20 @@ export function reportProviderSelectionFailure(input: ReportProviderSelectionFai
input.writeError(
` Requested provider '${input.reason.providerKey}' is not available in this environment.`,
);
if (input.reason.providerKey === "vllm") {
input.writeError(
" NEMOCLAW_PROVIDER=vllm requires an already-running local vLLM server, but none was detected.",
);
input.writeError(
input.availableProviderKeys.includes("install-vllm")
? " Re-run with NEMOCLAW_PROVIDER=install-vllm to install and start the managed vLLM runtime."
: " Start a compatible local vLLM server and retry, or choose another provider.",
);
break;
}
input.writeError(
" Re-run without NEMOCLAW_PROVIDER to choose an available provider, or install and start the requested provider before retrying.",
);
break;
}
}
1 change: 1 addition & 0 deletions src/lib/onboard/setup-nim-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,7 @@ export function createSetupNim(
if (providerSelection.kind === "failure") {
reportProviderSelectionFailure({
reason: providerSelection.reason,
availableProviderKeys: options.map((option) => option.key),
isWindowsHostOllama,
rejectWindowsHostOllama,
writeError: deps.error,
Expand Down
26 changes: 26 additions & 0 deletions src/lib/verify-deployment.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,32 @@ describe("verifyDeployment", () => {
expect(sleepCalls).toEqual([10, 10]);
});

it("keeps polling through a cold OpenClaw gateway startup before verification (#8901)", async () => {
let elapsedMs = 0;
const deps = makeDeps({
executeSandboxCommand: (_name: string, script: string) => ({
status: 0,
stdout: script.includes("openclaw --version")
? "2026.5.27"
: script.includes("inference.local")
? "200"
: elapsedMs >= 60_000
? "200"
: "000",
stderr: "",
}),
});

const result = await verifyDeployment("my-sandbox", chain, deps, {
sleep: async (ms: number) => {
elapsedMs += ms;
},
});

expect(result.healthy).toBe(true);
expect(elapsedMs).toBe(60_000);
});

it("retries the dashboard probe and recovers when the port forward comes up late (#3563)", async () => {
let dashboardCalls = 0;
const deps = makeDeps({
Expand Down
6 changes: 4 additions & 2 deletions src/lib/verify-deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export interface VerifyDeploymentOptions {
* returns from createSandbox before the gateway process or the host port
* forward have finished coming up. Each entry below adds one extra attempt
* after the initial try, scheduled at the given delay from the previous
* attempt. The defaults give roughly a 25 s budget per probe before the
* attempt. The defaults give a 90 s budget per probe before the
* wizard surfaces a ✗ marker.
* Tests pass `[]` to disable retry.
*/
Expand All @@ -131,7 +131,9 @@ export interface VerifyDeploymentOptions {
diagnoseCustomOpenClawRuntime?: boolean;
}

const DEFAULT_RETRY_DELAYS_MS: readonly number[] = [1000, 2000, 5000, 7000, 10000];
const DEFAULT_RETRY_DELAYS_MS: readonly number[] = [
1000, 2000, 5000, 7000, 10000, 15000, 20000, 30000,
];
// OpenClaw cron stops its provider preflight after 2.5 seconds. Require a
// response within 2 seconds so onboarding leaves time for client overhead.
const INFERENCE_ROUTE_REACHABILITY_MAX_SECONDS = 2;
Expand Down
2 changes: 2 additions & 0 deletions test/managed-image-publication-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,8 @@ describe("complete managed-image publication workflow", () => {
STAGING_QA_SOURCE_SHA: "250d4abd2d602f864659c06d170699347b5d38bc",
Comment thread
cjagwani marked this conversation as resolved.
STAGING_QA_BASE_IMAGE: "nemoclaw-deepagents-code-base:staging-31396519688",
});
expect(qaBuilder.env).not.toHaveProperty("STAGING_PRODUCER_SHA");
expect(qaBuilder.env).not.toHaveProperty("STAGING_QA_RECORDED_INDEX_DIGEST");
expect(JSON.stringify(qaBuilder)).not.toContain(":latest");
expect(prCheckout.with).toMatchObject({
ref: "${{ github.event.pull_request.head.sha }}",
Expand Down
2 changes: 2 additions & 0 deletions test/onboard-selection-windows-provider-rejection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ describe("onboard Windows-host Ollama provider rejection", () => {
const errors: string[] = [];
reportProviderSelectionFailure({
reason: failedResolution.reason,
availableProviderKeys: options.map((option) => option.key),
isWindowsHostOllama: false,
rejectWindowsHostOllama: () => {
setup();
Expand Down Expand Up @@ -143,6 +144,7 @@ describe("onboard Windows-host Ollama provider rejection", () => {
const errors: string[] = [];
reportProviderSelectionFailure({
reason: failedResolution.reason,
availableProviderKeys: options.map((option) => option.key),
isWindowsHostOllama: false,
rejectWindowsHostOllama: () => {
install();
Expand Down
Loading