Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions nemoclaw/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,8 @@ function activeModelEntries(
onboardCfg: ReturnType<typeof loadOnboardConfig>,
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 [
{
Expand Down Expand Up @@ -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";
Comment on lines 340 to 350

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the footer banner sourced from the live probe, not just the model.

bannerModel now tracks the probed gateway, but bannerEndpoint and bannerProvider still prefer onboardCfg whenever it exists. After a runtime openshell inference set, the footer can still show stale endpoint/provider metadata, so the UI does not fully reflect the live gateway state this PR is aiming for.

♻️ Suggested fix
-  let bannerEndpoint = onboardCfg ? describeOnboardEndpoint(onboardCfg) : "";
-  let bannerProvider = onboardCfg ? describeOnboardProvider(onboardCfg) : "";
+  let bannerEndpoint = probed.endpoint || (onboardCfg ? describeOnboardEndpoint(onboardCfg) : "");
+  let bannerProvider = probed.provider || (onboardCfg ? describeOnboardProvider(onboardCfg) : "");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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";
let bannerEndpoint = probed.endpoint || (onboardCfg ? describeOnboardEndpoint(onboardCfg) : "");
let bannerProvider = probed.provider || (onboardCfg ? describeOnboardProvider(onboardCfg) : "");
// 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";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@nemoclaw/src/index.ts` around lines 340 - 350, The footer logic currently
prefers onboardCfg for bannerEndpoint/bannerProvider causing stale values;
update initialization so bannerEndpoint and bannerProvider prefer
probed.endpoint and probed.provider first (like bannerModel does), falling back
to describeOnboardEndpoint(onboardCfg)/describeOnboardProvider(onboardCfg) only
if probed values are falsy, and then to the same string defaults
("build.nvidia.com", "NVIDIA Endpoints"); adjust the assignments around
bannerEndpoint, bannerProvider, probed, and onboardCfg accordingly so the live
probe wins.


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
Expand Down
32 changes: 32 additions & 0 deletions nemoclaw/src/register.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
Loading