@@ -1320,7 +1320,16 @@ const sseHandlers: Record<string, SSEHandler> = {
13201320 typeof def . hasInterrupt === 'function'
13211321 ? ! ! def . hasInterrupt ( args || { } )
13221322 : ! ! def . hasInterrupt
1323- if ( ! hasInterrupt && typeof def . execute === 'function' ) {
1323+ // Check if tool is auto-allowed - if so, execute even if it has an interrupt
1324+ const { autoAllowedTools } = get ( )
1325+ const isAutoAllowed = name ? autoAllowedTools . includes ( name ) : false
1326+ if ( ( ! hasInterrupt || isAutoAllowed ) && typeof def . execute === 'function' ) {
1327+ if ( isAutoAllowed && hasInterrupt ) {
1328+ logger . info ( '[toolCallsById] Auto-executing tool with interrupt (auto-allowed)' , {
1329+ id,
1330+ name,
1331+ } )
1332+ }
13241333 const ctx = createExecutionContext ( { toolCallId : id , toolName : name || 'unknown_tool' } )
13251334 // Defer executing transition by a tick to let pending render
13261335 setTimeout ( ( ) => {
@@ -1426,11 +1435,20 @@ const sseHandlers: Record<string, SSEHandler> = {
14261435 logger . warn ( 'tool_call registry auto-exec check failed' , { id, name, error : e } )
14271436 }
14281437
1429- // Class-based auto-exec for non-interrupt tools
1438+ // Class-based auto-exec for non-interrupt tools or auto-allowed tools
14301439 try {
14311440 const inst = getClientTool ( id ) as any
14321441 const hasInterrupt = ! ! inst ?. getInterruptDisplays ?.( )
1433- if ( ! hasInterrupt && typeof inst ?. execute === 'function' ) {
1442+ // Check if tool is auto-allowed - if so, execute even if it has an interrupt
1443+ const { autoAllowedTools : classAutoAllowed } = get ( )
1444+ const isClassAutoAllowed = name ? classAutoAllowed . includes ( name ) : false
1445+ if ( ( ! hasInterrupt || isClassAutoAllowed ) && ( typeof inst ?. execute === 'function' || typeof inst ?. handleAccept === 'function' ) ) {
1446+ if ( isClassAutoAllowed && hasInterrupt ) {
1447+ logger . info ( '[toolCallsById] Auto-executing class tool with interrupt (auto-allowed)' , {
1448+ id,
1449+ name,
1450+ } )
1451+ }
14341452 setTimeout ( ( ) => {
14351453 // Guard against duplicate execution - check if already executing or terminal
14361454 const currentState = get ( ) . toolCallsById [ id ] ?. state
@@ -1449,7 +1467,12 @@ const sseHandlers: Record<string, SSEHandler> = {
14491467
14501468 Promise . resolve ( )
14511469 . then ( async ( ) => {
1452- await inst . execute ( args || { } )
1470+ // Use handleAccept for tools with interrupts, execute for others
1471+ if ( hasInterrupt && typeof inst ?. handleAccept === 'function' ) {
1472+ await inst . handleAccept ( args || { } )
1473+ } else {
1474+ await inst . execute ( args || { } )
1475+ }
14531476 // Success/error will be synced via registerToolStateSync
14541477 } )
14551478 . catch ( ( ) => {
@@ -1474,20 +1497,35 @@ const sseHandlers: Record<string, SSEHandler> = {
14741497 }
14751498 } catch { }
14761499
1477- // Integration tools: Stay in pending state until user confirms via buttons
1500+ // Integration tools: Check auto-allowed or stay in pending state until user confirms
14781501 // This handles tools like google_calendar_*, exa_*, gmail_read, etc. that aren't in the client registry
14791502 // Only relevant if mode is 'build' (agent)
1480- const { mode, workflowId } = get ( )
1503+ const { mode, workflowId, autoAllowedTools , executeIntegrationTool } = get ( )
14811504 if ( mode === 'build' && workflowId ) {
14821505 // Check if tool was NOT found in client registry
14831506 const def = name ? getTool ( name ) : undefined
14841507 const inst = getClientTool ( id ) as any
14851508 if ( ! def && ! inst && name ) {
1486- // Integration tools stay in pending state until user confirms
1487- logger . info ( '[build mode] Integration tool awaiting user confirmation' , {
1488- id,
1489- name,
1490- } )
1509+ // Check if this integration tool is auto-allowed - if so, execute it immediately
1510+ if ( autoAllowedTools . includes ( name ) ) {
1511+ logger . info ( '[build mode] Auto-executing integration tool (auto-allowed)' , { id, name } )
1512+ // Defer to allow pending state to render briefly
1513+ setTimeout ( ( ) => {
1514+ executeIntegrationTool ( id ) . catch ( ( err ) => {
1515+ logger . error ( '[build mode] Auto-execute integration tool failed' , {
1516+ id,
1517+ name,
1518+ error : err ,
1519+ } )
1520+ } )
1521+ } , 0 )
1522+ } else {
1523+ // Integration tools stay in pending state until user confirms
1524+ logger . info ( '[build mode] Integration tool awaiting user confirmation' , {
1525+ id,
1526+ name,
1527+ } )
1528+ }
14911529 }
14921530 }
14931531 } ,
@@ -1976,15 +2014,26 @@ const subAgentSSEHandlers: Record<string, SSEHandler> = {
19762014 }
19772015
19782016 // Execute client tools in parallel (non-blocking) - same pattern as main tool_call handler
2017+ // Check if tool is auto-allowed
2018+ const { autoAllowedTools : subAgentAutoAllowed } = get ( )
2019+ const isSubAgentAutoAllowed = name ? subAgentAutoAllowed . includes ( name ) : false
2020+
19792021 try {
19802022 const def = getTool ( name )
19812023 if ( def ) {
19822024 const hasInterrupt =
19832025 typeof def . hasInterrupt === 'function'
19842026 ? ! ! def . hasInterrupt ( args || { } )
19852027 : ! ! def . hasInterrupt
1986- if ( ! hasInterrupt ) {
1987- // Auto-execute tools without interrupts - non-blocking
2028+ // Auto-execute if no interrupt OR if auto-allowed
2029+ if ( ! hasInterrupt || isSubAgentAutoAllowed ) {
2030+ if ( isSubAgentAutoAllowed && hasInterrupt ) {
2031+ logger . info ( '[SubAgent] Auto-executing tool with interrupt (auto-allowed)' , {
2032+ id,
2033+ name,
2034+ } )
2035+ }
2036+ // Auto-execute tools - non-blocking
19882037 const ctx = createExecutionContext ( { toolCallId : id , toolName : name } )
19892038 Promise . resolve ( )
19902039 . then ( ( ) => def . execute ( ctx , args || { } ) )
@@ -2001,9 +2050,22 @@ const subAgentSSEHandlers: Record<string, SSEHandler> = {
20012050 const instance = getClientTool ( id )
20022051 if ( instance ) {
20032052 const hasInterruptDisplays = ! ! instance . getInterruptDisplays ?.( )
2004- if ( ! hasInterruptDisplays ) {
2053+ // Auto-execute if no interrupt OR if auto-allowed
2054+ if ( ! hasInterruptDisplays || isSubAgentAutoAllowed ) {
2055+ if ( isSubAgentAutoAllowed && hasInterruptDisplays ) {
2056+ logger . info ( '[SubAgent] Auto-executing class tool with interrupt (auto-allowed)' , {
2057+ id,
2058+ name,
2059+ } )
2060+ }
20052061 Promise . resolve ( )
2006- . then ( ( ) => instance . execute ( args || { } ) )
2062+ . then ( ( ) => {
2063+ // Use handleAccept for tools with interrupts, execute for others
2064+ if ( hasInterruptDisplays && typeof instance . handleAccept === 'function' ) {
2065+ return instance . handleAccept ( args || { } )
2066+ }
2067+ return instance . execute ( args || { } )
2068+ } )
20072069 . catch ( ( execErr : any ) => {
20082070 logger . error ( '[SubAgent] Class tool execution failed' , {
20092071 id,
@@ -3676,6 +3738,19 @@ export const useCopilotStore = create<CopilotStore>()(
36763738
36773739 const { id, name, params } = toolCall
36783740
3741+ // Guard against double execution - skip if already executing or in terminal state
3742+ if (
3743+ toolCall . state === ClientToolCallState . executing ||
3744+ isTerminalState ( toolCall . state )
3745+ ) {
3746+ logger . info ( '[executeIntegrationTool] Skipping - already executing or terminal' , {
3747+ id,
3748+ name,
3749+ state : toolCall . state ,
3750+ } )
3751+ return
3752+ }
3753+
36793754 // Set to executing state
36803755 const executingMap = { ...get ( ) . toolCallsById }
36813756 executingMap [ id ] = {
@@ -3824,6 +3899,46 @@ export const useCopilotStore = create<CopilotStore>()(
38243899 const data = await res . json ( )
38253900 set ( { autoAllowedTools : data . autoAllowedTools || [ ] } )
38263901 logger . info ( '[AutoAllowedTools] Added tool' , { toolId } )
3902+
3903+ // Auto-execute all pending tools of the same type
3904+ const { toolCallsById, executeIntegrationTool } = get ( )
3905+ const pendingToolCalls = Object . values ( toolCallsById ) . filter (
3906+ ( tc ) => tc . name === toolId && tc . state === ClientToolCallState . pending
3907+ )
3908+ if ( pendingToolCalls . length > 0 ) {
3909+ const isIntegrationTool = ! CLASS_TOOL_METADATA [ toolId ]
3910+ logger . info ( '[AutoAllowedTools] Auto-executing pending tools' , {
3911+ toolId,
3912+ count : pendingToolCalls . length ,
3913+ isIntegrationTool,
3914+ } )
3915+ for ( const tc of pendingToolCalls ) {
3916+ if ( isIntegrationTool ) {
3917+ // Integration tools use executeIntegrationTool
3918+ executeIntegrationTool ( tc . id ) . catch ( ( err ) => {
3919+ logger . error ( '[AutoAllowedTools] Auto-execute pending integration tool failed' , {
3920+ toolCallId : tc . id ,
3921+ toolId,
3922+ error : err ,
3923+ } )
3924+ } )
3925+ } else {
3926+ // Client tools with interrupts use handleAccept
3927+ const inst = getClientTool ( tc . id ) as any
3928+ if ( inst && typeof inst . handleAccept === 'function' ) {
3929+ Promise . resolve ( )
3930+ . then ( ( ) => inst . handleAccept ( tc . params || { } ) )
3931+ . catch ( ( err : any ) => {
3932+ logger . error ( '[AutoAllowedTools] Auto-execute pending client tool failed' , {
3933+ toolCallId : tc . id ,
3934+ toolId,
3935+ error : err ,
3936+ } )
3937+ } )
3938+ }
3939+ }
3940+ }
3941+ }
38273942 }
38283943 } catch ( err ) {
38293944 logger . error ( '[AutoAllowedTools] Failed to add tool' , { toolId, error : err } )
0 commit comments