@@ -459,6 +459,30 @@ export class AgenticChatController implements ChatHandlers {
459
459
#getPendingToolUses( toolUses : Record < string , ToolUse & { stop : boolean } > ) : Array < ToolUse & { stop : boolean } > {
460
460
return Object . values ( toolUses ) . filter ( toolUse => toolUse . stop )
461
461
}
462
+ /**
463
+ * Creates a promise that does not resolve until the user accepts or rejects the tool usage.
464
+ * @param toolUseId
465
+ * @param toolUseName
466
+ * @param resultStream
467
+ * @param promptBlockId id of approval block. This allows us to overwrite the buttons with 'accepted' or 'rejected' text.
468
+ * @param session
469
+ */
470
+ async waitForToolApproval (
471
+ toolUseId : string ,
472
+ toolUseName : string ,
473
+ resultStream : AgenticChatResultStream ,
474
+ promptBlockId : number ,
475
+ session : ChatSessionService
476
+ ) {
477
+ const deferred = this . #createDeferred( )
478
+ session . setDeferredToolExecution ( toolUseId , deferred . resolve , deferred . reject )
479
+ this . #log( `Prompting for tool approval for tool: ${ toolUseName } ` )
480
+ await deferred . promise
481
+ if ( toolUseName === 'executeBash' ) {
482
+ // Note: we want to overwrite the button block because it already exists in the stream.
483
+ await resultStream . overwriteResultBlock ( this . #getUpdateBashConfirmResult( toolUseId , true ) , promptBlockId )
484
+ }
485
+ }
462
486
463
487
/**
464
488
* Processes tool uses by running the tools and collecting results
@@ -474,7 +498,6 @@ export class AgenticChatController implements ChatHandlers {
474
498
for ( const toolUse of toolUses ) {
475
499
if ( ! toolUse . name || ! toolUse . toolUseId ) continue
476
500
this . #triggerContext. getToolUseLookup ( ) . set ( toolUse . toolUseId , toolUse )
477
- let needsConfirmation
478
501
479
502
try {
480
503
const { explanation } = toolUse . input as unknown as ExplanatoryParams
@@ -506,9 +529,15 @@ export class AgenticChatController implements ChatHandlers {
506
529
toolUse . input as unknown as ExecuteBashParams
507
530
)
508
531
if ( requiresAcceptance ) {
509
- needsConfirmation = true
510
532
const confirmationResult = this . #processExecuteBashConfirmation( toolUse , warning )
511
- await chatResultStream . writeResultBlock ( confirmationResult )
533
+ const buttonBlockId = await chatResultStream . writeResultBlock ( confirmationResult )
534
+ await this . waitForToolApproval (
535
+ toolUse . toolUseId ,
536
+ toolUse . name ,
537
+ chatResultStream ,
538
+ buttonBlockId ,
539
+ session
540
+ )
512
541
}
513
542
break
514
543
default :
@@ -520,16 +549,6 @@ export class AgenticChatController implements ChatHandlers {
520
549
break
521
550
}
522
551
523
- if ( needsConfirmation ) {
524
- const deferred = this . #createDeferred( )
525
- session . setDeferredToolExecution ( toolUse . toolUseId , deferred . resolve , deferred . reject )
526
- this . #log( `Prompting for tool approval for tool: ${ toolUse . name } ` )
527
- await deferred . promise
528
- if ( toolUse . name === 'executeBash' ) {
529
- await chatResultStream . writeResultBlock ( this . #getUpdateBashConfirmResult( toolUse , true ) )
530
- }
531
- }
532
-
533
552
const result = await this . #features. agent . runTool ( toolUse . name , toolUse . input , token )
534
553
let toolResultContent : ToolResultContentBlock
535
554
@@ -572,7 +591,9 @@ export class AgenticChatController implements ChatHandlers {
572
591
// If we did not approve a tool to be used or the user stopped the response, bubble this up to interrupt agentic loop
573
592
if ( CancellationError . isUserCancelled ( err ) || err instanceof ToolApprovalException ) {
574
593
if ( err instanceof ToolApprovalException ) {
575
- await chatResultStream . writeResultBlock ( this . #getUpdateBashConfirmResult( toolUse , false ) )
594
+ await chatResultStream . writeResultBlock (
595
+ this . #getUpdateBashConfirmResult( toolUse . toolUseId , false )
596
+ )
576
597
}
577
598
throw err
578
599
}
@@ -592,9 +613,9 @@ export class AgenticChatController implements ChatHandlers {
592
613
return results
593
614
}
594
615
595
- #getUpdateBashConfirmResult( toolUse : ToolUse , isAccept : boolean ) : ChatResult {
616
+ #getUpdateBashConfirmResult( toolUseId : string , isAccept : boolean ) : ChatResult {
596
617
return {
597
- messageId : toolUse . toolUseId ,
618
+ messageId : toolUseId ,
598
619
type : 'tool' ,
599
620
body : '' ,
600
621
header : {
0 commit comments