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
This commit is contained in:
ismeth 2026-02-19 18:28:31 +01:00 committed by YeonGyu-Kim
parent 0d88fe61f0
commit 6c98677d22
4 changed files with 12 additions and 10 deletions

View File

@ -3,19 +3,20 @@ import { discoverSkills } from "./loader"
import type { LoadedSkill } from "./types" import type { LoadedSkill } from "./types"
import type { SkillResolutionOptions } from "./skill-resolution-options" import type { SkillResolutionOptions } from "./skill-resolution-options"
const cachedSkillsByProvider = new Map<string, LoadedSkill[]>() const skillCache = new Map<string, LoadedSkill[]>()
export function clearSkillCache(): void { export function clearSkillCache(): void {
cachedSkillsByProvider.clear() skillCache.clear()
} }
export async function getAllSkills(options?: SkillResolutionOptions): Promise<LoadedSkill[]> { export async function getAllSkills(options?: SkillResolutionOptions): Promise<LoadedSkill[]> {
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 const hasDisabledSkills = options?.disabledSkills && options.disabledSkills.size > 0
// Skip cache if disabledSkills is provided (varies between calls) // Skip cache if disabledSkills is provided (varies between calls)
if (!hasDisabledSkills) { if (!hasDisabledSkills) {
const cached = cachedSkillsByProvider.get(cacheKey) const cached = skillCache.get(cacheKey)
if (cached) return cached if (cached) return cached
} }
@ -69,7 +70,7 @@ export async function getAllSkills(options?: SkillResolutionOptions): Promise<Lo
if (hasDisabledSkills) { if (hasDisabledSkills) {
allSkills = allSkills.filter((skill) => !options!.disabledSkills!.has(skill.name)) allSkills = allSkills.filter((skill) => !options!.disabledSkills!.has(skill.name))
} else { } else {
cachedSkillsByProvider.set(cacheKey, allSkills) skillCache.set(cacheKey, allSkills)
} }
return allSkills return allSkills

View File

@ -1,10 +1,9 @@
import type { GitMasterConfig, BrowserAutomationProvider } from "../../config/schema" import type { GitMasterConfig, BrowserAutomationProvider } from "../../config/schema"
import { resolveMultipleSkillsAsync } from "../../features/opencode-skill-loader/skill-content" import { resolveMultipleSkillsAsync, getAllSkills } from "../../features/opencode-skill-loader/skill-content"
import { discoverSkills } from "../../features/opencode-skill-loader"
export async function resolveSkillContent( export async function resolveSkillContent(
skills: string[], skills: string[],
options: { gitMasterConfig?: GitMasterConfig; browserProvider?: BrowserAutomationProvider, disabledSkills?: Set<string>, directory?: string } options: { gitMasterConfig?: GitMasterConfig; browserProvider?: BrowserAutomationProvider; disabledSkills?: Set<string>; directory?: string }
): Promise<{ content: string | undefined; error: string | null }> { ): Promise<{ content: string | undefined; error: string | null }> {
if (skills.length === 0) { if (skills.length === 0) {
return { content: undefined, error: null } return { content: undefined, error: null }
@ -12,7 +11,7 @@ export async function resolveSkillContent(
const { resolved, notFound } = await resolveMultipleSkillsAsync(skills, options) const { resolved, notFound } = await resolveMultipleSkillsAsync(skills, options)
if (notFound.length > 0) { 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(", ") const available = allSkills.map(s => s.name).join(", ")
return { content: undefined, error: `Skills not found: ${notFound.join(", ")}. Available: ${available}` } return { content: undefined, error: `Skills not found: ${notFound.join(", ")}. Available: ${available}` }
} }

View File

@ -189,7 +189,7 @@ export function createSkillTool(options: SkillLoadOptions = {}): ToolDefinition
const getSkills = async (): Promise<LoadedSkill[]> => { const getSkills = async (): Promise<LoadedSkill[]> => {
if (options.skills) return options.skills if (options.skills) return options.skills
if (cachedSkills) return cachedSkills if (cachedSkills) return cachedSkills
cachedSkills = await getAllSkills({disabledSkills: options?.disabledSkills}) cachedSkills = await getAllSkills({disabledSkills: options?.disabledSkills, directory: options?.directory})
return cachedSkills return cachedSkills
} }

View File

@ -33,4 +33,6 @@ export interface SkillLoadOptions {
/** Git master configuration for watermark/co-author settings */ /** Git master configuration for watermark/co-author settings */
gitMasterConfig?: GitMasterConfig gitMasterConfig?: GitMasterConfig
disabledSkills?: Set<string> disabledSkills?: Set<string>
/** Project directory for skill discovery. Falls back to process.cwd() if not provided. */
directory?: string
} }