11export * as PluginV2 from "./plugin"
22
3- import { Context , Effect , Exit , Layer , Schema , Scope } from "effect"
3+ import { Context , Deferred , Effect , Exit , Layer , Schema , Scope } from "effect"
44import type { Plugin } from "@opencode-ai/plugin/v2/effect"
55import { AgentV2 } from "./agent"
66import { AISDK } from "./aisdk"
@@ -29,6 +29,7 @@ export const Event = {
2929export interface Interface {
3030 readonly add : ( id : ID , effect : Plugin [ "effect" ] ) => Effect . Effect < void >
3131 readonly remove : ( id : ID ) => Effect . Effect < void >
32+ readonly wait : ( id : ID ) => Effect . Effect < void >
3233}
3334
3435export class Service extends Context . Service < Service , Interface > ( ) ( "@opencode/v2/Plugin" ) { }
@@ -41,13 +42,18 @@ export const layer = Layer.effect(
4142 const scope = yield * Scope . make ( )
4243 const active = new Map < ID , Scope . Closeable > ( )
4344 const loading = new Set < ID > ( )
45+ const waiters = new Map < ID , Set < Deferred . Deferred < void > > > ( )
46+ const failures = new Map < ID , Exit . Exit < void , never > > ( )
4447 let host : Parameters < Plugin [ "effect" ] > [ 0 ]
4548
4649 const add = Effect . fn ( "Plugin.add" ) ( function * ( id : ID , effect : Plugin [ "effect" ] ) {
4750 if ( loading . has ( id ) ) return yield * Effect . die ( `Plugin load cycle detected for ${ id } ` )
4851
4952 yield * locks . withLock ( id ) (
50- Effect . sync ( ( ) => loading . add ( id ) ) . pipe (
53+ Effect . sync ( ( ) => {
54+ loading . add ( id )
55+ failures . delete ( id )
56+ } ) . pipe (
5157 Effect . andThen (
5258 State . batch (
5359 Effect . gen ( function * ( ) {
@@ -61,11 +67,22 @@ export const layer = Layer.effect(
6167 Effect . withSpan ( "Plugin.load" , { attributes : { "plugin.id" : id } } ) ,
6268 Effect . onExit ( ( exit ) => ( Exit . isFailure ( exit ) ? Scope . close ( child , exit ) : Effect . void ) ) ,
6369 )
64- active . set ( id , child )
6570 yield * events . publish ( Event . Added , { id } )
71+ active . set ( id , child )
72+ yield * Effect . forEach ( waiters . get ( id ) ?? [ ] , ( waiter ) => Deferred . succeed ( waiter , undefined ) , {
73+ discard : true ,
74+ } )
75+ waiters . delete ( id )
6676 } ) ,
6777 ) ,
6878 ) ,
79+ Effect . onExit ( ( exit ) => {
80+ if ( Exit . isSuccess ( exit ) ) return Effect . void
81+ failures . set ( id , exit )
82+ return Effect . forEach ( waiters . get ( id ) ?? [ ] , ( waiter ) => Deferred . done ( waiter , exit ) , {
83+ discard : true ,
84+ } ) . pipe ( Effect . ensuring ( Effect . sync ( ( ) => waiters . delete ( id ) ) ) )
85+ } ) ,
6986 Effect . ensuring ( Effect . sync ( ( ) => loading . delete ( id ) ) ) ,
7087 ) ,
7188 )
@@ -79,12 +96,41 @@ export const layer = Layer.effect(
7996 Effect . gen ( function * ( ) {
8097 const current = active . get ( id )
8198 active . delete ( id )
99+ failures . delete ( id )
82100 if ( current ) yield * Scope . close ( current , Exit . void ) . pipe ( Effect . ignore )
83101 } ) ,
84102 ) ,
85103 )
86104 } )
87105
106+ const wait = Effect . fn ( "Plugin.wait" ) ( function * ( id : ID ) {
107+ const waiter = yield * Deferred . make < void > ( )
108+ const pending = yield * locks . withLock ( id ) (
109+ Effect . sync ( ( ) => {
110+ if ( active . has ( id ) ) return false
111+ const failure = failures . get ( id )
112+ if ( failure ) return failure
113+ const current = waiters . get ( id ) ?? new Set ( )
114+ current . add ( waiter )
115+ waiters . set ( id , current )
116+ return true
117+ } ) ,
118+ )
119+ if ( ! pending ) return
120+ if ( typeof pending !== "boolean" ) return yield * pending
121+ yield * Deferred . await ( waiter ) . pipe (
122+ Effect . ensuring (
123+ locks . withLock ( id ) (
124+ Effect . sync ( ( ) => {
125+ const current = waiters . get ( id )
126+ current ?. delete ( waiter )
127+ if ( current ?. size === 0 ) waiters . delete ( id )
128+ } ) ,
129+ ) ,
130+ ) ,
131+ )
132+ } )
133+
88134 yield * Effect . addFinalizer ( ( exit ) =>
89135 Effect . gen ( function * ( ) {
90136 active . clear ( )
@@ -95,6 +141,7 @@ export const layer = Layer.effect(
95141 const service = Service . of ( {
96142 add,
97143 remove,
144+ wait,
98145 } )
99146 host = yield * PluginHost . make ( service )
100147 return service
0 commit comments