@@ -63,16 +63,23 @@ export namespace Skill {
6363 readonly available : ( agent ?: Agent . Info ) => Effect . Effect < Info [ ] >
6464 }
6565
66- const add = async ( state : State , match : string ) => {
67- const md = await ConfigMarkdown . parse ( match ) . catch ( async ( err ) => {
68- const message = ConfigMarkdown . FrontmatterError . isInstance ( err )
69- ? err . data . message
70- : `Failed to parse skill ${ match } `
71- const { Session } = await import ( "@/session" )
72- Bus . publish ( Session . Event . Error , { error : new NamedError . Unknown ( { message } ) . toObject ( ) } )
73- log . error ( "failed to load skill" , { skill : match , err } )
74- return undefined
75- } )
66+ const add = Effect . fnUntraced ( function * ( state : State , match : string , bus : Bus . Interface ) {
67+ const md = yield * Effect . tryPromise ( {
68+ try : ( ) => ConfigMarkdown . parse ( match ) ,
69+ catch : ( err ) => err ,
70+ } ) . pipe (
71+ Effect . catch (
72+ Effect . fnUntraced ( function * ( err ) {
73+ const message = ConfigMarkdown . FrontmatterError . isInstance ( err )
74+ ? err . data . message
75+ : `Failed to parse skill ${ match } `
76+ const { Session } = yield * Effect . promise ( ( ) => import ( "@/session" ) )
77+ yield * bus . publish ( Session . Event . Error , { error : new NamedError . Unknown ( { message } ) . toObject ( ) } )
78+ log . error ( "failed to load skill" , { skill : match , err } )
79+ return undefined
80+ } ) ,
81+ ) ,
82+ )
7683
7784 if ( ! md ) return
7885
@@ -94,80 +101,115 @@ export namespace Skill {
94101 location : match ,
95102 content : md . content ,
96103 }
97- }
104+ } )
98105
99- const scan = async ( state : State , root : string , pattern : string , opts ?: { dot ?: boolean ; scope ?: string } ) => {
100- return Glob . scan ( pattern , {
101- cwd : root ,
102- absolute : true ,
103- include : "file" ,
104- symlink : true ,
105- dot : opts ?. dot ,
106- } )
107- . then ( ( matches ) => Promise . all ( matches . map ( ( match ) => add ( state , match ) ) ) )
108- . catch ( ( error ) => {
109- if ( ! opts ?. scope ) throw error
106+ const scan = Effect . fnUntraced ( function * (
107+ state : State ,
108+ bus : Bus . Interface ,
109+ root : string ,
110+ pattern : string ,
111+ opts ?: { dot ?: boolean ; scope ?: string } ,
112+ ) {
113+ const matches = yield * Effect . tryPromise ( {
114+ try : ( ) =>
115+ Glob . scan ( pattern , {
116+ cwd : root ,
117+ absolute : true ,
118+ include : "file" ,
119+ symlink : true ,
120+ dot : opts ?. dot ,
121+ } ) ,
122+ catch : ( error ) => error ,
123+ } ) . pipe (
124+ Effect . catch ( ( error ) => {
125+ if ( ! opts ?. scope ) return Effect . die ( error )
110126 log . error ( `failed to scan ${ opts . scope } skills` , { dir : root , error } )
111- } )
112- }
127+ return Effect . succeed ( [ ] as string [ ] )
128+ } ) ,
129+ )
130+
131+ yield * Effect . forEach ( matches , ( match ) => add ( state , match , bus ) , {
132+ concurrency : "unbounded" ,
133+ discard : true ,
134+ } )
135+ } )
113136
114- async function loadSkills ( state : State , discovery : Discovery . Interface , directory : string , worktree : string ) {
137+ const loadSkills = Effect . fnUntraced ( function * (
138+ state : State ,
139+ config : Config . Interface ,
140+ discovery : Discovery . Interface ,
141+ bus : Bus . Interface ,
142+ directory : string ,
143+ worktree : string ,
144+ ) {
115145 if ( ! Flag . OPENCODE_DISABLE_EXTERNAL_SKILLS ) {
116146 for ( const dir of EXTERNAL_DIRS ) {
117147 const root = path . join ( Global . Path . home , dir )
118- if ( ! ( await Filesystem . isDir ( root ) ) ) continue
119- await scan ( state , root , EXTERNAL_SKILL_PATTERN , { dot : true , scope : "global" } )
148+ const isDir = yield * Effect . promise ( ( ) => Filesystem . isDir ( root ) )
149+ if ( ! isDir ) continue
150+ yield * scan ( state , bus , root , EXTERNAL_SKILL_PATTERN , { dot : true , scope : "global" } )
120151 }
121152
122- for await ( const root of Filesystem . up ( {
123- targets : EXTERNAL_DIRS ,
124- start : directory ,
125- stop : worktree ,
126- } ) ) {
127- await scan ( state , root , EXTERNAL_SKILL_PATTERN , { dot : true , scope : "project" } )
153+ const upDirs = yield * Effect . promise ( async ( ) => {
154+ const dirs : string [ ] = [ ]
155+ for await ( const root of Filesystem . up ( {
156+ targets : EXTERNAL_DIRS ,
157+ start : directory ,
158+ stop : worktree ,
159+ } ) ) {
160+ dirs . push ( root )
161+ }
162+ return dirs
163+ } )
164+
165+ for ( const root of upDirs ) {
166+ yield * scan ( state , bus , root , EXTERNAL_SKILL_PATTERN , { dot : true , scope : "project" } )
128167 }
129168 }
130169
131- for ( const dir of await Config . directories ( ) ) {
132- await scan ( state , dir , OPENCODE_SKILL_PATTERN )
170+ const configDirs = yield * config . directories ( )
171+ for ( const dir of configDirs ) {
172+ yield * scan ( state , bus , dir , OPENCODE_SKILL_PATTERN )
133173 }
134174
135- const cfg = await Config . get ( )
175+ const cfg = yield * config . get ( )
136176 for ( const item of cfg . skills ?. paths ?? [ ] ) {
137177 const expanded = item . startsWith ( "~/" ) ? path . join ( os . homedir ( ) , item . slice ( 2 ) ) : item
138178 const dir = path . isAbsolute ( expanded ) ? expanded : path . join ( directory , expanded )
139- if ( ! ( await Filesystem . isDir ( dir ) ) ) {
179+ const isDir = yield * Effect . promise ( ( ) => Filesystem . isDir ( dir ) )
180+ if ( ! isDir ) {
140181 log . warn ( "skill path not found" , { path : dir } )
141182 continue
142183 }
143184
144- await scan ( state , dir , SKILL_PATTERN )
185+ yield * scan ( state , bus , dir , SKILL_PATTERN )
145186 }
146187
147188 for ( const url of cfg . skills ?. urls ?? [ ] ) {
148- for ( const dir of await Effect . runPromise ( discovery . pull ( url ) ) ) {
189+ const pulledDirs = yield * discovery . pull ( url )
190+ for ( const dir of pulledDirs ) {
149191 state . dirs . add ( dir )
150- await scan ( state , dir , SKILL_PATTERN )
192+ yield * scan ( state , bus , dir , SKILL_PATTERN )
151193 }
152194 }
153195
154196 log . info ( "init" , { count : Object . keys ( state . skills ) . length } )
155- }
197+ } )
156198
157199 export class Service extends ServiceMap . Service < Service , Interface > ( ) ( "@opencode/Skill" ) { }
158200
159- export const layer : Layer . Layer < Service , never , Discovery . Service > = Layer . effect (
201+ export const layer : Layer . Layer < Service , never , Discovery . Service | Config . Service | Bus . Service > = Layer . effect (
160202 Service ,
161203 Effect . gen ( function * ( ) {
162204 const discovery = yield * Discovery . Service
205+ const config = yield * Config . Service
206+ const bus = yield * Bus . Service
163207 const state = yield * InstanceState . make (
164- Effect . fn ( "Skill.state" ) ( ( ctx ) =>
165- Effect . gen ( function * ( ) {
166- const s : State = { skills : { } , dirs : new Set ( ) }
167- yield * Effect . promise ( ( ) => loadSkills ( s , discovery , ctx . directory , ctx . worktree ) )
168- return s
169- } ) ,
170- ) ,
208+ Effect . fn ( "Skill.state" ) ( function * ( ctx ) {
209+ const s : State = { skills : { } , dirs : new Set ( ) }
210+ yield * loadSkills ( s , config , discovery , bus , ctx . directory , ctx . worktree )
211+ return s
212+ } ) ,
171213 )
172214
173215 const get = Effect . fn ( "Skill.get" ) ( function * ( name : string ) {
@@ -196,7 +238,11 @@ export namespace Skill {
196238 } ) ,
197239 )
198240
199- export const defaultLayer : Layer . Layer < Service > = layer . pipe ( Layer . provide ( Discovery . defaultLayer ) )
241+ export const defaultLayer : Layer . Layer < Service > = layer . pipe (
242+ Layer . provide ( Discovery . defaultLayer ) ,
243+ Layer . provide ( Config . defaultLayer ) ,
244+ Layer . provide ( Bus . layer ) ,
245+ )
200246
201247 export function fmt ( list : Info [ ] , opts : { verbose : boolean } ) {
202248 if ( list . length === 0 ) return "No skills are currently available."
0 commit comments