From 76bf269b392873efdfa949ab82cf3cd8f3f5ce2a Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Fri, 27 Mar 2026 17:42:43 +0900 Subject: [PATCH] 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 --- src/plugin/tool-registry.ts | 2 ++ src/tools/skill/tools.test.ts | 29 +++++++++++++++++++++++++++++ src/tools/skill/tools.ts | 31 +++++++++++++++++++++++++++---- src/tools/skill/types.ts | 6 ++++++ 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/src/plugin/tool-registry.ts b/src/plugin/tool-registry.ts index a50dd3955..6775802b0 100644 --- a/src/plugin/tool-registry.ts +++ b/src/plugin/tool-registry.ts @@ -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 diff --git a/src/tools/skill/tools.test.ts b/src/tools/skill/tools.test.ts index 69839f5e6..1c91610fe 100644 --- a/src/tools/skill/tools.test.ts +++ b/src/tools/skill/tools.test.ts @@ -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") + }) +}) diff --git a/src/tools/skill/tools.ts b/src/tools/skill/tools.ts index e7335c7e9..1d8bebe53 100644 --- a/src/tools/skill/tools.ts +++ b/src/tools/skill/tools.ts @@ -190,10 +190,33 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition const getSkills = async (): Promise => { 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[] => { diff --git a/src/tools/skill/types.ts b/src/tools/skill/types.ts index 579eb69cc..ce54d8238 100644 --- a/src/tools/skill/types.ts +++ b/src/tools/skill/types.ts @@ -37,4 +37,10 @@ export interface SkillLoadOptions { pluginsEnabled?: boolean /** Override plugin enablement from Claude settings by plugin key */ enabledPluginsOverride?: Record + /** 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 + } }