@@ -139,6 +139,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
139139 const [ enableButtons , setEnableButtons ] = useState < boolean > ( false )
140140 const [ primaryButtonText , setPrimaryButtonText ] = useState < string | undefined > ( undefined )
141141 const [ secondaryButtonText , setSecondaryButtonText ] = useState < string | undefined > ( undefined )
142+ const [ tertiaryButtonText , setTertiaryButtonText ] = useState < string | undefined > ( undefined )
142143 const [ didClickCancel , setDidClickCancel ] = useState ( false )
143144 const virtuosoRef = useRef < VirtuosoHandle > ( null )
144145 const [ expandedRows , setExpandedRows ] = useState < Record < number , boolean > > ( { } )
@@ -312,7 +313,8 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
312313 setClineAsk ( "command" )
313314 setEnableButtons ( ! isPartial )
314315 setPrimaryButtonText ( t ( "chat:runCommand.title" ) )
315- setSecondaryButtonText ( t ( "chat:reject.title" ) )
316+ setSecondaryButtonText ( "Add & Run" )
317+ setTertiaryButtonText ( t ( "chat:reject.title" ) )
316318 break
317319 case "command_output" :
318320 setSendingDisabled ( false )
@@ -609,6 +611,22 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
609611 startNewTask ( )
610612 break
611613 case "command" :
614+ // For command case, secondary button is "Add & Run"
615+ // Only send text/images if they exist
616+ if ( trimmedInput || ( images && images . length > 0 ) ) {
617+ vscode . postMessage ( {
618+ type : "askResponse" ,
619+ askResponse : "addAndRunButtonClicked" ,
620+ text : trimmedInput ,
621+ images : images ,
622+ } )
623+ } else {
624+ vscode . postMessage ( { type : "askResponse" , askResponse : "addAndRunButtonClicked" } )
625+ }
626+ // Clear input state after sending
627+ setInputValue ( "" )
628+ setSelectedImages ( [ ] )
629+ break
612630 case "tool" :
613631 case "browser_action_launch" :
614632 case "use_mcp_server" :
@@ -639,6 +657,36 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
639657 [ clineAsk , startNewTask , isStreaming ] ,
640658 )
641659
660+ const handleTertiaryButtonClick = useCallback (
661+ ( text ?: string , images ?: string [ ] ) => {
662+ const trimmedInput = text ?. trim ( )
663+
664+ switch ( clineAsk ) {
665+ case "command" :
666+ // For command case, tertiary button is "Reject"
667+ // Only send text/images if they exist
668+ if ( trimmedInput || ( images && images . length > 0 ) ) {
669+ vscode . postMessage ( {
670+ type : "askResponse" ,
671+ askResponse : "noButtonClicked" ,
672+ text : trimmedInput ,
673+ images : images ,
674+ } )
675+ } else {
676+ vscode . postMessage ( { type : "askResponse" , askResponse : "noButtonClicked" } )
677+ }
678+ // Clear input state after sending
679+ setInputValue ( "" )
680+ setSelectedImages ( [ ] )
681+ break
682+ }
683+ setSendingDisabled ( true )
684+ setClineAsk ( undefined )
685+ setEnableButtons ( false )
686+ } ,
687+ [ clineAsk ] ,
688+ )
689+
642690 const handleTaskCloseButtonClick = useCallback ( ( ) => startNewTask ( ) , [ startNewTask ] )
643691
644692 const { info : model } = useSelectedModel ( apiConfiguration )
@@ -690,6 +738,9 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
690738 case "secondaryButtonClick" :
691739 handleSecondaryButtonClick ( message . text ?? "" , message . images ?? [ ] )
692740 break
741+ case "tertiaryButtonClick" :
742+ handleTertiaryButtonClick ( message . text ?? "" , message . images ?? [ ] )
743+ break
693744 }
694745 break
695746 case "condenseTaskContextResponse" :
@@ -716,6 +767,7 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
716767 handleSetChatBoxMessage ,
717768 handlePrimaryButtonClick ,
718769 handleSecondaryButtonClick ,
770+ handleTertiaryButtonClick ,
719771 ] ,
720772 )
721773
@@ -1490,67 +1542,128 @@ const ChatViewComponent: React.ForwardRefRenderFunction<ChatViewRef, ChatViewPro
14901542 </ div >
14911543 ) : (
14921544 < div
1493- className = { `flex ${
1545+ className = { `${
14941546 primaryButtonText || secondaryButtonText || isStreaming ? "px-[15px] pt-[10px]" : "p-0"
14951547 } ${
14961548 primaryButtonText || secondaryButtonText || isStreaming
14971549 ? enableButtons || ( isStreaming && ! didClickCancel )
14981550 ? "opacity-100"
14991551 : "opacity-50"
15001552 : "opacity-0"
1501- } `} >
1502- { primaryButtonText && ! isStreaming && (
1503- < StandardTooltip
1504- content = {
1505- primaryButtonText === t ( "chat:retry.title" )
1506- ? t ( "chat:retry.tooltip" )
1507- : primaryButtonText === t ( "chat:save.title" )
1508- ? t ( "chat:save.tooltip" )
1509- : primaryButtonText === t ( "chat:approve.title" )
1510- ? t ( "chat:approve.tooltip" )
1511- : primaryButtonText === t ( "chat:runCommand.title" )
1553+ } ${ tertiaryButtonText ? "flex flex-col gap-[6px]" : "flex" } `} >
1554+ { /* Three-button layout for command approval */ }
1555+ { tertiaryButtonText && ! isStreaming ? (
1556+ < >
1557+ { /* Top row: Run and Add & Run buttons */ }
1558+ < div className = "flex gap-[6px]" >
1559+ { primaryButtonText && (
1560+ < StandardTooltip
1561+ content = {
1562+ primaryButtonText === t ( "chat:runCommand.title" )
15121563 ? t ( "chat:runCommand.tooltip" )
1513- : primaryButtonText === t ( "chat:startNewTask.title" )
1514- ? t ( "chat:startNewTask.tooltip" )
1515- : primaryButtonText === t ( "chat:resumeTask.title" )
1516- ? t ( "chat:resumeTask.tooltip" )
1517- : primaryButtonText === t ( "chat:proceedAnyways.title" )
1518- ? t ( "chat:proceedAnyways.tooltip" )
1519- : primaryButtonText ===
1520- t ( "chat:proceedWhileRunning.title" )
1521- ? t ( "chat:proceedWhileRunning.tooltip" )
1522- : undefined
1523- } >
1524- < VSCodeButton
1525- appearance = "primary"
1526- disabled = { ! enableButtons }
1527- className = { secondaryButtonText ? "flex-1 mr-[6px]" : "flex-[2] mr-0" }
1528- onClick = { ( ) => handlePrimaryButtonClick ( inputValue , selectedImages ) } >
1529- { primaryButtonText }
1530- </ VSCodeButton >
1531- </ StandardTooltip >
1532- ) }
1533- { ( secondaryButtonText || isStreaming ) && (
1534- < StandardTooltip
1535- content = {
1536- isStreaming
1537- ? t ( "chat:cancel.tooltip" )
1538- : secondaryButtonText === t ( "chat:startNewTask.title" )
1539- ? t ( "chat:startNewTask.tooltip" )
1540- : secondaryButtonText === t ( "chat:reject.title" )
1541- ? t ( "chat:reject.tooltip" )
1542- : secondaryButtonText === t ( "chat:terminate.title" )
1543- ? t ( "chat:terminate.tooltip" )
15441564 : undefined
1545- } >
1546- < VSCodeButton
1547- appearance = "secondary"
1548- disabled = { ! enableButtons && ! ( isStreaming && ! didClickCancel ) }
1549- className = { isStreaming ? "flex-[2] ml-0" : "flex-1 ml-[6px]" }
1550- onClick = { ( ) => handleSecondaryButtonClick ( inputValue , selectedImages ) } >
1551- { isStreaming ? t ( "chat:cancel.title" ) : secondaryButtonText }
1552- </ VSCodeButton >
1553- </ StandardTooltip >
1565+ } >
1566+ < VSCodeButton
1567+ appearance = "primary"
1568+ disabled = { ! enableButtons }
1569+ className = "flex-1"
1570+ onClick = { ( ) =>
1571+ handlePrimaryButtonClick ( inputValue , selectedImages )
1572+ } >
1573+ { primaryButtonText }
1574+ </ VSCodeButton >
1575+ </ StandardTooltip >
1576+ ) }
1577+ { secondaryButtonText && (
1578+ < StandardTooltip content = "Add command to whitelist and run it" >
1579+ < VSCodeButton
1580+ appearance = "secondary"
1581+ disabled = { ! enableButtons }
1582+ className = "flex-1"
1583+ onClick = { ( ) =>
1584+ handleSecondaryButtonClick ( inputValue , selectedImages )
1585+ } >
1586+ { secondaryButtonText }
1587+ </ VSCodeButton >
1588+ </ StandardTooltip >
1589+ ) }
1590+ </ div >
1591+ { /* Bottom row: Reject button */ }
1592+ < div className = "flex" >
1593+ < StandardTooltip
1594+ content = {
1595+ tertiaryButtonText === t ( "chat:reject.title" )
1596+ ? t ( "chat:reject.tooltip" )
1597+ : undefined
1598+ } >
1599+ < VSCodeButton
1600+ appearance = "secondary"
1601+ disabled = { ! enableButtons }
1602+ className = "flex-1"
1603+ onClick = { ( ) => handleTertiaryButtonClick ( inputValue , selectedImages ) } >
1604+ { tertiaryButtonText }
1605+ </ VSCodeButton >
1606+ </ StandardTooltip >
1607+ </ div >
1608+ </ >
1609+ ) : (
1610+ /* Two-button layout for other cases */
1611+ < >
1612+ { primaryButtonText && ! isStreaming && (
1613+ < StandardTooltip
1614+ content = {
1615+ primaryButtonText === t ( "chat:retry.title" )
1616+ ? t ( "chat:retry.tooltip" )
1617+ : primaryButtonText === t ( "chat:save.title" )
1618+ ? t ( "chat:save.tooltip" )
1619+ : primaryButtonText === t ( "chat:approve.title" )
1620+ ? t ( "chat:approve.tooltip" )
1621+ : primaryButtonText === t ( "chat:runCommand.title" )
1622+ ? t ( "chat:runCommand.tooltip" )
1623+ : primaryButtonText === t ( "chat:startNewTask.title" )
1624+ ? t ( "chat:startNewTask.tooltip" )
1625+ : primaryButtonText === t ( "chat:resumeTask.title" )
1626+ ? t ( "chat:resumeTask.tooltip" )
1627+ : primaryButtonText ===
1628+ t ( "chat:proceedAnyways.title" )
1629+ ? t ( "chat:proceedAnyways.tooltip" )
1630+ : primaryButtonText ===
1631+ t ( "chat:proceedWhileRunning.title" )
1632+ ? t ( "chat:proceedWhileRunning.tooltip" )
1633+ : undefined
1634+ } >
1635+ < VSCodeButton
1636+ appearance = "primary"
1637+ disabled = { ! enableButtons }
1638+ className = { secondaryButtonText ? "flex-1 mr-[6px]" : "flex-[2] mr-0" }
1639+ onClick = { ( ) => handlePrimaryButtonClick ( inputValue , selectedImages ) } >
1640+ { primaryButtonText }
1641+ </ VSCodeButton >
1642+ </ StandardTooltip >
1643+ ) }
1644+ { ( secondaryButtonText || isStreaming ) && ! tertiaryButtonText && (
1645+ < StandardTooltip
1646+ content = {
1647+ isStreaming
1648+ ? t ( "chat:cancel.tooltip" )
1649+ : secondaryButtonText === t ( "chat:startNewTask.title" )
1650+ ? t ( "chat:startNewTask.tooltip" )
1651+ : secondaryButtonText === t ( "chat:reject.title" )
1652+ ? t ( "chat:reject.tooltip" )
1653+ : secondaryButtonText === t ( "chat:terminate.title" )
1654+ ? t ( "chat:terminate.tooltip" )
1655+ : undefined
1656+ } >
1657+ < VSCodeButton
1658+ appearance = "secondary"
1659+ disabled = { ! enableButtons && ! ( isStreaming && ! didClickCancel ) }
1660+ className = { isStreaming ? "flex-[2] ml-0" : "flex-1 ml-[6px]" }
1661+ onClick = { ( ) => handleSecondaryButtonClick ( inputValue , selectedImages ) } >
1662+ { isStreaming ? t ( "chat:cancel.title" ) : secondaryButtonText }
1663+ </ VSCodeButton >
1664+ </ StandardTooltip >
1665+ ) }
1666+ </ >
15541667 ) }
15551668 </ div >
15561669 ) }
0 commit comments