diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts index 4f7c8d3e02d..bcc61073330 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -266,8 +266,9 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS const userRules = newRules.filter(r => r.scope === 'user'); // Handle session-scoped rules (temporary, in-memory only) + const chatSessionId = this.context.element.sessionId; for (const rule of sessionRules) { - this.terminalChatService.addSessionAutoApproveRule(rule.key, rule.value); + this.terminalChatService.addSessionAutoApproveRule(chatSessionId, rule.key, rule.value); } // Handle workspace-scoped rules diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index d85cc00157f..c41ab0d9d4f 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -220,16 +220,18 @@ export interface ITerminalChatService { /** * Add a session-scoped auto-approve rule. + * @param chatSessionId The chat session ID to associate the rule with * @param key The rule key (command or regex pattern) * @param value The rule value (approval boolean or object with approve and matchCommandLine) */ - addSessionAutoApproveRule(key: string, value: boolean | { approve: boolean; matchCommandLine?: boolean }): void; + addSessionAutoApproveRule(chatSessionId: string, key: string, value: boolean | { approve: boolean; matchCommandLine?: boolean }): void; /** - * Get all session-scoped auto-approve rules. - * @returns A record of all session-scoped auto-approve rules + * Get all session-scoped auto-approve rules for a specific chat session. + * @param chatSessionId The chat session ID to get rules for + * @returns A record of all session-scoped auto-approve rules for the session */ - getSessionAutoApproveRules(): Readonly>; + getSessionAutoApproveRules(chatSessionId: string): Readonly>; } /** diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index 4cf7e886fbb..438c345051b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -54,10 +54,11 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ private readonly _sessionAutoApprovalEnabled = new Set(); /** - * Tracks session-scoped auto-approve rules. These are temporary rules that last only for the - * duration of the VS Code session (not persisted to disk). + * Tracks session-scoped auto-approve rules per chat session. These are temporary rules that + * last only for the duration of the chat session (not persisted to disk). + * Map> */ - private readonly _sessionAutoApproveRules: Record = {}; + private readonly _sessionAutoApproveRules = new Map>(); constructor( @ILogService private readonly _logService: ILogService, @@ -72,6 +73,16 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._hasHiddenToolTerminalContext = TerminalChatContextKeys.hasHiddenChatTerminals.bindTo(this._contextKeyService); this._restoreFromStorage(); + + // Clear session auto-approve rules when chat sessions end + this._register(this._chatService.onDidDisposeSession(e => { + for (const resource of e.sessionResource) { + const sessionId = LocalChatSessionUri.parseLocalSessionId(resource); + if (sessionId) { + this._sessionAutoApproveRules.delete(sessionId); + } + } + })); } registerTerminalInstanceWithToolSession(terminalToolSessionId: string | undefined, instance: ITerminalInstance): void { @@ -320,11 +331,16 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ return this._sessionAutoApprovalEnabled.has(chatSessionId); } - addSessionAutoApproveRule(key: string, value: boolean | { approve: boolean; matchCommandLine?: boolean }): void { - this._sessionAutoApproveRules[key] = value; + addSessionAutoApproveRule(chatSessionId: string, key: string, value: boolean | { approve: boolean; matchCommandLine?: boolean }): void { + let sessionRules = this._sessionAutoApproveRules.get(chatSessionId); + if (!sessionRules) { + sessionRules = {}; + this._sessionAutoApproveRules.set(chatSessionId, sessionRules); + } + sessionRules[key] = value; } - getSessionAutoApproveRules(): Readonly> { - return this._sessionAutoApproveRules; + getSessionAutoApproveRules(chatSessionId: string): Readonly> { + return this._sessionAutoApproveRules.get(chatSessionId) ?? {}; } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts index 381937619e8..61d5cda35f7 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/commandLineAutoApprover.ts @@ -85,7 +85,7 @@ export class CommandLineAutoApprover extends Disposable { this._denyListCommandLineRules = denyListCommandLineRules; } - isCommandAutoApproved(command: string, shell: string, os: OperatingSystem): ICommandApprovalResultWithReason { + isCommandAutoApproved(command: string, shell: string, os: OperatingSystem, chatSessionId?: string): ICommandApprovalResultWithReason { // Check if the command has a transient environment variable assignment prefix which we // always deny for now as it can easily lead to execute other commands if (transientEnvVarRegex.test(command)) { @@ -107,7 +107,7 @@ export class CommandLineAutoApprover extends Disposable { } // Check session allow rules (session deny rules can't exist) - for (const rule of this._getSessionRules().allowListRules) { + for (const rule of this._getSessionRules(chatSessionId).allowListRules) { if (this._commandMatchesRule(rule, command, shell, os)) { return { result: 'approved', @@ -137,7 +137,7 @@ export class CommandLineAutoApprover extends Disposable { }; } - isCommandLineAutoApproved(commandLine: string): ICommandApprovalResultWithReason { + isCommandLineAutoApproved(commandLine: string, chatSessionId?: string): ICommandApprovalResultWithReason { // Check the config deny list first to see if this command line requires explicit approval for (const rule of this._denyListCommandLineRules) { if (rule.regex.test(commandLine)) { @@ -150,7 +150,7 @@ export class CommandLineAutoApprover extends Disposable { } // Check session allow list (session deny rules can't exist) - for (const rule of this._getSessionRules().allowListCommandLineRules) { + for (const rule of this._getSessionRules(chatSessionId).allowListCommandLineRules) { if (rule.regex.test(commandLine)) { return { result: 'approved', @@ -176,7 +176,7 @@ export class CommandLineAutoApprover extends Disposable { }; } - private _getSessionRules(): { + private _getSessionRules(chatSessionId?: string): { denyListRules: IAutoApproveRule[]; allowListRules: IAutoApproveRule[]; allowListCommandLineRules: IAutoApproveRule[]; @@ -187,7 +187,11 @@ export class CommandLineAutoApprover extends Disposable { const allowListCommandLineRules: IAutoApproveRule[] = []; const denyListCommandLineRules: IAutoApproveRule[] = []; - const sessionRulesConfig = this._terminalChatService.getSessionAutoApproveRules(); + if (!chatSessionId) { + return { denyListRules, allowListRules, allowListCommandLineRules, denyListCommandLineRules }; + } + + const sessionRulesConfig = this._terminalChatService.getSessionAutoApproveRules(chatSessionId); for (const [key, value] of Object.entries(sessionRulesConfig)) { if (typeof value === 'boolean') { const { regex, regexCaseInsensitive } = this._convertAutoApproveEntryToRegex(key); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts index 77db325f68f..86a45af8248 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAutoApproveAnalyzer.ts @@ -87,8 +87,8 @@ export class CommandLineAutoApproveAnalyzer extends Disposable implements IComma }; } - const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, options.shell, options.os)); - const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(options.commandLine); + const subCommandResults = subCommands.map(e => this._commandLineAutoApprover.isCommandAutoApproved(e, options.shell, options.os, options.chatSessionId)); + const commandLineResult = this._commandLineAutoApprover.isCommandLineAutoApproved(options.commandLine, options.chatSessionId); const autoApproveReasons: string[] = [ ...subCommandResults.map(e => e.reason), commandLineResult.reason,