diff --git a/nemoclaw/src/index.ts b/nemoclaw/src/index.ts index 4d62108b2c..37def202b6 100644 --- a/nemoclaw/src/index.ts +++ b/nemoclaw/src/index.ts @@ -218,7 +218,8 @@ function activeModelEntries( onboardCfg: ReturnType, fallbackModel = "", ): ModelProviderEntry[] { - const activeModel = onboardCfg?.model ?? fallbackModel; + // Prefer fallbackModel (live gateway model) over the potentially stale onboard config (#2608). + const activeModel = fallbackModel || onboardCfg?.model || ""; if (!activeModel) { return [ { @@ -330,31 +331,28 @@ export default function register(api: OpenClawPluginApi): void { handler: (ctx) => handleSlashCommand(ctx, api), }); - // 2. Register nvidia-nim provider — use onboard config if available + // 2. Register nvidia-nim provider — always probe the live gateway inference + // state so the TUI footer reflects the current model after a runtime + // `openshell inference set` (#2608). const onboardCfg = loadOnboardConfig(); + const probed = probeOpenShellInference(); - // Prefer onboard config; fall back to live OpenShell inference state when - // the config file is unavailable (e.g. inside the sandbox). Only resort to - // hardcoded defaults if both lookups fail. let bannerEndpoint = onboardCfg ? describeOnboardEndpoint(onboardCfg) : ""; let bannerProvider = onboardCfg ? describeOnboardProvider(onboardCfg) : ""; - let bannerModel = onboardCfg?.model ?? ""; - let probedModel = ""; - - if (!bannerEndpoint || !bannerProvider || !bannerModel) { - const probed = probeOpenShellInference(); - if (!bannerEndpoint) bannerEndpoint = probed.endpoint; - if (!bannerProvider) bannerProvider = probed.provider; - if (!bannerModel) bannerModel = probed.model; - probedModel = probed.model; - } + // Prefer the live gateway model over the stale onboard config model. + let bannerModel = probed.model || onboardCfg?.model || ""; + + if (!bannerEndpoint) bannerEndpoint = probed.endpoint; + if (!bannerProvider) bannerProvider = probed.provider; if (!bannerEndpoint) bannerEndpoint = "build.nvidia.com"; if (!bannerProvider) bannerProvider = "NVIDIA Endpoints"; if (!bannerModel) bannerModel = "nvidia/nemotron-3-super-120b-a12b"; const providerCredentialEnv = onboardCfg?.credentialEnv ?? "NVIDIA_API_KEY"; - api.registerProvider(registeredProviderForConfig(onboardCfg, providerCredentialEnv, probedModel)); + api.registerProvider( + registeredProviderForConfig(onboardCfg, providerCredentialEnv, probed.model), + ); // 3. Register before_tool_call hook to block secrets in memory writes (#1233) // NOTE: This relies on OpenClaw's before_tool_call plugin hook contract diff --git a/nemoclaw/src/register.test.ts b/nemoclaw/src/register.test.ts index b0c5e864a4..9460e6f7e1 100644 --- a/nemoclaw/src/register.test.ts +++ b/nemoclaw/src/register.test.ts @@ -113,6 +113,38 @@ describe("plugin registration", () => { expect(logLines.some((line) => line.includes("Model: llama3.2:latest"))).toBe(true); }); + it("prefers live gateway model over stale onboard config model after runtime switch (#2608)", () => { + mockedLoadOnboardConfig.mockReturnValue({ + endpointType: "build", + endpointUrl: "https://api.build.nvidia.com/v1", + ncpPartner: null, + model: "nvidia/nemotron-3-super-120b-a12b", + profile: "default", + credentialEnv: "NVIDIA_API_KEY", + onboardedAt: "2026-03-01T00:00:00.000Z", + }); + mockedExecFileSync.mockReturnValue( + JSON.stringify({ + provider: "NVIDIA", + endpoint: "https://api.build.nvidia.com/v1", + model: "nvidia/llama-3.3-nemotron-super-49b-v1.5", + }), + ); + + const api = createMockApi(); + register(api); + + const providerArg = vi.mocked(api.registerProvider).mock.calls[0][0]; + expect(providerArg.models?.chat).toEqual([ + expect.objectContaining({ id: "inference/nvidia/llama-3.3-nemotron-super-49b-v1.5" }), + ]); + + const logLines = vi.mocked(api.logger.info).mock.calls.map(([message]) => message); + expect( + logLines.some((line) => line.includes("Model: nvidia/llama-3.3-nemotron-super-49b-v1.5")), + ).toBe(true); + }); + it("does not treat the provider name as a fallback endpoint", () => { mockedExecFileSync.mockReturnValue( JSON.stringify({