agents: preserve remote skill sync eligibility

This commit is contained in:
Gustavo Madeira Santana
2026-04-03 14:18:52 -04:00
parent 045010a2a5
commit 5e365a8ec4
4 changed files with 81 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
import fs from "node:fs/promises";
import type { OpenClawConfig } from "../../config/config.js";
import { loadConfig } from "../../config/config.js";
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
import { DEFAULT_BROWSER_EVALUATE_ENABLED } from "../../plugin-sdk/browser-config.js";
import {
ensureBrowserControlAuth,
@@ -57,6 +58,7 @@ async function ensureSandboxWorkspaceLayout(params: {
targetWorkspaceDir: sandboxWorkspaceDir,
config: params.config,
agentId: params.agentId,
eligibility: { remote: getRemoteSkillEligibility() },
});
} catch (error) {
const message = error instanceof Error ? error.message : JSON.stringify(error);

View File

@@ -299,4 +299,45 @@ describe("buildWorkspaceSkillsPrompt", () => {
});
expect(emptyPrompt).toBe("");
});
it("syncs remote-eligible filtered skills into the target workspace", async () => {
const sourceWorkspace = await createCaseDir("source");
const targetWorkspace = await createCaseDir("target");
await writeSkill({
dir: path.join(sourceWorkspace, "skills", "remote-only"),
name: "remote-only",
description: "Sandbox-only bin",
metadata: '{"openclaw":{"requires":{"anyBins":["missingbin","sandboxbin"]}}}',
});
await withEnv({ HOME: sourceWorkspace, PATH: "" }, () =>
syncSkillsToWorkspace({
sourceWorkspaceDir: sourceWorkspace,
targetWorkspaceDir: targetWorkspace,
agentId: "alpha",
config: {
agents: {
defaults: {
skills: ["remote-only"],
},
list: [{ id: "alpha" }],
},
},
eligibility: {
remote: {
platforms: ["linux"],
hasBin: () => false,
hasAnyBin: (bins: string[]) => bins.includes("sandboxbin"),
note: "sandbox",
},
},
bundledSkillsDir: path.join(sourceWorkspace, ".bundled"),
managedSkillsDir: path.join(sourceWorkspace, ".managed"),
}),
);
expect(await pathExists(path.join(targetWorkspace, "skills", "remote-only", "SKILL.md"))).toBe(
true,
);
});
});

View File

@@ -209,6 +209,40 @@ describe("loadWorkspaceSkillEntries", () => {
expect(entries.map((entry) => entry.skill.name)).toEqual(["docs-search"]);
});
it("keeps remote-eligible skills when agent filtering is active", async () => {
const workspaceDir = await createTempWorkspaceDir();
await writeSkill({
dir: path.join(workspaceDir, "skills", "remote-only"),
name: "remote-only",
description: "Needs a remote bin",
metadata: '{"openclaw":{"requires":{"anyBins":["missingbin","sandboxbin"]}}}',
});
const entries = loadWorkspaceSkillEntries(workspaceDir, {
config: {
agents: {
defaults: {
skills: ["remote-only"],
},
list: [{ id: "writer" }],
},
},
agentId: "writer",
eligibility: {
remote: {
platforms: ["linux"],
hasBin: () => false,
hasAnyBin: (bins: string[]) => bins.includes("sandboxbin"),
note: "sandbox",
},
},
managedSkillsDir: path.join(workspaceDir, ".managed"),
bundledSkillsDir: path.join(workspaceDir, ".bundled"),
});
expect(entries.map((entry) => entry.skill.name)).toEqual(["remote-only"]);
});
it.runIf(process.platform !== "win32")(
"skips workspace skill directories that resolve outside the workspace root",
async () => {

View File

@@ -688,6 +688,7 @@ export function loadWorkspaceSkillEntries(
bundledSkillsDir?: string;
skillFilter?: string[];
agentId?: string;
eligibility?: SkillEligibilityContext;
},
): SkillEntry[] {
const entries = loadSkillEntries(workspaceDir, opts);
@@ -695,7 +696,7 @@ export function loadWorkspaceSkillEntries(
if (effectiveSkillFilter === undefined) {
return entries;
}
return filterSkillEntries(entries, opts?.config, effectiveSkillFilter, undefined);
return filterSkillEntries(entries, opts?.config, effectiveSkillFilter, opts?.eligibility);
}
export function loadVisibleWorkspaceSkillEntries(
@@ -759,6 +760,7 @@ export async function syncSkillsToWorkspace(params: {
config?: OpenClawConfig;
skillFilter?: string[];
agentId?: string;
eligibility?: SkillEligibilityContext;
managedSkillsDir?: string;
bundledSkillsDir?: string;
}) {
@@ -775,6 +777,7 @@ export async function syncSkillsToWorkspace(params: {
config: params.config,
skillFilter: params.skillFilter,
agentId: params.agentId,
eligibility: params.eligibility,
managedSkillsDir: params.managedSkillsDir,
bundledSkillsDir: params.bundledSkillsDir,
});