@@ -42,6 +42,84 @@ export class ApprovalService extends Effect.Service<ApprovalService>()('Approval
4242 } ;
4343 }
4444
45+ function makeForkedService (
46+ engine : RuleEngine ,
47+ permMode : PermissionMode ,
48+ roTools : Set < string > ,
49+ destTools : Set < string >
50+ ) : ApprovalService {
51+ let currentPermMode = permMode ;
52+ return ApprovalService . make ( {
53+ evaluate : ( request : {
54+ tool : string ;
55+ input : Record < string , unknown > ;
56+ context ?: Record < string , unknown > ;
57+ callId ?: string ;
58+ sessionId : string ;
59+ } ) : Effect . Effect < ApprovalDecision > =>
60+ Effect . gen ( function * ( ) {
61+ return yield * runPipeline (
62+ {
63+ tool : request . tool ,
64+ input : request . input ,
65+ context : request . context ,
66+ callId : request . callId ,
67+ } ,
68+ {
69+ ruleEngine : engine ,
70+ readonlyTools : roTools ,
71+ destructiveTools : destTools ,
72+ permissionMode : currentPermMode ,
73+ hooks : buildPipelineHooks ( ) ,
74+ interactive : process . stdin . isTTY ?? false ,
75+ asyncConfirm : hasEmitter ( request . sessionId ) ,
76+ asyncConfirmService : approvalWait ,
77+ onAlways : ( rule ) => engine . addRule ( rule ) ,
78+ onNever : ( rule ) => engine . addRule ( rule ) ,
79+ sessionId : request . sessionId ,
80+ callId : request . callId ,
81+ }
82+ ) ;
83+ } ) ,
84+ addRule : ( rule : PermissionRule ) : Effect . Effect < void > =>
85+ Effect . sync ( ( ) => engine . addRule ( rule ) ) ,
86+ removeRule : ( id : string ) : Effect . Effect < void > => Effect . sync ( ( ) => engine . removeRule ( id ) ) ,
87+ setPermissionMode : ( mode : PermissionMode ) : Effect . Effect < void > =>
88+ Effect . sync ( ( ) => {
89+ currentPermMode = mode ;
90+ } ) ,
91+ getPermissionMode : ( ) : PermissionMode => currentPermMode ,
92+ fork : ( opts ?: {
93+ extraDenyRules ?: PermissionRule [ ] ;
94+ readonly ?: boolean ;
95+ } ) : Effect . Effect < ApprovalService > =>
96+ Effect . sync ( ( ) => {
97+ const nextEngine = createRuleEngine ( engine . getAllRules ( ) ) ;
98+ if ( opts ?. extraDenyRules ) {
99+ for ( const rule of opts . extraDenyRules ) {
100+ nextEngine . addRule ( rule ) ;
101+ }
102+ }
103+ if ( opts ?. readonly ) {
104+ for ( const toolName of DESTRUCTIVE_TOOL_NAMES ) {
105+ nextEngine . addRule ( {
106+ id : `readonly-${ toolName } ` ,
107+ action : 'deny' as const ,
108+ toolPattern : toolName ,
109+ source : 'system' as const ,
110+ } ) ;
111+ }
112+ }
113+ return makeForkedService (
114+ nextEngine ,
115+ currentPermMode ,
116+ new Set ( roTools ) ,
117+ new Set ( destTools )
118+ ) ;
119+ } ) ,
120+ } ) ;
121+ }
122+
45123 return {
46124 evaluate : ( request : {
47125 tool : string ;
@@ -110,116 +188,12 @@ export class ApprovalService extends Effect.Service<ApprovalService>()('Approval
110188 childEngine . addRule ( rule ) ;
111189 }
112190 }
113- let childPermissionMode : PermissionMode = _globalPermissionMode ;
114- const childReadonlyTools = new Set ( readonlyTools ) ;
115- const childDestructiveTools = new Set ( destructiveTools ) ;
116- return {
117- evaluate : ( request : {
118- tool : string ;
119- input : Record < string , unknown > ;
120- context ?: Record < string , unknown > ;
121- callId ?: string ;
122- sessionId : string ;
123- } ) =>
124- Effect . gen ( function * ( ) {
125- return yield * runPipeline (
126- {
127- tool : request . tool ,
128- input : request . input ,
129- context : request . context ,
130- callId : request . callId ,
131- } ,
132- {
133- ruleEngine : childEngine ,
134- readonlyTools : childReadonlyTools ,
135- destructiveTools : childDestructiveTools ,
136- permissionMode : childPermissionMode ,
137- hooks : buildPipelineHooks ( ) ,
138- interactive : process . stdin . isTTY ?? false ,
139- asyncConfirm : hasEmitter ( request . sessionId ) ,
140- asyncConfirmService : approvalWait ,
141- onAlways : ( rule ) => childEngine . addRule ( rule ) ,
142- onNever : ( rule ) => childEngine . addRule ( rule ) ,
143- sessionId : request . sessionId ,
144- callId : request . callId ,
145- }
146- ) ;
147- } ) ,
148- addRule : ( rule : PermissionRule ) => Effect . sync ( ( ) => childEngine . addRule ( rule ) ) ,
149- removeRule : ( id : string ) => Effect . sync ( ( ) => childEngine . removeRule ( id ) ) ,
150- setPermissionMode : ( mode : PermissionMode ) =>
151- Effect . sync ( ( ) => {
152- childPermissionMode = mode ;
153- } ) ,
154- getPermissionMode : ( ) => childPermissionMode ,
155- fork : ( opts2 : any ) => {
156- const nestedParentRules = childEngine . getAllRules ( ) ;
157- const nestedEngine = createRuleEngine ( nestedParentRules ) ;
158- if ( opts2 ?. extraDenyRules ) {
159- for ( const rule of opts2 . extraDenyRules ) {
160- nestedEngine . addRule ( rule ) ;
161- }
162- }
163- if ( opts2 ?. readonly ) {
164- const denyRules : PermissionRule [ ] = DESTRUCTIVE_TOOL_NAMES . map ( ( toolName ) => ( {
165- id : `readonly-${ toolName } ` ,
166- action : 'deny' as const ,
167- toolPattern : toolName ,
168- source : 'system' as const ,
169- } ) ) ;
170- for ( const rule of denyRules ) {
171- nestedEngine . addRule ( rule ) ;
172- }
173- }
174- let nestedPermissionMode : PermissionMode = childPermissionMode ;
175- const nestedReadonlyTools = new Set ( childReadonlyTools ) ;
176- const nestedDestructiveTools = new Set ( childDestructiveTools ) ;
177- return Effect . succeed ( {
178- evaluate : ( request : {
179- tool : string ;
180- input : Record < string , unknown > ;
181- context ?: Record < string , unknown > ;
182- callId ?: string ;
183- sessionId : string ;
184- } ) =>
185- Effect . gen ( function * ( ) {
186- return yield * runPipeline (
187- {
188- tool : request . tool ,
189- input : request . input ,
190- context : request . context ,
191- callId : request . callId ,
192- } ,
193- {
194- ruleEngine : nestedEngine ,
195- readonlyTools : nestedReadonlyTools ,
196- destructiveTools : nestedDestructiveTools ,
197- permissionMode : nestedPermissionMode ,
198- hooks : buildPipelineHooks ( ) ,
199- interactive : process . stdin . isTTY ?? false ,
200- asyncConfirm : hasEmitter ( request . sessionId ) ,
201- asyncConfirmService : approvalWait ,
202- onAlways : ( rule : PermissionRule ) => nestedEngine . addRule ( rule ) ,
203- onNever : ( rule : PermissionRule ) => nestedEngine . addRule ( rule ) ,
204- sessionId : request . sessionId ,
205- callId : request . callId ,
206- }
207- ) ;
208- } ) ,
209- addRule : ( rule : PermissionRule ) => Effect . sync ( ( ) => nestedEngine . addRule ( rule ) ) ,
210- removeRule : ( id : string ) => Effect . sync ( ( ) => nestedEngine . removeRule ( id ) ) ,
211- setPermissionMode : ( mode : PermissionMode ) =>
212- Effect . sync ( ( ) => {
213- nestedPermissionMode = mode ;
214- } ) ,
215- getPermissionMode : ( ) => nestedPermissionMode ,
216- fork : ( opts3 : any ) => {
217- // Nested fork not commonly used, use parent's fork for now
218- return Effect . fail ( new Error ( 'Deeply nested fork not supported' ) ) ;
219- } ,
220- } as any ) ;
221- } ,
222- } as any ;
191+ return makeForkedService (
192+ childEngine ,
193+ _globalPermissionMode ,
194+ new Set ( readonlyTools ) ,
195+ new Set ( destructiveTools )
196+ ) ;
223197 } ) ,
224198 } ;
225199 } ) ,
0 commit comments