From 6c98677d2243efb2829a396d73f8995d24cb050c Mon Sep 17 00:00:00 2001 From: ismeth Date: Thu, 19 Feb 2026 18:28:31 +0100 Subject: [PATCH] fix(skills): pass directory through skill resolution chain for Desktop mode Skill discovery in the task tool failed to find project-level skills in OpenCode Desktop because: 1. resolveSkillContent() never passed directory to the skill resolution functions, causing them to fall back to process.cwd() which differs from the project directory in Desktop mode. 2. getAllSkills() cache key only included browserProvider, not directory. A first call with the wrong directory would cache stale results that all subsequent calls (even with correct directory) would return. 3. The error message used discoverSkills() (discovered only) instead of getAllSkills() (discovered + builtins), hiding builtin skills from the Available list. Changes: - skill-resolver.ts: accept and pass directory; use getAllSkills for error msg - tools.ts: pass options.directory to resolveSkillContent - skill-discovery.ts: include directory in cache key; rename cache variable - skill/types.ts + tools.ts: add directory to SkillLoadOptions for consistency --- src/features/opencode-skill-loader/skill-discovery.ts | 11 ++++++----- src/tools/delegate-task/skill-resolver.ts | 7 +++---- src/tools/skill/tools.ts | 2 +- src/tools/skill/types.ts | 2 ++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/features/opencode-skill-loader/skill-discovery.ts b/src/features/opencode-skill-loader/skill-discovery.ts index fb991e44..2ee2cbca 100644 --- a/src/features/opencode-skill-loader/skill-discovery.ts +++ b/src/features/opencode-skill-loader/skill-discovery.ts @@ -3,19 +3,20 @@ import { discoverSkills } from "./loader" import type { LoadedSkill } from "./types" import type { SkillResolutionOptions } from "./skill-resolution-options" -const cachedSkillsByProvider = new Map() +const skillCache = new Map() export function clearSkillCache(): void { - cachedSkillsByProvider.clear() + skillCache.clear() } export async function getAllSkills(options?: SkillResolutionOptions): Promise { - const cacheKey = options?.browserProvider ?? "playwright" + const directory = options?.directory ?? process.cwd() + const cacheKey = `${options?.browserProvider ?? "playwright"}:${directory}` const hasDisabledSkills = options?.disabledSkills && options.disabledSkills.size > 0 // Skip cache if disabledSkills is provided (varies between calls) if (!hasDisabledSkills) { - const cached = cachedSkillsByProvider.get(cacheKey) + const cached = skillCache.get(cacheKey) if (cached) return cached } @@ -69,7 +70,7 @@ export async function getAllSkills(options?: SkillResolutionOptions): Promise !options!.disabledSkills!.has(skill.name)) } else { - cachedSkillsByProvider.set(cacheKey, allSkills) + skillCache.set(cacheKey, allSkills) } return allSkills diff --git a/src/tools/delegate-task/skill-resolver.ts b/src/tools/delegate-task/skill-resolver.ts index bfd58e17..217079a8 100644 --- a/src/tools/delegate-task/skill-resolver.ts +++ b/src/tools/delegate-task/skill-resolver.ts @@ -1,10 +1,9 @@ import type { GitMasterConfig, BrowserAutomationProvider } from "../../config/schema" -import { resolveMultipleSkillsAsync } from "../../features/opencode-skill-loader/skill-content" -import { discoverSkills } from "../../features/opencode-skill-loader" +import { resolveMultipleSkillsAsync, getAllSkills } from "../../features/opencode-skill-loader/skill-content" export async function resolveSkillContent( skills: string[], - options: { gitMasterConfig?: GitMasterConfig; browserProvider?: BrowserAutomationProvider, disabledSkills?: Set, directory?: string } + options: { gitMasterConfig?: GitMasterConfig; browserProvider?: BrowserAutomationProvider; disabledSkills?: Set; directory?: string } ): Promise<{ content: string | undefined; error: string | null }> { if (skills.length === 0) { return { content: undefined, error: null } @@ -12,7 +11,7 @@ export async function resolveSkillContent( const { resolved, notFound } = await resolveMultipleSkillsAsync(skills, options) if (notFound.length > 0) { - const allSkills = await discoverSkills({ includeClaudeCodePaths: true, directory: options?.directory }) + const allSkills = await getAllSkills(options) const available = allSkills.map(s => s.name).join(", ") return { content: undefined, error: `Skills not found: ${notFound.join(", ")}. Available: ${available}` } } diff --git a/src/tools/skill/tools.ts b/src/tools/skill/tools.ts index 04477690..60f6ad51 100644 --- a/src/tools/skill/tools.ts +++ b/src/tools/skill/tools.ts @@ -189,7 +189,7 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition const getSkills = async (): Promise => { if (options.skills) return options.skills if (cachedSkills) return cachedSkills - cachedSkills = await getAllSkills({disabledSkills: options?.disabledSkills}) + cachedSkills = await getAllSkills({disabledSkills: options?.disabledSkills, directory: options?.directory}) return cachedSkills } diff --git a/src/tools/skill/types.ts b/src/tools/skill/types.ts index 4fd48d6c..b914957d 100644 --- a/src/tools/skill/types.ts +++ b/src/tools/skill/types.ts @@ -33,4 +33,6 @@ export interface SkillLoadOptions { /** Git master configuration for watermark/co-author settings */ gitMasterConfig?: GitMasterConfig disabledSkills?: Set + /** Project directory for skill discovery. Falls back to process.cwd() if not provided. */ + directory?: string }