Skip to content

Commit d66de2c

Browse files
committed
fix(policy): filter policy-add presets by agent
1 parent 6da5868 commit d66de2c

2 files changed

Lines changed: 56 additions & 4 deletions

File tree

src/lib/actions/sandbox/policy-channel.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { hashCredential } from "../../security/credential-hash";
1414
const { isNonInteractive } = require("../../onboard") as { isNonInteractive: () => boolean };
1515
const onboardProviders = require("../../onboard/providers");
1616

17+
import { filterSetupPolicyPresetsForAgent } from "../../onboard/agent-policy-presets";
1718
import * as policies from "../../policy";
1819

1920
// Lazy-required: keeps qrcode-terminal + the iLink HTTP client out of the
@@ -118,7 +119,8 @@ export async function addSandboxPolicy(
118119
return;
119120
}
120121

121-
const allPresets = policies.listPresets();
122+
const sandboxAgent = registry.getSandbox(sandboxName)?.agent ?? null;
123+
const allPresets = filterSetupPolicyPresetsForAgent(policies.listPresets(), sandboxAgent);
122124
const applied = policies.getAppliedPresets(sandboxName);
123125

124126
let answer = null;

test/policies.test.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ type PolicyCall = {
3333
sandboxName?: string;
3434
presetName?: string;
3535
path?: string;
36+
presets?: string[];
3637
};
3738

3839
type AppliedOptions = {
@@ -63,6 +64,7 @@ function runPolicyAdd(
6364
extraArgs: string[] = [],
6465
envOverrides: Record<string, string | undefined> = {},
6566
presetName: string = "pypi",
67+
agent: string | null = null,
6668
) {
6769
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-policy-add-"));
6870
const scriptPath = path.join(tmpDir, "policy-add-check.js");
@@ -71,19 +73,25 @@ const registry = require(${REGISTRY_PATH});
7173
const policies = require(${POLICIES_PATH});
7274
const credentials = require(${CREDENTIALS_PATH});
7375
const calls = [];
74-
policies.selectFromList = async () => ${JSON.stringify(presetName)};
76+
policies.selectFromList = async (items) => {
77+
calls.push({ type: "select", presets: items.map((item) => item.name) });
78+
return ${JSON.stringify(presetName)};
79+
};
7580
policies.loadPreset = () => "network_policies:\n example:\n host: example.com\n";
7681
policies.getPresetEndpoints = () => ["example.com"];
7782
credentials.prompt = async (message) => {
7883
calls.push({ type: "prompt", message });
7984
return ${JSON.stringify(confirmAnswer)};
8085
};
81-
registry.getSandbox = (name) => (name === "test-sandbox" ? { name } : null);
82-
registry.listSandboxes = () => ({ sandboxes: [{ name: "test-sandbox" }] });
86+
registry.getSandbox = (name) => (name === "test-sandbox" ? { name, agent: ${JSON.stringify(agent)} } : null);
87+
registry.listSandboxes = () => ({ sandboxes: [{ name: "test-sandbox", agent: ${JSON.stringify(agent)} }] });
8388
policies.listPresets = () => [
8489
{ name: "npm", description: "npm and Yarn registry access" },
8590
{ name: "pypi", description: "Python Package Index (PyPI) access" },
8691
{ name: "discord", description: "Discord API, gateway, and CDN access" },
92+
{ name: "openclaw-pricing", description: "OpenClaw pricing lookup" },
93+
{ name: "nous-web", description: "Nous Portal managed web search and crawl gateway" },
94+
{ name: "nous-code", description: "Nous Portal managed sandboxed code execution gateway" },
8795
];
8896
policies.getAppliedPresets = () => [];
8997
policies.applyPreset = (sandboxName, presetName) => {
@@ -2387,6 +2395,48 @@ selectForRemoval(items, options)
23872395
);
23882396
});
23892397

2398+
it("filters Hermes-only presets from the OpenClaw policy-add picker", () => {
2399+
const result = runPolicyAdd("y", [], {}, "pypi", "openclaw");
2400+
2401+
expect(result.status).toBe(0);
2402+
const calls = JSON.parse(result.stdout.split("__CALLS__")[1].trim()) as PolicyCall[];
2403+
const selectCall = calls.find((call) => call.type === "select");
2404+
expect(selectCall?.presets).toEqual(
2405+
expect.arrayContaining(["npm", "pypi", "discord", "openclaw-pricing"]),
2406+
);
2407+
expect(selectCall?.presets).not.toContain("nous-web");
2408+
expect(selectCall?.presets).not.toContain("nous-code");
2409+
});
2410+
2411+
it("rejects Hermes-only preset names for OpenClaw policy-add", () => {
2412+
const result = runPolicyAdd("y", ["nous-web", "--yes"], {}, "pypi", "openclaw");
2413+
2414+
expect(result.status).not.toBe(0);
2415+
expect(result.stderr).toMatch(/Unknown preset 'nous-web'/);
2416+
expect(result.stderr).toMatch(/Valid presets: npm, pypi, discord, openclaw-pricing/);
2417+
expect(result.stderr).not.toMatch(/nous-code/);
2418+
});
2419+
2420+
it("filters OpenClaw-only presets from the Hermes policy-add picker", () => {
2421+
const result = runPolicyAdd("y", [], {}, "pypi", "hermes");
2422+
2423+
expect(result.status).toBe(0);
2424+
const calls = JSON.parse(result.stdout.split("__CALLS__")[1].trim()) as PolicyCall[];
2425+
const selectCall = calls.find((call) => call.type === "select");
2426+
expect(selectCall?.presets).toEqual(
2427+
expect.arrayContaining(["npm", "pypi", "discord", "nous-web", "nous-code"]),
2428+
);
2429+
expect(selectCall?.presets).not.toContain("openclaw-pricing");
2430+
});
2431+
2432+
it("rejects OpenClaw-only preset names for Hermes policy-add", () => {
2433+
const result = runPolicyAdd("y", ["openclaw-pricing", "--yes"], {}, "pypi", "hermes");
2434+
2435+
expect(result.status).not.toBe(0);
2436+
expect(result.stderr).toMatch(/Unknown preset 'openclaw-pricing'/);
2437+
expect(result.stderr).toMatch(/Valid presets: npm, pypi, discord, nous-web, nous-code/);
2438+
});
2439+
23902440
it("warns the user that the telegram preset alone does not enable Telegram messaging", () => {
23912441
const result = runPolicyAdd("y", [], {}, "telegram");
23922442

0 commit comments

Comments
 (0)