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:
YeonGyu-Kim
2026-03-09 11:16:24 +09:00
parent d553bb75a4
commit 1528e46faa
2 changed files with 128 additions and 5 deletions

View 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()
}
})
})

View File

@@ -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 },
)