fix(skill-context): gate discovered browser skills by provider
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
88
src/plugin/skill-context.test.ts
Normal file
88
src/plugin/skill-context.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, spyOn } from "bun:test"
|
||||
import { mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
|
||||
import { OhMyOpenCodeConfigSchema } from "../config"
|
||||
import * as mcpLoader from "../features/claude-code-mcp-loader"
|
||||
import * as skillLoader from "../features/opencode-skill-loader"
|
||||
import { createSkillContext } from "./skill-context"
|
||||
|
||||
describe("createSkillContext", () => {
|
||||
const testDirectory = join(tmpdir(), `skill-context-test-${Date.now()}`)
|
||||
|
||||
beforeEach(() => {
|
||||
mkdirSync(testDirectory, { recursive: true })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(testDirectory, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
it("excludes discovered playwright skill when browser provider is agent-browser", async () => {
|
||||
// given
|
||||
const discoveredPlaywrightDir = join(testDirectory, ".claude", "skills", "playwright")
|
||||
mkdirSync(discoveredPlaywrightDir, { recursive: true })
|
||||
writeFileSync(
|
||||
join(discoveredPlaywrightDir, "SKILL.md"),
|
||||
[
|
||||
"---",
|
||||
"name: playwright",
|
||||
"description: Discovered playwright skill",
|
||||
"---",
|
||||
"Discovered playwright body.",
|
||||
"",
|
||||
].join("\n"),
|
||||
)
|
||||
|
||||
const discoverConfigSourceSkillsSpy = spyOn(
|
||||
skillLoader,
|
||||
"discoverConfigSourceSkills",
|
||||
).mockResolvedValue([])
|
||||
const discoverUserClaudeSkillsSpy = spyOn(
|
||||
skillLoader,
|
||||
"discoverUserClaudeSkills",
|
||||
).mockResolvedValue([])
|
||||
const discoverOpencodeGlobalSkillsSpy = spyOn(
|
||||
skillLoader,
|
||||
"discoverOpencodeGlobalSkills",
|
||||
).mockResolvedValue([])
|
||||
const discoverProjectAgentsSkillsSpy = spyOn(
|
||||
skillLoader,
|
||||
"discoverProjectAgentsSkills",
|
||||
).mockResolvedValue([])
|
||||
const discoverGlobalAgentsSkillsSpy = spyOn(
|
||||
skillLoader,
|
||||
"discoverGlobalAgentsSkills",
|
||||
).mockResolvedValue([])
|
||||
const getSystemMcpServerNamesSpy = spyOn(
|
||||
mcpLoader,
|
||||
"getSystemMcpServerNames",
|
||||
).mockReturnValue(new Set<string>())
|
||||
|
||||
const pluginConfig = OhMyOpenCodeConfigSchema.parse({
|
||||
browser_automation_engine: { provider: "agent-browser" },
|
||||
})
|
||||
|
||||
try {
|
||||
// when
|
||||
const result = await createSkillContext({
|
||||
directory: testDirectory,
|
||||
pluginConfig,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result.browserProvider).toBe("agent-browser")
|
||||
expect(result.mergedSkills.some((skill) => skill.name === "agent-browser")).toBe(true)
|
||||
expect(result.mergedSkills.some((skill) => skill.name === "playwright")).toBe(false)
|
||||
expect(result.availableSkills.some((skill) => skill.name === "playwright")).toBe(false)
|
||||
} finally {
|
||||
discoverConfigSourceSkillsSpy.mockRestore()
|
||||
discoverUserClaudeSkillsSpy.mockRestore()
|
||||
discoverOpencodeGlobalSkillsSpy.mockRestore()
|
||||
discoverProjectAgentsSkillsSpy.mockRestore()
|
||||
discoverGlobalAgentsSkillsSpy.mockRestore()
|
||||
getSystemMcpServerNamesSpy.mockRestore()
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -26,12 +26,27 @@ export type SkillContext = {
|
||||
disabledSkills: Set<string>
|
||||
}
|
||||
|
||||
const PROVIDER_GATED_SKILL_NAMES = new Set(["agent-browser", "playwright"])
|
||||
|
||||
function mapScopeToLocation(scope: SkillScope): AvailableSkill["location"] {
|
||||
if (scope === "user" || scope === "opencode") return "user"
|
||||
if (scope === "project" || scope === "opencode-project") return "project"
|
||||
return "plugin"
|
||||
}
|
||||
|
||||
function filterProviderGatedSkills(
|
||||
skills: LoadedSkill[],
|
||||
browserProvider: BrowserAutomationProvider,
|
||||
): LoadedSkill[] {
|
||||
return skills.filter((skill) => {
|
||||
if (!PROVIDER_GATED_SKILL_NAMES.has(skill.name)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return skill.name === browserProvider
|
||||
})
|
||||
}
|
||||
|
||||
export async function createSkillContext(args: {
|
||||
directory: string
|
||||
pluginConfig: OhMyOpenCodeConfig
|
||||
@@ -71,14 +86,34 @@ export async function createSkillContext(args: {
|
||||
discoverGlobalAgentsSkills(),
|
||||
])
|
||||
|
||||
const filteredConfigSourceSkills = filterProviderGatedSkills(
|
||||
configSourceSkills,
|
||||
browserProvider,
|
||||
)
|
||||
const filteredUserSkills = filterProviderGatedSkills(userSkills, browserProvider)
|
||||
const filteredGlobalSkills = filterProviderGatedSkills(globalSkills, browserProvider)
|
||||
const filteredProjectSkills = filterProviderGatedSkills(projectSkills, browserProvider)
|
||||
const filteredOpencodeProjectSkills = filterProviderGatedSkills(
|
||||
opencodeProjectSkills,
|
||||
browserProvider,
|
||||
)
|
||||
const filteredAgentsProjectSkills = filterProviderGatedSkills(
|
||||
agentsProjectSkills,
|
||||
browserProvider,
|
||||
)
|
||||
const filteredAgentsGlobalSkills = filterProviderGatedSkills(
|
||||
agentsGlobalSkills,
|
||||
browserProvider,
|
||||
)
|
||||
|
||||
const mergedSkills = mergeSkills(
|
||||
builtinSkills,
|
||||
pluginConfig.skills,
|
||||
configSourceSkills,
|
||||
[...userSkills, ...agentsGlobalSkills],
|
||||
globalSkills,
|
||||
[...projectSkills, ...agentsProjectSkills],
|
||||
opencodeProjectSkills,
|
||||
filteredConfigSourceSkills,
|
||||
[...filteredUserSkills, ...filteredAgentsGlobalSkills],
|
||||
filteredGlobalSkills,
|
||||
[...filteredProjectSkills, ...filteredAgentsProjectSkills],
|
||||
filteredOpencodeProjectSkills,
|
||||
{ configDir: directory },
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user