@@ -11,15 +11,22 @@ import type {
1111 InvokeOptions ,
1212} from "./tools" ;
1313import type { Source , SourceDetectionResult , SourceRegistry } from "./sources" ;
14- import type { Policy , PolicyEngine } from "./policies" ;
14+ import type {
15+ Policy ,
16+ PolicyEngine ,
17+ PolicyDecision ,
18+ CreatePolicyPayload ,
19+ UpdatePolicyPayload ,
20+ } from "./policies" ;
1521import type { Scope } from "./scope" ;
1622import type { ExecutorPlugin , PluginExtensions , PluginHandle } from "./plugin" ;
23+ import { PolicyDeniedError } from "./errors" ;
1724import type {
1825 ToolNotFoundError ,
1926 ToolInvocationError ,
2027 SecretNotFoundError ,
2128 SecretResolutionError ,
22- PolicyDeniedError ,
29+ PolicyNotFoundError ,
2330} from "./errors" ;
2431import {
2532 FormElicitation ,
@@ -64,7 +71,12 @@ export type Executor<TPlugins extends readonly ExecutorPlugin<string, object>[]
6471
6572 readonly policies : {
6673 readonly list : ( ) => Effect . Effect < readonly Policy [ ] > ;
67- readonly add : ( policy : Omit < Policy , "id" | "createdAt" > ) => Effect . Effect < Policy > ;
74+ readonly get : ( policyId : string ) => Effect . Effect < Policy , PolicyNotFoundError > ;
75+ readonly add : ( policy : CreatePolicyPayload ) => Effect . Effect < Policy > ;
76+ readonly update : (
77+ policyId : string ,
78+ patch : UpdatePolicyPayload ,
79+ ) => Effect . Effect < Policy , PolicyNotFoundError > ;
6880 readonly remove : ( policyId : string ) => Effect . Effect < boolean > ;
6981 } ;
7082
@@ -120,6 +132,29 @@ export const createExecutor = <
120132 Effect . gen ( function * ( ) {
121133 const { scope, tools, sources, secrets, policies, plugins = [ ] } = config ;
122134
135+ const runApproval = (
136+ decision : PolicyDecision ,
137+ toolId : ToolId ,
138+ args : unknown ,
139+ options : InvokeOptions ,
140+ message : string ,
141+ source : "policy" | "annotation" ,
142+ ) => {
143+ const handler = resolveElicitationHandler ( options ) ;
144+ return handler ( {
145+ toolId,
146+ args,
147+ request : new FormElicitation ( {
148+ message,
149+ requestedSchema : { } ,
150+ } ) ,
151+ approval : {
152+ source,
153+ ...( decision . matchedPolicyId ? { matchedPolicyId : decision . matchedPolicyId } : { } ) ,
154+ } ,
155+ } ) ;
156+ } ;
157+
123158 // Initialize all plugins
124159 const handles = new Map < string , PluginHandle < object > > ( ) ;
125160 const extensions : Record < string , object > = { } ;
@@ -146,20 +181,53 @@ export const createExecutor = <
146181 invoke : ( toolId : string , args : unknown , options : InvokeOptions ) => {
147182 const tid = toolId as ToolId ;
148183 return Effect . gen ( function * ( ) {
149- yield * policies . check ( { scopeId : scope . id , toolId : tid } ) ;
184+ const decision = yield * policies . check ( { scopeId : scope . id , toolId : tid } ) ;
185+
186+ if ( decision . kind === "deny" ) {
187+ return yield * new PolicyDeniedError ( {
188+ policyId : decision . matchedPolicyId as PolicyId ,
189+ toolId : tid ,
190+ reason : decision . reason ,
191+ } ) ;
192+ }
193+
194+ if ( decision . kind === "require_interaction" ) {
195+ const response = yield * runApproval (
196+ decision ,
197+ tid ,
198+ args ,
199+ options ,
200+ decision . reason ,
201+ "policy" ,
202+ ) ;
203+ if ( response . action !== "accept" ) {
204+ return yield * new ElicitationDeclinedError ( {
205+ toolId : tid ,
206+ action : response . action ,
207+ } ) ;
208+ }
209+ return yield * tools . invoke ( tid , args , options ) ;
210+ }
211+
212+ if ( decision . kind === "allow" ) {
213+ return yield * tools . invoke ( tid , args , options ) ;
214+ }
150215
151216 // Dynamically resolve annotations from the plugin
152217 const annotations = yield * tools . resolveAnnotations ( tid ) ;
153218 if ( annotations ?. requiresApproval ) {
154- const handler = resolveElicitationHandler ( options ) ;
155- const response = yield * handler ( {
156- toolId : tid ,
219+ const response = yield * runApproval (
220+ {
221+ kind : "fallback" ,
222+ matchedPolicyId : null ,
223+ reason : annotations . approvalDescription ?? `Approve ${ toolId } ?` ,
224+ } as PolicyDecision ,
225+ tid ,
157226 args ,
158- request : new FormElicitation ( {
159- message : annotations . approvalDescription ?? `Approve ${ toolId } ?` ,
160- requestedSchema : { } ,
161- } ) ,
162- } ) ;
227+ options ,
228+ annotations . approvalDescription ?? `Approve ${ toolId } ?` ,
229+ "annotation" ,
230+ ) ;
163231 if ( response . action !== "accept" ) {
164232 return yield * new ElicitationDeclinedError ( {
165233 toolId : tid ,
@@ -182,8 +250,11 @@ export const createExecutor = <
182250
183251 policies : {
184252 list : ( ) => policies . list ( scope . id ) ,
185- add : ( policy : Omit < Policy , "id" | "createdAt" > ) =>
253+ get : ( policyId : string ) => policies . get ( policyId as PolicyId ) ,
254+ add : ( policy : CreatePolicyPayload ) =>
186255 policies . add ( { ...policy , scopeId : scope . id } ) ,
256+ update : ( policyId : string , patch : UpdatePolicyPayload ) =>
257+ policies . update ( policyId as PolicyId , patch ) ,
187258 remove : ( policyId : string ) => policies . remove ( policyId as PolicyId ) ,
188259 } ,
189260
0 commit comments