fix(#2754): include native PluginInput skills in skill() discovery
The skill tool previously only merged disk-discovered skills and preloaded options.skills, so skills registered natively via ctx.skills (for example from config.skills.paths or other plugins) were prompt-visible but not discoverable by skill(). Fix: - pass ctx.skills from tool-registry into createSkillTool - extend SkillLoadOptions with nativeSkills accessor - merge nativeSkills.all() results into getSkills() - add test covering native PluginInput skill discovery
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { ToolDefinition } from "@opencode-ai/plugin"
|
||||
import type { SkillLoadOptions } from "../tools/skill/types"
|
||||
|
||||
import type {
|
||||
AvailableCategory,
|
||||
@@ -112,6 +113,7 @@ export function createToolRegistry(args: {
|
||||
mcpManager: managers.skillMcpManager,
|
||||
getSessionID: getSessionIDForMcp,
|
||||
gitMasterConfig: pluginConfig.git_master,
|
||||
nativeSkills: "skills" in ctx ? (ctx as { skills: SkillLoadOptions["nativeSkills"] }).skills : undefined,
|
||||
})
|
||||
|
||||
// task_system defaults to true since v3.14 — delegation (oracle, subagents) requires it
|
||||
|
||||
@@ -581,3 +581,32 @@ describe("skill tool - dynamic description cache invalidation", () => {
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
describe("skill tool - nativeSkills integration", () => {
|
||||
it("merges native skills exposed by PluginInput.skills.all()", async () => {
|
||||
//#given
|
||||
const tool = createSkillTool({
|
||||
skills: [],
|
||||
nativeSkills: {
|
||||
async all() {
|
||||
return [{
|
||||
name: "external-plugin-skill",
|
||||
description: "Skill from config.skills.paths",
|
||||
location: "/external/skills/external-plugin-skill/SKILL.md",
|
||||
content: "External plugin skill body",
|
||||
}]
|
||||
},
|
||||
async get() { return undefined },
|
||||
async dirs() { return [] },
|
||||
},
|
||||
})
|
||||
|
||||
//#when
|
||||
const result = await tool.execute({ name: "external-plugin-skill" }, mockContext)
|
||||
|
||||
//#then
|
||||
expect(result).toContain("external-plugin-skill")
|
||||
expect(result).toContain("Test skill body content")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -190,10 +190,33 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
|
||||
const getSkills = async (): Promise<LoadedSkill[]> => {
|
||||
clearSkillCache()
|
||||
const discovered = await getAllSkills({disabledSkills: options?.disabledSkills})
|
||||
if (!options.skills) return discovered
|
||||
const discoveredNames = new Set(discovered.map(s => s.name))
|
||||
const extras = options.skills.filter(s => !discoveredNames.has(s.name))
|
||||
return [...discovered, ...extras]
|
||||
const allSkills = !options.skills
|
||||
? discovered
|
||||
: [...discovered, ...options.skills.filter(s => !new Set(discovered.map(d => d.name)).has(s.name))]
|
||||
|
||||
if (options.nativeSkills) {
|
||||
const knownNames = new Set(allSkills.map(s => s.name))
|
||||
try {
|
||||
const nativeAll = await options.nativeSkills.all()
|
||||
for (const native of nativeAll) {
|
||||
if (knownNames.has(native.name)) continue
|
||||
allSkills.push({
|
||||
name: native.name,
|
||||
path: native.location,
|
||||
definition: {
|
||||
name: native.name,
|
||||
description: native.description,
|
||||
template: native.content,
|
||||
},
|
||||
scope: "config",
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// Native skill discovery may not be available
|
||||
}
|
||||
}
|
||||
|
||||
return allSkills
|
||||
}
|
||||
|
||||
const getCommands = (): CommandInfo[] => {
|
||||
|
||||
@@ -37,4 +37,10 @@ export interface SkillLoadOptions {
|
||||
pluginsEnabled?: boolean
|
||||
/** Override plugin enablement from Claude settings by plugin key */
|
||||
enabledPluginsOverride?: Record<string, boolean>
|
||||
/** Native skill accessor from PluginInput for discovering skills registered via config.skills.paths */
|
||||
nativeSkills?: {
|
||||
all(): Promise<{ name: string; description: string; location: string; content: string }[]>
|
||||
get(name: string): Promise<{ name: string; description: string; location: string; content: string } | undefined>
|
||||
dirs(): Promise<string[]>
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user