First Release of Claw3D (#11)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,274 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import type { SkillStatusEntry } from "@/lib/skills/types";
|
||||
import {
|
||||
deriveAgentSkillDisplayState,
|
||||
buildAgentSkillsAllowlistSet,
|
||||
buildSkillMissingDetails,
|
||||
buildSkillReasons,
|
||||
canRemoveSkill,
|
||||
deriveAgentSkillsAccessMode,
|
||||
deriveSkillReadinessState,
|
||||
filterOsCompatibleSkills,
|
||||
groupSkillsBySource,
|
||||
hasInstallableMissingBinary,
|
||||
isBundledBlockedSkill,
|
||||
isSkillOsIncompatible,
|
||||
normalizeAgentSkillsAllowlist,
|
||||
resolvePreferredInstallOption,
|
||||
} from "@/lib/skills/presentation";
|
||||
|
||||
const createSkill = (overrides: Partial<SkillStatusEntry>): SkillStatusEntry => ({
|
||||
name: "skill",
|
||||
description: "",
|
||||
source: "openclaw-workspace",
|
||||
bundled: false,
|
||||
filePath: "/tmp/workspace/skill/SKILL.md",
|
||||
baseDir: "/tmp/workspace/skill",
|
||||
skillKey: "skill",
|
||||
always: false,
|
||||
disabled: false,
|
||||
blockedByAllowlist: false,
|
||||
eligible: true,
|
||||
requirements: { bins: [], anyBins: [], env: [], config: [], os: [] },
|
||||
missing: { bins: [], anyBins: [], env: [], config: [], os: [] },
|
||||
configChecks: [],
|
||||
install: [],
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("skills presentation helpers", () => {
|
||||
it("groups skills by source with stable ordering", () => {
|
||||
const groups = groupSkillsBySource([
|
||||
createSkill({ name: "other", source: "custom-source" }),
|
||||
createSkill({ name: "installed", source: "openclaw-managed" }),
|
||||
createSkill({ name: "workspace", source: "openclaw-workspace" }),
|
||||
createSkill({ name: "bundled", source: "openclaw-bundled", bundled: true }),
|
||||
createSkill({ name: "extra", source: "openclaw-extra" }),
|
||||
]);
|
||||
|
||||
expect(groups.map((group) => group.id)).toEqual([
|
||||
"workspace",
|
||||
"built-in",
|
||||
"installed",
|
||||
"extra",
|
||||
"other",
|
||||
]);
|
||||
expect(groups[0]?.skills.map((skill) => skill.name)).toEqual(["workspace"]);
|
||||
expect(groups[1]?.skills.map((skill) => skill.name)).toEqual(["bundled"]);
|
||||
});
|
||||
|
||||
it("builds explicit missing detail lines", () => {
|
||||
const details = buildSkillMissingDetails(
|
||||
createSkill({
|
||||
eligible: false,
|
||||
missing: {
|
||||
bins: ["playwright"],
|
||||
anyBins: ["chromium", "chrome"],
|
||||
env: ["GITHUB_TOKEN"],
|
||||
config: ["browser.enabled"],
|
||||
os: ["linux"],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(details).toEqual([
|
||||
"Missing tools: playwright",
|
||||
"Missing one-of tools (install any): chromium | chrome",
|
||||
"Missing env vars (set in gateway env): GITHUB_TOKEN",
|
||||
"Missing config values (set in openclaw.json): browser.enabled",
|
||||
"Requires OS: Linux",
|
||||
]);
|
||||
});
|
||||
|
||||
it("builds reasons from policy and missing requirements", () => {
|
||||
const reasons = buildSkillReasons(
|
||||
createSkill({
|
||||
eligible: false,
|
||||
disabled: true,
|
||||
blockedByAllowlist: true,
|
||||
missing: {
|
||||
bins: ["playwright"],
|
||||
anyBins: [],
|
||||
env: [],
|
||||
config: [],
|
||||
os: [],
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
expect(reasons).toEqual(["disabled", "blocked by allowlist", "missing tools"]);
|
||||
});
|
||||
|
||||
it("detects_os_incompatibility_from_missing_os_requirements", () => {
|
||||
expect(
|
||||
isSkillOsIncompatible(
|
||||
createSkill({
|
||||
missing: {
|
||||
bins: [],
|
||||
anyBins: [],
|
||||
env: [],
|
||||
config: [],
|
||||
os: ["darwin"],
|
||||
},
|
||||
})
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
isSkillOsIncompatible(
|
||||
createSkill({
|
||||
missing: {
|
||||
bins: [],
|
||||
anyBins: [],
|
||||
env: [],
|
||||
config: [],
|
||||
os: [" "],
|
||||
},
|
||||
})
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("filters_out_os_incompatible_skills_while_preserving_order", () => {
|
||||
const filtered = filterOsCompatibleSkills([
|
||||
createSkill({ name: "github", missing: { bins: [], anyBins: [], env: [], config: [], os: [] } }),
|
||||
createSkill({
|
||||
name: "apple-notes",
|
||||
missing: { bins: [], anyBins: [], env: [], config: [], os: ["darwin"] },
|
||||
}),
|
||||
createSkill({ name: "slack", missing: { bins: [], anyBins: [], env: [], config: [], os: [] } }),
|
||||
]);
|
||||
|
||||
expect(filtered.map((skill) => skill.name)).toEqual(["github", "slack"]);
|
||||
});
|
||||
|
||||
it("detects bundled blocked skills", () => {
|
||||
expect(
|
||||
isBundledBlockedSkill(
|
||||
createSkill({
|
||||
source: "openclaw-bundled",
|
||||
bundled: true,
|
||||
eligible: false,
|
||||
})
|
||||
)
|
||||
).toBe(true);
|
||||
expect(isBundledBlockedSkill(createSkill({ bundled: true, eligible: true }))).toBe(false);
|
||||
});
|
||||
|
||||
it("detects installable missing binaries including anyBins overlap", () => {
|
||||
const skill = createSkill({
|
||||
eligible: false,
|
||||
missing: {
|
||||
bins: [],
|
||||
anyBins: ["chromium", "chrome"],
|
||||
env: [],
|
||||
config: [],
|
||||
os: [],
|
||||
},
|
||||
install: [
|
||||
{
|
||||
id: "install-chromium",
|
||||
kind: "download",
|
||||
label: "Install chromium",
|
||||
bins: ["chromium"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(hasInstallableMissingBinary(skill)).toBe(true);
|
||||
expect(resolvePreferredInstallOption(skill)?.id).toBe("install-chromium");
|
||||
});
|
||||
|
||||
it("selects_install_option_that_matches_missing_bins", () => {
|
||||
const skill = createSkill({
|
||||
eligible: false,
|
||||
missing: {
|
||||
bins: ["gh"],
|
||||
anyBins: [],
|
||||
env: [],
|
||||
config: [],
|
||||
os: [],
|
||||
},
|
||||
install: [
|
||||
{
|
||||
id: "install-other",
|
||||
kind: "download",
|
||||
label: "Install other tool",
|
||||
bins: ["other"],
|
||||
},
|
||||
{
|
||||
id: "install-gh",
|
||||
kind: "brew",
|
||||
label: "Install gh",
|
||||
bins: ["gh"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(resolvePreferredInstallOption(skill)?.id).toBe("install-gh");
|
||||
});
|
||||
|
||||
it("marks only gateway-managed and workspace skill sources as removable", () => {
|
||||
expect(canRemoveSkill(createSkill({ source: "openclaw-managed" }))).toBe(true);
|
||||
expect(canRemoveSkill(createSkill({ source: "openclaw-workspace" }))).toBe(true);
|
||||
expect(canRemoveSkill(createSkill({ source: "agents-skills-personal" }))).toBe(false);
|
||||
expect(canRemoveSkill(createSkill({ source: "agents-skills-project" }))).toBe(false);
|
||||
expect(canRemoveSkill(createSkill({ source: "openclaw-bundled", bundled: true }))).toBe(
|
||||
false
|
||||
);
|
||||
expect(canRemoveSkill(createSkill({ source: "openclaw-extra" }))).toBe(false);
|
||||
});
|
||||
|
||||
it("derives agent access mode from allowlist shape", () => {
|
||||
expect(deriveAgentSkillsAccessMode(undefined)).toBe("all");
|
||||
expect(deriveAgentSkillsAccessMode([])).toBe("none");
|
||||
expect(deriveAgentSkillsAccessMode([" ", "github"])).toBe("selected");
|
||||
});
|
||||
|
||||
it("normalizes allowlist values and creates a lookup set", () => {
|
||||
expect(normalizeAgentSkillsAllowlist([" github ", "github", "slack", " "])).toEqual([
|
||||
"github",
|
||||
"slack",
|
||||
]);
|
||||
expect(buildAgentSkillsAllowlistSet([" github ", "slack"]).has("github")).toBe(true);
|
||||
expect(buildAgentSkillsAllowlistSet([" github ", "slack"]).has("browser")).toBe(false);
|
||||
});
|
||||
|
||||
it("classifies readiness with disabled and unavailable precedence", () => {
|
||||
expect(deriveSkillReadinessState(createSkill({ disabled: true, eligible: false }))).toBe(
|
||||
"disabled-globally"
|
||||
);
|
||||
expect(
|
||||
deriveSkillReadinessState(
|
||||
createSkill({
|
||||
eligible: false,
|
||||
missing: { bins: [], anyBins: [], env: [], config: [], os: ["darwin"] },
|
||||
})
|
||||
)
|
||||
).toBe("unavailable");
|
||||
expect(
|
||||
deriveSkillReadinessState(
|
||||
createSkill({
|
||||
eligible: false,
|
||||
blockedByAllowlist: true,
|
||||
})
|
||||
)
|
||||
).toBe("unavailable");
|
||||
expect(
|
||||
deriveSkillReadinessState(
|
||||
createSkill({
|
||||
eligible: false,
|
||||
missing: { bins: ["gh"], anyBins: [], env: [], config: [], os: [] },
|
||||
})
|
||||
)
|
||||
).toBe("needs-setup");
|
||||
expect(deriveSkillReadinessState(createSkill({ eligible: true }))).toBe("ready");
|
||||
});
|
||||
|
||||
it("maps readiness into agent display states", () => {
|
||||
expect(deriveAgentSkillDisplayState("ready")).toBe("ready");
|
||||
expect(deriveAgentSkillDisplayState("needs-setup")).toBe("setup-required");
|
||||
expect(deriveAgentSkillDisplayState("disabled-globally")).toBe("setup-required");
|
||||
expect(deriveAgentSkillDisplayState("unavailable")).toBe("not-supported");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user