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
+7 -2
View File
@@ -65,13 +65,18 @@ export function createSkillTool(options: SkillLoadOptions): ToolDefinition {
if (!force && cachedDescription) return cachedDescription
const skills = await getSkills()
const commands = getCommands()
const skillInfos = skills.map(loadedSkillToInfo)
// Exclude agent-restricted skills from the description: they must not be
// visible to agents that are not their designated owner. The execute-time
// check already enforces the restriction at call time.
const publicSkills = skills.filter((s) => !s.definition.agent)
const skillInfos = publicSkills.map(loadedSkillToInfo)
cachedDescription = formatCombinedDescription(skillInfos, commands)
return cachedDescription
}
if (options.skills !== undefined) {
const skillInfos = options.skills.map(loadedSkillToInfo)
const publicSkills = options.skills.filter((s) => !s.definition.agent)
const skillInfos = publicSkills.map(loadedSkillToInfo)
const commandsForDescription = options.commands ?? []
let needsAsyncRefresh = false
@@ -636,6 +636,50 @@ describe("skill tool - dynamic discovery", () => {
expect(result).not.toContain("SHOULD_BE_OVERRIDDEN")
})
})
describe("skill tool - agent-restricted skill visibility in description", () => {
it("excludes agent-restricted skill from description <available_items>", () => {
// given: a skill restricted to oracle, and a public skill
const loadedSkills = [
createMockSkill("public-skill"),
createMockSkill("oracle-only-skill", { agent: "oracle" }),
]
// when: tool is created with these skills (as tool-registry would inject them)
const tool = createSkillTool({ skills: loadedSkills })
// then: oracle-only skill must NOT appear in the description
expect(tool.description).toContain("public-skill")
expect(tool.description).not.toContain("oracle-only-skill")
})
it("includes public skill (no agent field) in description regardless of context", () => {
// given
const loadedSkills = [createMockSkill("public-skill")]
// when
const tool = createSkillTool({ skills: loadedSkills })
// then
expect(tool.description).toContain("public-skill")
})
it("execute still works for agent-restricted skill when called with correct agent context", async () => {
// given: tool created WITHOUT the restricted skill in description list,
// but the full skill list is available for execute via getSkills()
// (simulating what tool-registry does: description uses filtered list,
// but execute discovers from disk / full list)
const restrictedSkill = createMockSkill("oracle-only-skill", { agent: "oracle" })
const tool = createSkillTool({ skills: [restrictedSkill] })
const oracleContext = { ...mockContext, agent: "oracle" }
// when: oracle agent explicitly calls the skill
const result = await tool.execute({ name: "oracle-only-skill" }, oracleContext)
// then: execution succeeds
expect(result).toContain("oracle-only-skill")
})
})
describe("skill tool - dynamic description cache invalidation", () => {
it("keeps description available after execute misses a skill", async () => {
// given