@@ -15,6 +15,10 @@ import { Plugin } from "@/plugin"
1515import { SystemPrompt } from "./system"
1616import { Flag } from "@/flag/flag"
1717import { Permission } from "@/permission"
18+ import { PermissionID } from "@/permission/schema"
19+ import { Bus } from "@/bus"
20+ import { Wildcard } from "@/util/wildcard"
21+ import { SessionID } from "@/session/schema"
1822import { Auth } from "@/auth"
1923import { Installation } from "@/installation"
2024
@@ -231,6 +235,7 @@ export namespace LLM {
231235 // and results sent back over the WebSocket.
232236 if ( language instanceof GitLabWorkflowLanguageModel ) {
233237 const workflowModel = language
238+ workflowModel . sessionID = input . sessionID
234239 workflowModel . systemPrompt = system . join ( "\n" )
235240 workflowModel . toolExecutor = async ( toolName , argsJson , _requestID ) => {
236241 const t = tools [ toolName ]
@@ -253,6 +258,57 @@ export namespace LLM {
253258 return { result : "" , error : e . message ?? String ( e ) }
254259 }
255260 }
261+
262+ const ruleset = Permission . merge ( input . agent . permission ?? [ ] , input . permission ?? [ ] )
263+ workflowModel . sessionPreapprovedTools = Object . keys ( tools ) . filter ( ( name ) => {
264+ const match = ruleset . findLast ( ( rule ) => Wildcard . match ( name , rule . permission ) )
265+ return ! match || match . action !== "ask"
266+ } )
267+
268+ const approvedToolsForSession = new Set < string > ( )
269+ workflowModel . approvalHandler = Instance . bind ( async ( approvalTools ) => {
270+ const uniqueNames = [ ...new Set ( approvalTools . map ( ( t : { name : string } ) => t . name ) ) ] as string [ ]
271+ // Auto-approve tools that were already approved in this session
272+ // (prevents infinite approval loops for server-side MCP tools)
273+ if ( uniqueNames . every ( ( name ) => approvedToolsForSession . has ( name ) ) ) {
274+ return { approved : true }
275+ }
276+
277+ const id = PermissionID . ascending ( )
278+ let reply : Permission . Reply | undefined
279+ let unsub : ( ( ) => void ) | undefined
280+ try {
281+ unsub = Bus . subscribe ( Permission . Event . Replied , ( evt ) => {
282+ if ( evt . properties . requestID === id ) reply = evt . properties . reply
283+ } )
284+ const toolPatterns = approvalTools . map ( ( t : { name : string ; args : string } ) => {
285+ try {
286+ const parsed = JSON . parse ( t . args ) as Record < string , unknown >
287+ const title = ( parsed ?. title ?? parsed ?. name ?? "" ) as string
288+ return title ? `${ t . name } : ${ title } ` : t . name
289+ } catch {
290+ return t . name
291+ }
292+ } )
293+ const uniquePatterns = [ ...new Set ( toolPatterns ) ] as string [ ]
294+ await Permission . ask ( {
295+ id,
296+ sessionID : SessionID . make ( input . sessionID ) ,
297+ permission : "workflow_tool_approval" ,
298+ patterns : uniquePatterns ,
299+ metadata : { tools : approvalTools } ,
300+ always : uniquePatterns ,
301+ ruleset : [ ] ,
302+ } )
303+ for ( const name of uniqueNames ) approvedToolsForSession . add ( name )
304+ workflowModel . sessionPreapprovedTools = [ ...workflowModel . sessionPreapprovedTools , ...uniqueNames ]
305+ return { approved : true }
306+ } catch {
307+ return { approved : false }
308+ } finally {
309+ unsub ?.( )
310+ }
311+ } )
256312 }
257313
258314 return streamText ( {
0 commit comments