@@ -11,7 +11,7 @@ import { asArray } from '../../../../../../../base/common/arrays.js';
1111import { CancellationTokenSource } from '../../../../../../../base/common/cancellation.js' ;
1212import { Codicon } from '../../../../../../../base/common/codicons.js' ;
1313import { ErrorNoTelemetry } from '../../../../../../../base/common/errors.js' ;
14- import { createCommandUri , MarkdownString , type IMarkdownString } from '../../../../../../../base/common/htmlContent.js' ;
14+ import { createCommandUri , escapeMarkdownSyntaxTokens , MarkdownString , type IMarkdownString } from '../../../../../../../base/common/htmlContent.js' ;
1515import { toDisposable } from '../../../../../../../base/common/lifecycle.js' ;
1616import Severity from '../../../../../../../base/common/severity.js' ;
1717import { isObject } from '../../../../../../../base/common/types.js' ;
@@ -191,29 +191,92 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS
191191 position : { hoverPosition : HoverPosition . LEFT } ,
192192 } ) ) ;
193193
194+ const riskBadge = this . _createRiskBadge ( state . parameters ) ;
195+
194196 const confirmWidget = this . _register ( this . instantiationService . createInstance (
195197 ChatCustomConfirmationWidget < TerminalNewAutoApproveButtonData | boolean > ,
196198 this . context ,
197199 {
198200 title,
199201 icon : Codicon . terminal ,
200202 message : elements . root ,
201- footerBanner : this . _createRiskBadge ( state . parameters ) ,
203+ footerBanner : riskBadge ?. domNode ,
202204 buttons : this . _createButtons ( moreActions )
203205 } ,
204206 ) ) ;
205207
208+ // Build the unsandboxed-execution reason and disclaimer markdown. When
209+ // the risk badge is shown, surface them via its details hover (with
210+ // labelled prefixes) instead of the dedicated disclaimer row to keep
211+ // the confirmation compact.
212+ interface IDetailPart {
213+ readonly inline : IMarkdownString ;
214+ readonly hoverLabel : string ;
215+ readonly hoverBody : string ;
216+ readonly isTrusted : IMarkdownString [ 'isTrusted' ] ;
217+ }
218+ const detailParts : IDetailPart [ ] = [ ] ;
206219 if ( terminalData . requestUnsandboxedExecution ) {
207220 const reasonText = ( terminalData . requestUnsandboxedExecutionReason && terminalData . requestUnsandboxedExecutionReason . trim ( ) )
208221 || localize ( 'chat.terminal.unsandboxedExecution.defaultReason' , "The model did not provide a reason for requesting unsandboxed execution." ) ;
209- const unsandboxedReasonMarkdown = new MarkdownString ( undefined , { supportThemeIcons : true } ) ;
210- unsandboxedReasonMarkdown . appendMarkdown ( `$(${ Codicon . info . id } ) ` ) ;
211- unsandboxedReasonMarkdown . appendText ( reasonText ) ;
212- this . _appendMarkdownPart ( elements . disclaimer , unsandboxedReasonMarkdown , codeBlockRenderOptions ) ;
222+ const inline = new MarkdownString ( undefined , { supportThemeIcons : true } ) ;
223+ inline . appendMarkdown ( `$(${ Codicon . info . id } ) ` ) ;
224+ inline . appendText ( reasonText ) ;
225+ detailParts . push ( {
226+ inline,
227+ hoverLabel : localize ( 'chat.terminal.detail.sandboxInsufficient' , "Sandbox insufficient:" ) ,
228+ hoverBody : escapeMarkdownSyntaxTokens ( reasonText ) ,
229+ isTrusted : undefined ,
230+ } ) ;
213231 }
214-
215232 if ( disclaimer ) {
216- this . _appendMarkdownPart ( elements . disclaimer , disclaimer , codeBlockRenderOptions ) ;
233+ const inline = typeof disclaimer === 'string' ? new MarkdownString ( disclaimer ) : disclaimer ;
234+ // For the hover, drop the leading `$(info) ` icon prefix that the
235+ // disclaimer carries for inline rendering — the labelled prefix
236+ // already conveys the same role.
237+ const hoverBody = inline . value . replace ( / ^ \s * \$ \( [ ^ ) ] + \) \s * / , '' ) ;
238+ detailParts . push ( {
239+ inline,
240+ hoverLabel : localize ( 'chat.terminal.detail.approvalNeeded' , "Approval needed:" ) ,
241+ hoverBody,
242+ isTrusted : inline . isTrusted ,
243+ } ) ;
244+ }
245+
246+ const renderInlineDisclaimers = ( ) => {
247+ elements . disclaimer . replaceChildren ( ) ;
248+ for ( const part of detailParts ) {
249+ this . _appendMarkdownPart ( elements . disclaimer , part . inline , codeBlockRenderOptions ) ;
250+ }
251+ } ;
252+
253+ if ( riskBadge && detailParts . length ) {
254+ const combined = new MarkdownString ( undefined , {
255+ supportThemeIcons : true ,
256+ isTrusted : detailParts . reduce < MarkdownString [ 'isTrusted' ] > ( ( acc , part ) => {
257+ if ( part . isTrusted === true || acc === true ) {
258+ return true ;
259+ }
260+ if ( typeof part . isTrusted === 'object' && part . isTrusted ) {
261+ const enabled = new Set ( [
262+ ...( typeof acc === 'object' && acc ?. enabledCommands ? acc . enabledCommands : [ ] ) ,
263+ ...part . isTrusted . enabledCommands ,
264+ ] ) ;
265+ return { enabledCommands : [ ...enabled ] } ;
266+ }
267+ return acc ;
268+ } , undefined ) ,
269+ } ) ;
270+ detailParts . forEach ( ( part , i ) => {
271+ if ( i > 0 ) {
272+ combined . appendMarkdown ( '\n\n' ) ;
273+ }
274+ combined . appendMarkdown ( `**${ escapeMarkdownSyntaxTokens ( part . hoverLabel ) } ** ${ part . hoverBody } ` ) ;
275+ } ) ;
276+ riskBadge . setDetails ( combined ) ;
277+ this . _register ( riskBadge . onDidHide ( ( ) => renderInlineDisclaimers ( ) ) ) ;
278+ } else {
279+ renderInlineDisclaimers ( ) ;
217280 }
218281
219282 const hasToolConfirmationKey = ChatContextKeys . Editing . hasToolConfirmation . bindTo ( this . contextKeyService ) ;
@@ -431,7 +494,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS
431494 return promptResult . result === true ;
432495 }
433496
434- private _createRiskBadge ( parameters : unknown ) : HTMLElement | undefined {
497+ private _createRiskBadge ( parameters : unknown ) : ToolRiskBadgeWidget | undefined {
435498 if ( ! this . riskAssessmentService . isEnabled ( ) ) {
436499 return undefined ;
437500 }
@@ -462,7 +525,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS
462525 }
463526 } ) ( ) ;
464527 }
465- return widget . domNode ;
528+ return widget ;
466529 }
467530
468531 private _appendMarkdownPart ( container : HTMLElement , message : string | IMarkdownString , codeBlockRenderOptions : ICodeBlockRenderOptions ) {
0 commit comments