11export * as SessionV2 from "./session"
22export * from "./session/schema"
33
4- import { DateTime , Effect , Layer , Schema , Context , Stream } from "effect"
4+ import { DateTime , Effect , Layer , Schema , Context , Stream , Scope } from "effect"
55import { ListAnchor } from "@opencode-ai/schema/session"
66import { and , asc , desc , eq , gt , like , lt , or , type SQL } from "drizzle-orm"
77import { ProjectV2 } from "./project"
@@ -37,6 +37,7 @@ import { SessionRevert } from "./session/revert"
3737import { Revert } from "@opencode-ai/schema/revert"
3838import { FSUtil } from "./fs-util"
3939import { SessionDurable } from "@opencode-ai/schema/durable-event-manifest"
40+ import { SkillV2 } from "./skill"
4041
4142export const RevertState = Revert . State
4243export type RevertState = Revert . State
@@ -95,7 +96,7 @@ export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Ses
9596export class OperationUnavailableError extends Schema . TaggedErrorClass < OperationUnavailableError > ( ) (
9697 "Session.OperationUnavailableError" ,
9798 {
98- operation : Schema . Literals ( [ "move" , "shell" , "skill" , "switchAgent" , "compact" , "wait" ] ) ,
99+ operation : Schema . Literals ( [ "move" , "shell" , "skill" , "switchAgent" , "compact" ] ) ,
99100 } ,
100101) { }
101102
@@ -105,10 +106,13 @@ export class PromptConflictError extends Schema.TaggedErrorClass<PromptConflictE
105106 sessionID : SessionSchema . ID ,
106107 messageID : SessionMessage . ID ,
107108} ) { }
109+ export class SkillNotFoundError extends Schema . TaggedErrorClass < SkillNotFoundError > ( ) ( "Session.SkillNotFoundError" , {
110+ skill : Schema . String ,
111+ } ) { }
108112export const MessageNotFoundError = SessionRevert . MessageNotFoundError
109113export type MessageNotFoundError = SessionRevert . MessageNotFoundError
110114
111- export type Error = NotFoundError | MessageDecodeError | OperationUnavailableError | PromptConflictError
115+ export type Error = NotFoundError | MessageDecodeError | OperationUnavailableError | PromptConflictError | SkillNotFoundError
112116
113117export interface Interface {
114118 readonly list : ( input ?: ListInput ) => Effect . Effect < SessionSchema . Info [ ] >
@@ -158,13 +162,13 @@ export interface Interface {
158162 resume ?: boolean
159163 } ) => Effect . Effect < void , OperationUnavailableError >
160164 readonly skill : ( input : {
161- id ?: EventV2 . ID
165+ id ?: SessionMessage . ID
162166 sessionID : SessionSchema . ID
163167 skill : string
164168 resume ?: boolean
165- } ) => Effect . Effect < void , OperationUnavailableError >
169+ } ) => Effect . Effect < void , NotFoundError | SkillNotFoundError >
166170 readonly compact : ( input : CompactInput ) => Effect . Effect < void , NotFoundError | OperationUnavailableError >
167- readonly wait : ( id : SessionSchema . ID ) => Effect . Effect < void , NotFoundError | OperationUnavailableError >
171+ readonly wait : ( id : SessionSchema . ID ) => Effect . Effect < void , NotFoundError >
168172 readonly active : Effect . Effect < ReadonlySet < SessionSchema . ID > >
169173 readonly resume : ( sessionID : SessionSchema . ID ) => Effect . Effect < void , NotFoundError | SessionRunner . RunError >
170174 readonly interrupt : ( sessionID : SessionSchema . ID ) => Effect . Effect < void >
@@ -191,6 +195,7 @@ const layer = Layer.effect(
191195 const execution = yield * SessionExecution . Service
192196 const store = yield * SessionStore . Service
193197 const locations = yield * LocationServiceMap . Service
198+ const scope = yield * Scope . Scope
194199 const decodeMessage = Schema . decodeUnknownEffect ( SessionMessage . Message )
195200 const isDurableSessionEvent = Schema . is ( SessionEvent . Durable )
196201 const decode = ( row : typeof SessionMessageTable . $inferSelect ) =>
@@ -387,8 +392,22 @@ const layer = Layer.effect(
387392 shell : Effect . fn ( "V2Session.shell" ) ( function * ( ) {
388393 return yield * new OperationUnavailableError ( { operation : "shell" } )
389394 } ) ,
390- skill : Effect . fn ( "V2Session.skill" ) ( function * ( ) {
391- return yield * new OperationUnavailableError ( { operation : "skill" } )
395+ skill : Effect . fn ( "V2Session.skill" ) ( function * ( input ) {
396+ const session = yield * result . get ( input . sessionID )
397+ const skills = yield * SkillV2 . Service . pipe ( Effect . provide ( locations . get ( session . location ) ) )
398+ const skill = ( yield * skills . list ( ) ) . find ( ( item ) => item . name === input . skill )
399+ if ( ! skill ) return yield * new SkillNotFoundError ( { skill : input . skill } )
400+ yield * events . publish ( SessionEvent . Skill . Activated , {
401+ sessionID : input . sessionID ,
402+ messageID : input . id ?? SessionMessage . ID . create ( ) ,
403+ timestamp : yield * DateTime . now ,
404+ name : skill . name ,
405+ text : skill . content ,
406+ } )
407+ if ( input . resume !== false )
408+ yield * execution
409+ . resume ( input . sessionID )
410+ . pipe ( Effect . ignore , Effect . forkIn ( scope , { startImmediately : true } ) , Effect . asVoid )
392411 } ) ,
393412 switchAgent : Effect . fn ( "V2Session.switchAgent" ) ( function * ( input ) {
394413 yield * result . get ( input . sessionID )
@@ -427,7 +446,7 @@ const layer = Layer.effect(
427446 } ) ,
428447 wait : Effect . fn ( "V2Session.wait" ) ( function * ( sessionID ) {
429448 yield * result . get ( sessionID )
430- return yield * new OperationUnavailableError ( { operation : "wait" } )
449+ yield * execution . awaitIdle ( sessionID )
431450 } ) ,
432451 active : execution . active ,
433452 resume : Effect . fn ( "V2Session.resume" ) ( function * ( sessionID ) {
0 commit comments