Merge pull request #2827 from z-traveler/feat/per-agent-skill-filtering

feat: filter agent-restricted skills from prompts and tool description
This commit is contained in:
YeonGyu-Kim
2026-05-21 13:07:30 +09:00
committed by GitHub
5 changed files with 143 additions and 10 deletions
+3 -5
View File
@@ -100,8 +100,6 @@ export async function createBuiltinAgents(
description: categories?.[name]?.description ?? CATEGORY_DESCRIPTIONS[name] ?? "General tasks",
}))
const availableSkills = buildAvailableSkills(discoveredSkills, browserProvider, disabledSkills, teamModeEnabled)
// Collect general agents first (for availableAgents), but don't add to result yet
const { pendingAgentConfigs, availableAgents } = collectPendingBuiltinAgents({
agentSources,
@@ -129,7 +127,7 @@ export async function createBuiltinAgents(
systemDefaultModel,
isFirstRunNoCache,
availableAgents,
availableSkills,
availableSkills: buildAvailableSkills(discoveredSkills, browserProvider, disabledSkills, teamModeEnabled, "sisyphus"),
availableCategories,
mergedCategories,
directory,
@@ -148,7 +146,7 @@ export async function createBuiltinAgents(
systemDefaultModel,
isFirstRunNoCache,
availableAgents,
availableSkills,
availableSkills: buildAvailableSkills(discoveredSkills, browserProvider, disabledSkills, teamModeEnabled, "hephaestus"),
availableCategories,
mergedCategories,
directory,
@@ -171,7 +169,7 @@ export async function createBuiltinAgents(
availableModels,
systemDefaultModel,
availableAgents,
availableSkills,
availableSkills: buildAvailableSkills(discoveredSkills, browserProvider, disabledSkills, teamModeEnabled, "atlas"),
mergedCategories,
directory,
userCategories: categories,
@@ -1,9 +1,24 @@
import { describe, expect, test } from "bun:test"
/// <reference types="bun-types" />
import { describe, expect, it, test } from "bun:test"
import type { LoadedSkill } from "../../features/opencode-skill-loader/types"
import { buildAvailableSkills } from "./available-skills"
type DiscoveredSkills = Parameters<typeof buildAvailableSkills>[0]
function makeSkill(name: string, agent?: string): LoadedSkill {
return {
name,
resolvedPath: `/test/skills/${name}`,
definition: {
name,
description: `Skill ${name}`,
template: "",
agent,
},
scope: "user",
}
}
describe("buildAvailableSkills", () => {
test("includes team-mode when team mode is enabled", () => {
// given
@@ -27,3 +42,67 @@ describe("buildAvailableSkills", () => {
expect(availableSkills.some((skill) => skill.name === "team-mode")).toBe(false)
})
})
describe("buildAvailableSkills - agentName filtering", () => {
it("includes agent-restricted skill when agentName is not provided (backward compat)", () => {
// given
const skills = [makeSkill("oracle-only", "oracle")]
// when
const result = buildAvailableSkills(skills, undefined, undefined, undefined, undefined)
// then: no agentName → no filtering, skill is included
expect(result.map((s) => s.name)).toContain("oracle-only")
})
it("includes skill when agentName matches the skill's agent field", () => {
// given
const skills = [makeSkill("sisyphus-only", "sisyphus")]
// when
const result = buildAvailableSkills(skills, undefined, undefined, undefined, "sisyphus")
// then: matching agent → included
expect(result.map((s) => s.name)).toContain("sisyphus-only")
})
it("excludes skill when agentName does not match the skill's agent field", () => {
// given
const skills = [makeSkill("sisyphus-only", "sisyphus")]
// when
const result = buildAvailableSkills(skills, undefined, undefined, undefined, "oracle")
// then: wrong agent → excluded
expect(result.map((s) => s.name)).not.toContain("sisyphus-only")
})
it("includes skill with no agent field regardless of agentName", () => {
// given
const skills = [makeSkill("public-skill")]
// when
const result = buildAvailableSkills(skills, undefined, undefined, undefined, "sisyphus")
// then: no agent restriction → always included
expect(result.map((s) => s.name)).toContain("public-skill")
})
it("filters per-agent while keeping public skills", () => {
// given
const skills = [
makeSkill("public-skill"),
makeSkill("sisyphus-only", "sisyphus"),
makeSkill("oracle-only", "oracle"),
]
// when
const result = buildAvailableSkills(skills, undefined, undefined, undefined, "sisyphus")
// then
const names = result.map((s) => s.name)
expect(names).toContain("public-skill")
expect(names).toContain("sisyphus-only")
expect(names).not.toContain("oracle-only")
})
})
@@ -14,6 +14,7 @@ export function buildAvailableSkills(
browserProvider?: BrowserAutomationProvider,
disabledSkills?: Set<string>,
teamModeEnabled?: boolean,
agentName?: string,
): AvailableSkill[] {
const builtinSkills = createBuiltinSkills({ browserProvider, disabledSkills, teamModeEnabled })
const builtinSkillNames = new Set(builtinSkills.map(s => s.name))
@@ -25,7 +26,13 @@ export function buildAvailableSkills(
}))
const discoveredAvailable: AvailableSkill[] = discoveredSkills
.filter(s => !builtinSkillNames.has(s.name) && !disabledSkills?.has(s.name))
.filter(s => {
if (builtinSkillNames.has(s.name) || disabledSkills?.has(s.name)) return false
// If the skill declares an agent restriction and we know the current agent,
// exclude skills that don't belong to this agent.
if (agentName && s.definition.agent && s.definition.agent !== agentName) return false
return true
})
.map((skill) => ({
name: skill.name,
description: skill.definition.description ?? "",