@@ -11,12 +11,13 @@ import { structuralEquals } from '../../../../../base/common/equals.js';
1111import { ConfigurationTarget , IConfigurationService , type IConfigurationValue } from '../../../../../platform/configuration/common/configuration.js' ;
1212import { TerminalChatAgentToolsSettingId } from '../common/terminalChatAgentToolsConfiguration.js' ;
1313import { isPowerShell } from './runInTerminalHelpers.js' ;
14+ import { ITerminalChatService } from '../../../terminal/browser/terminal.js' ;
1415
1516export interface IAutoApproveRule {
1617 regex : RegExp ;
1718 regexCaseInsensitive : RegExp ;
1819 sourceText : string ;
19- sourceTarget : ConfigurationTarget ;
20+ sourceTarget : ConfigurationTarget | 'session' ;
2021 isDefaultRule : boolean ;
2122}
2223
@@ -39,6 +40,7 @@ export class CommandLineAutoApprover extends Disposable {
3940
4041 constructor (
4142 @IConfigurationService private readonly _configurationService : IConfigurationService ,
43+ @ITerminalChatService private readonly _terminalChatService : ITerminalChatService ,
4244 ) {
4345 super ( ) ;
4446 this . updateConfiguration ( ) ;
@@ -76,7 +78,7 @@ export class CommandLineAutoApprover extends Disposable {
7678 this . _denyListCommandLineRules = denyListCommandLineRules ;
7779 }
7880
79- isCommandAutoApproved ( command : string , shell : string , os : OperatingSystem ) : ICommandApprovalResultWithReason {
81+ isCommandAutoApproved ( command : string , shell : string , os : OperatingSystem , chatSessionId ?: string ) : ICommandApprovalResultWithReason {
8082 // Check if the command has a transient environment variable assignment prefix which we
8183 // always deny for now as it can easily lead to execute other commands
8284 if ( transientEnvVarRegex . test ( command ) ) {
@@ -86,7 +88,7 @@ export class CommandLineAutoApprover extends Disposable {
8688 } ;
8789 }
8890
89- // Check the deny list to see if this command requires explicit approval
91+ // Check the config deny list to see if this command requires explicit approval
9092 for ( const rule of this . _denyListRules ) {
9193 if ( this . _commandMatchesRule ( rule , command , shell , os ) ) {
9294 return {
@@ -97,7 +99,18 @@ export class CommandLineAutoApprover extends Disposable {
9799 }
98100 }
99101
100- // Check the allow list to see if the command is allowed to run without explicit approval
102+ // Check session allow rules (session deny rules can't exist)
103+ for ( const rule of this . _getSessionRules ( chatSessionId ) . allowListRules ) {
104+ if ( this . _commandMatchesRule ( rule , command , shell , os ) ) {
105+ return {
106+ result : 'approved' ,
107+ rule,
108+ reason : `Command '${ command } ' is approved by session allow list rule: ${ rule . sourceText } `
109+ } ;
110+ }
111+ }
112+
113+ // Check the config allow list to see if the command is allowed to run without explicit approval
101114 for ( const rule of this . _allowListRules ) {
102115 if ( this . _commandMatchesRule ( rule , command , shell , os ) ) {
103116 return {
@@ -117,8 +130,8 @@ export class CommandLineAutoApprover extends Disposable {
117130 } ;
118131 }
119132
120- isCommandLineAutoApproved ( commandLine : string ) : ICommandApprovalResultWithReason {
121- // Check the deny list first to see if this command line requires explicit approval
133+ isCommandLineAutoApproved ( commandLine : string , chatSessionId ?: string ) : ICommandApprovalResultWithReason {
134+ // Check the config deny list first to see if this command line requires explicit approval
122135 for ( const rule of this . _denyListCommandLineRules ) {
123136 if ( rule . regex . test ( commandLine ) ) {
124137 return {
@@ -129,7 +142,18 @@ export class CommandLineAutoApprover extends Disposable {
129142 }
130143 }
131144
132- // Check if the full command line matches any of the allow list command line regexes
145+ // Check session allow list (session deny rules can't exist)
146+ for ( const rule of this . _getSessionRules ( chatSessionId ) . allowListCommandLineRules ) {
147+ if ( rule . regex . test ( commandLine ) ) {
148+ return {
149+ result : 'approved' ,
150+ rule,
151+ reason : `Command line '${ commandLine } ' is approved by session allow list rule: ${ rule . sourceText } `
152+ } ;
153+ }
154+ }
155+
156+ // Check if the full command line matches any of the config allow list command line regexes
133157 for ( const rule of this . _allowListCommandLineRules ) {
134158 if ( rule . regex . test ( commandLine ) ) {
135159 return {
@@ -145,6 +169,54 @@ export class CommandLineAutoApprover extends Disposable {
145169 } ;
146170 }
147171
172+ private _getSessionRules ( chatSessionId ?: string ) : {
173+ denyListRules : IAutoApproveRule [ ] ;
174+ allowListRules : IAutoApproveRule [ ] ;
175+ allowListCommandLineRules : IAutoApproveRule [ ] ;
176+ denyListCommandLineRules : IAutoApproveRule [ ] ;
177+ } {
178+ const denyListRules : IAutoApproveRule [ ] = [ ] ;
179+ const allowListRules : IAutoApproveRule [ ] = [ ] ;
180+ const allowListCommandLineRules : IAutoApproveRule [ ] = [ ] ;
181+ const denyListCommandLineRules : IAutoApproveRule [ ] = [ ] ;
182+
183+ if ( ! chatSessionId ) {
184+ return { denyListRules, allowListRules, allowListCommandLineRules, denyListCommandLineRules } ;
185+ }
186+
187+ const sessionRulesConfig = this . _terminalChatService . getSessionAutoApproveRules ( chatSessionId ) ;
188+ for ( const [ key , value ] of Object . entries ( sessionRulesConfig ) ) {
189+ if ( typeof value === 'boolean' ) {
190+ const { regex, regexCaseInsensitive } = this . _convertAutoApproveEntryToRegex ( key ) ;
191+ if ( value === true ) {
192+ allowListRules . push ( { regex, regexCaseInsensitive, sourceText : key , sourceTarget : 'session' , isDefaultRule : false } ) ;
193+ } else if ( value === false ) {
194+ denyListRules . push ( { regex, regexCaseInsensitive, sourceText : key , sourceTarget : 'session' , isDefaultRule : false } ) ;
195+ }
196+ } else if ( typeof value === 'object' && value !== null ) {
197+ const objectValue = value as { approve ?: boolean ; matchCommandLine ?: boolean } ;
198+ if ( typeof objectValue . approve === 'boolean' ) {
199+ const { regex, regexCaseInsensitive } = this . _convertAutoApproveEntryToRegex ( key ) ;
200+ if ( objectValue . approve === true ) {
201+ if ( objectValue . matchCommandLine === true ) {
202+ allowListCommandLineRules . push ( { regex, regexCaseInsensitive, sourceText : key , sourceTarget : 'session' , isDefaultRule : false } ) ;
203+ } else {
204+ allowListRules . push ( { regex, regexCaseInsensitive, sourceText : key , sourceTarget : 'session' , isDefaultRule : false } ) ;
205+ }
206+ } else if ( objectValue . approve === false ) {
207+ if ( objectValue . matchCommandLine === true ) {
208+ denyListCommandLineRules . push ( { regex, regexCaseInsensitive, sourceText : key , sourceTarget : 'session' , isDefaultRule : false } ) ;
209+ } else {
210+ denyListRules . push ( { regex, regexCaseInsensitive, sourceText : key , sourceTarget : 'session' , isDefaultRule : false } ) ;
211+ }
212+ }
213+ }
214+ }
215+ }
216+
217+ return { denyListRules, allowListRules, allowListCommandLineRules, denyListCommandLineRules } ;
218+ }
219+
148220 private _commandMatchesRule ( rule : IAutoApproveRule , command : string , shell : string , os : OperatingSystem ) : boolean {
149221 const isPwsh = isPowerShell ( shell , os ) ;
150222
0 commit comments