Skip to content
Draft
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
29 changes: 25 additions & 4 deletions agents/pi/generate-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
NVIDIA_HOSTED_NATIVE_ENDPOINT,
} from "../../src/lib/inference/native-nvidia/contract.ts";

import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";

Expand Down Expand Up @@ -215,9 +215,30 @@ function main(): void {

const configPath = join(configDir, "models.json");
const config = buildConfig(settings);
writeFileSync(configPath, config.text, { mode: 0o600 });
chmodSync(configPath, 0o600);
syncManagedDefaults(configDir, config.model, config.nativeLocal);
let previousCatalog: Buffer | null;
try {
previousCatalog = readFileSync(configPath);
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
previousCatalog = null;
}
try {
writeFileSync(configPath, config.text, { mode: 0o600 });
chmodSync(configPath, 0o600);
syncManagedDefaults(configDir, config.model, config.nativeLocal);
} catch (error) {
try {
if (previousCatalog === null) {
rmSync(configPath, { force: true });
} else {
writeFileSync(configPath, previousCatalog, { mode: 0o600 });
chmodSync(configPath, 0o600);
}
} catch (rollbackError) {
throw new AggregateError([error, rollbackError], "Pi catalog rollback failed.");
}
throw error;
}

console.log(`[config] Wrote ${configPath} (model=${config.model}, base_url=${config.baseUrl})`);
}
Expand Down
8 changes: 4 additions & 4 deletions ci/pi-agent-qualification-v1-linux-amd64.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"agent": "pi",
"platform": "linux/amd64",
"image": "ghcr.io/nvidia/nemoclaw/pi-sandbox",
"digest": "sha256:0ef30351916295b822e16401c4d61fc032ad858c3d2f6f20152ff43e7ee0f6f8",
"reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:0ef30351916295b822e16401c4d61fc032ad858c3d2f6f20152ff43e7ee0f6f8",
"digest": "sha256:35683a18dff9233b4465f6de83f7749f6bcd02a284765fc77a822d7188b51a38",
"reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:35683a18dff9233b4465f6de83f7749f6bcd02a284765fc77a822d7188b51a38",
"source": {
"repository": "NVIDIA/NemoClaw",
"revision": "2be64a15ee2e3afd769b7a5dacccab893eba3aed",
"revision": "f4bc4efd9e29961624fc82bc9866862af98bb5d2",
"release": "v0.1.0",
"cohort": "ghrun-38941037068-1"
"cohort": "ghrun-38991416051-1"
},
"startupProfileContractVersion": 1,
"capabilityContractVersion": 1
Expand Down
8 changes: 4 additions & 4 deletions ci/pi-agent-qualification-v1-linux-arm64.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
"agent": "pi",
"platform": "linux/arm64",
"image": "ghcr.io/nvidia/nemoclaw/pi-sandbox",
"digest": "sha256:4278426016b05e098fa4fc4b22b662e0635773b095a6b8947839b1d386554e79",
"reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:4278426016b05e098fa4fc4b22b662e0635773b095a6b8947839b1d386554e79",
"digest": "sha256:46108cfed56bad0ca341147cd7b7f6f1ae410fe7e50be9db7c19c2883bea8bfb",
"reference": "ghcr.io/nvidia/nemoclaw/pi-sandbox@sha256:46108cfed56bad0ca341147cd7b7f6f1ae410fe7e50be9db7c19c2883bea8bfb",
"source": {
"repository": "NVIDIA/NemoClaw",
"revision": "2be64a15ee2e3afd769b7a5dacccab893eba3aed",
"revision": "f4bc4efd9e29961624fc82bc9866862af98bb5d2",
"release": "v0.1.0",
"cohort": "ghrun-38941037068-1"
"cohort": "ghrun-38991416051-1"
},
"startupProfileContractVersion": 1,
"capabilityContractVersion": 1
Expand Down
4 changes: 2 additions & 2 deletions src/lib/agent/candidate-authority.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export const CANDIDATE_QUALIFICATION_RECEIPT_DIGESTS: Readonly<
Record<CandidateManagedImageAgent, readonly string[]>
> = Object.freeze({
pi: Object.freeze([
"1900249b823ef7ab9a20cf35780e94b378fcd71931f62ec38346d84797164b06",
"7a2555a63e36c8e37ac91d1d0d821a4da43528065129295f390dd02b6a0ae955",
"8ff0bc51fa60d58057cc5505d95261c9833b934fcd55d1f900967a66c028090c",
"0a346869512ea536379c2c2ea58209ddffd7b37654a690ba95085544c14d8ecc",
]),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,51 @@ describe("Pi managed model catalog generation", () => {
try {
expect(status).not.toBe(0);
expect(fs.readFileSync(path.join(home, ".pi/agent/settings.json"), "utf8")).toBe(settings);
expect(fs.existsSync(path.join(home, ".pi/agent/models.json"))).toBe(false);
} finally {
fs.rmSync(home, { recursive: true, force: true });
}
},
);

it("preserves the previous Pi catalog when settings sync fails (#13362)", () => {
const selected = {
NEMOCLAW_MODEL: "previous-model",
NEMOCLAW_UPSTREAM_PROVIDER: "compatible-endpoint",
NEMOCLAW_INFERENCE_BASE_URL: "http://host.openshell.internal:11434/v1",
};
const { home, status, stderr } = generate(selected);
try {
expect(status, stderr).toBe(0);
const configDir = path.join(home, ".pi/agent");
const modelsPath = path.join(configDir, "models.json");
const settingsPath = path.join(configDir, "settings.json");
const previousCatalog = fs.readFileSync(modelsPath);
fs.writeFileSync(settingsPath, "{invalid json");

const failed = spawnSync(
process.execPath,
[path.join(root, "agents/pi/generate-config.ts")],
{
cwd: root,
encoding: "utf8",
env: {
PATH: process.env.PATH ?? "",
HOME: home,
...selected,
NEMOCLAW_MODEL: "next-model",
},
},
);
expect(failed.status).not.toBe(0);
expect(fs.readFileSync(modelsPath)).toEqual(previousCatalog);
expect(fs.statSync(modelsPath).mode & 0o777).toBe(0o600);
expect(fs.readFileSync(settingsPath, "utf8")).toBe("{invalid json");
} finally {
fs.rmSync(home, { recursive: true, force: true });
}
});

it("updates Pi's managed defaults when the native-local model changes and preserves other settings", () => {
const selected = {
NEMOCLAW_MODEL: "selected-local-model",
Expand Down
Loading