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 74cd04b8333..e17e6487abe 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.ts @@ -30,6 +30,7 @@ import { IKeybindingService } from '../../../../../../platform/keybinding/common import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js'; import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js'; import { IPreferencesService } from '../../../../../services/preferences/common/preferences.js'; +import { ITerminalChatService } from '../../../../terminal/browser/terminal.js'; import { TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js'; import { migrateLegacyTerminalToolSpecificData } from '../../../common/chat.js'; import { ChatContextKeys } from '../../../common/chatContextKeys.js'; @@ -42,7 +43,7 @@ import { ChatCustomConfirmationWidget, IChatConfirmationButton } from '../chatCo import { EditorPool } from '../chatContentCodePools.js'; import { IChatContentPartRenderContext } from '../chatContentParts.js'; import { ChatMarkdownContentPart } from '../chatMarkdownContentPart.js'; -import { openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js'; +import { disableSessionAutoApprovalCommandId, openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js'; import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js'; export const enum TerminalToolConfirmationStorageKeys { @@ -61,7 +62,8 @@ export type TerminalNewAutoApproveButtonData = ( { type: 'enable' } | { type: 'configure' } | { type: 'skip' } | - { type: 'newRule'; rule: ITerminalNewAutoApproveRule | ITerminalNewAutoApproveRule[] } + { type: 'newRule'; rule: ITerminalNewAutoApproveRule | ITerminalNewAutoApproveRule[] } | + { type: 'sessionApproval' } ); export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationSubPart { @@ -87,6 +89,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IStorageService private readonly storageService: IStorageService, + @ITerminalChatService private readonly terminalChatService: ITerminalChatService, @ITextModelService textModelService: ITextModelService, @IHoverService hoverService: IHoverService, ) { @@ -302,6 +305,19 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS doComplete = false; break; } + case 'sessionApproval': { + const sessionId = this.context.element.sessionId; + this.terminalChatService.setChatSessionAutoApproval(sessionId, true); + const disableUri = createCommandUri(disableSessionAutoApprovalCommandId, sessionId); + const mdTrustSettings = { + isTrusted: { + enabledCommands: [disableSessionAutoApprovalCommandId] + } + }; + terminalData.autoApproveInfo = new MarkdownString(`${localize('sessionApproval', 'All commands will be auto approved for this session')} ([${localize('sessionApproval.disable', 'Disable')}](${disableUri.toString()}))`, mdTrustSettings); + toolConfirmKind = ToolConfirmKind.UserAction; + break; + } } } diff --git a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts index c824bed947b..59ee5568c84 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.ts @@ -861,6 +861,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, { }); export const openTerminalSettingsLinkCommandId = '_chat.openTerminalSettingsLink'; +export const disableSessionAutoApprovalCommandId = '_chat.disableSessionAutoApproval'; CommandsRegistry.registerCommand(openTerminalSettingsLinkCommandId, async (accessor, scopeRaw: string) => { const preferencesService = accessor.get(IPreferencesService); @@ -897,6 +898,11 @@ CommandsRegistry.registerCommand(openTerminalSettingsLinkCommandId, async (acces } }); +CommandsRegistry.registerCommand(disableSessionAutoApprovalCommandId, async (accessor, chatSessionId: string) => { + const terminalChatService = accessor.get(ITerminalChatService); + terminalChatService.setChatSessionAutoApproval(chatSessionId, false); +}); + class ToggleChatTerminalOutputAction extends Action implements IAction { private _expanded = false; diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index c6d9cbb50b9..eee3a039a73 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -203,6 +203,20 @@ export interface ITerminalChatService { * @returns The most recent progress part or undefined if none exist */ getMostRecentProgressPart(): IChatTerminalToolProgressPart | undefined; + + /** + * Enable or disable auto approval for all commands in a specific session. + * @param chatSessionId The chat session ID + * @param enabled Whether to enable or disable session auto approval + */ + setChatSessionAutoApproval(chatSessionId: string, enabled: boolean): void; + + /** + * Check if a session has auto approval enabled for all commands. + * @param chatSessionId The chat session ID + * @returns True if the session has auto approval enabled + */ + hasChatSessionAutoApproval(chatSessionId: string): boolean; } /** diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index f0d7c422ba4..0f8b2421e1b 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -47,6 +47,12 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ private readonly _hasToolTerminalContext: IContextKey; private readonly _hasHiddenToolTerminalContext: IContextKey; + /** + * Tracks chat session IDs that have auto approval enabled for all commands. This is a temporary + * approval that lasts only for the duration of the session. + */ + private readonly _sessionAutoApprovalEnabled = new Set(); + constructor( @ILogService private readonly _logService: ILogService, @ITerminalService private readonly _terminalService: ITerminalService, @@ -83,6 +89,11 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ this._terminalInstancesByToolSessionId.delete(terminalToolSessionId); this._toolSessionIdByTerminalInstance.delete(instance); this._terminalInstanceListenersByToolSessionId.deleteAndDispose(terminalToolSessionId); + // Clean up session auto approval state + const sessionId = LocalChatSessionUri.parseLocalSessionId(e.sessionResource); + if (sessionId) { + this._sessionAutoApprovalEnabled.delete(sessionId); + } this._persistToStorage(); this._updateHasToolTerminalContextKeys(); } @@ -279,4 +290,16 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ const hiddenTerminalCount = this.getToolSessionTerminalInstances(true).length; this._hasHiddenToolTerminalContext.set(hiddenTerminalCount > 0); } + + setChatSessionAutoApproval(chatSessionId: string, enabled: boolean): void { + if (enabled) { + this._sessionAutoApprovalEnabled.add(chatSessionId); + } else { + this._sessionAutoApprovalEnabled.delete(chatSessionId); + } + } + + hasChatSessionAutoApproval(chatSessionId: string): boolean { + return this._sessionAutoApprovalEnabled.has(chatSessionId); + } } diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts index 19330fc97ca..1e9cb4911e9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/runInTerminalHelpers.ts @@ -177,6 +177,18 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str actions.push(new Separator()); } + + // Allow all commands for this session + actions.push({ + label: localize('allowSession', 'Allow All Commands in this Session'), + tooltip: localize('allowSessionTooltip', 'Allow this tool to run in this session without confirmation.'), + data: { + type: 'sessionApproval' + } satisfies TerminalNewAutoApproveButtonData + }); + + actions.push(new Separator()); + // Always show configure option actions.push({ label: localize('autoApprove.configure', 'Configure Auto Approve...'), diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts index c062448457e..8a70c590465 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/commandLineAnalyzer/commandLineAnalyzer.ts @@ -21,6 +21,7 @@ export interface ICommandLineAnalyzerOptions { os: OperatingSystem; treeSitterLanguage: TreeSitterCommandParserLanguage; terminalToolSessionId: string; + chatSessionId: string | undefined; } export interface ICommandLineAnalyzerResult { 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 36fbb02901e..066391005f7 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 @@ -10,6 +10,7 @@ import type { SingleOrMany } from '../../../../../../../base/common/types.js'; import { localize } from '../../../../../../../nls.js'; import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.js'; +import { ITerminalChatService } from '../../../../../terminal/browser/terminal.js'; import { IStorageService, StorageScope } from '../../../../../../../platform/storage/common/storage.js'; import { TerminalToolConfirmationStorageKeys } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js'; import { openTerminalSettingsLinkCommandId } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.js'; @@ -43,12 +44,29 @@ export class CommandLineAutoApproveAnalyzer extends Disposable implements IComma @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService private readonly _storageService: IStorageService, + @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, ) { super(); this._commandLineAutoApprover = this._register(instantiationService.createInstance(CommandLineAutoApprover)); } async analyze(options: ICommandLineAnalyzerOptions): Promise { + if (options.chatSessionId && this._terminalChatService.hasChatSessionAutoApproval(options.chatSessionId)) { + this._log('Session has auto approval enabled, auto approving command'); + const disableUri = createCommandUri('_chat.disableSessionAutoApproval', options.chatSessionId); + const mdTrustSettings = { + isTrusted: { + enabledCommands: ['_chat.disableSessionAutoApproval'] + } + }; + return { + isAutoApproved: true, + isAutoApproveAllowed: true, + disclaimers: [], + autoApproveInfo: new MarkdownString(`${localize('autoApprove.session', 'Auto approved for this session')} ([${localize('autoApprove.session.disable', 'Disable')}](${disableUri.toString()}))`, mdTrustSettings), + }; + } + let subCommands: string[] | undefined; try { subCommands = await this._treeSitterCommandParser.extractSubCommands(options.treeSitterLanguage, options.commandLine); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts index 5f20d825407..8e0bb04becd 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -412,6 +412,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { shell, treeSitterLanguage: isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash, terminalToolSessionId, + chatSessionId: context.chatSessionId, }; const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions))); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandLineAnalyzer/commandLineFileWriteAnalyzer.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandLineAnalyzer/commandLineFileWriteAnalyzer.test.ts index bd28832940a..600a29c97b2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandLineAnalyzer/commandLineFileWriteAnalyzer.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/commandLineAnalyzer/commandLineFileWriteAnalyzer.test.ts @@ -79,7 +79,8 @@ suite('CommandLineFileWriteAnalyzer', () => { shell: 'bash', os: OperatingSystem.Linux, treeSitterLanguage: TreeSitterCommandParserLanguage.Bash, - terminalToolSessionId: 'test' + terminalToolSessionId: 'test', + chatSessionId: 'test', }; const result = await analyzer.analyze(options); @@ -146,7 +147,8 @@ suite('CommandLineFileWriteAnalyzer', () => { shell: 'bash', os: OperatingSystem.Linux, treeSitterLanguage: TreeSitterCommandParserLanguage.Bash, - terminalToolSessionId: 'test' + terminalToolSessionId: 'test', + chatSessionId: 'test', }; const result = await analyzer.analyze(options); @@ -182,7 +184,8 @@ suite('CommandLineFileWriteAnalyzer', () => { shell: 'pwsh', os: OperatingSystem.Windows, treeSitterLanguage: TreeSitterCommandParserLanguage.PowerShell, - terminalToolSessionId: 'test' + terminalToolSessionId: 'test', + chatSessionId: 'test', }; const result = await analyzer.analyze(options); @@ -257,7 +260,8 @@ suite('CommandLineFileWriteAnalyzer', () => { shell: 'bash', os: OperatingSystem.Linux, treeSitterLanguage: TreeSitterCommandParserLanguage.Bash, - terminalToolSessionId: 'test' + terminalToolSessionId: 'test', + chatSessionId: 'test', }; const result = await analyzer.analyze(options); @@ -288,7 +292,8 @@ suite('CommandLineFileWriteAnalyzer', () => { shell: 'bash', os: OperatingSystem.Linux, treeSitterLanguage: TreeSitterCommandParserLanguage.Bash, - terminalToolSessionId: 'test' + terminalToolSessionId: 'test', + chatSessionId: 'test', }; const result = await analyzer.analyze(options); @@ -316,7 +321,8 @@ suite('CommandLineFileWriteAnalyzer', () => { shell: 'bash', os: OperatingSystem.Linux, treeSitterLanguage: TreeSitterCommandParserLanguage.Bash, - terminalToolSessionId: 'test' + terminalToolSessionId: 'test', + chatSessionId: 'test', }; const result = await analyzer.analyze(options); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 58e5993cb6b..09fcebd5295 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -33,11 +33,12 @@ import { TerminalToolConfirmationStorageKeys } from '../../../../chat/browser/ch import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js'; import { LocalChatSessionUri } from '../../../../chat/common/chatUri.js'; import { ILanguageModelToolsService, IPreparedToolInvocation, IToolInvocationPreparationContext, type ToolConfirmationAction } from '../../../../chat/common/languageModelToolsService.js'; -import { ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; +import { ITerminalChatService, ITerminalService, type ITerminalInstance } from '../../../../terminal/browser/terminal.js'; import { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js'; import { RunInTerminalTool, type IRunInTerminalInputParams } from '../../browser/tools/runInTerminalTool.js'; import { ShellIntegrationQuality } from '../../browser/toolTerminalCreator.js'; import { terminalChatAgentToolsConfiguration, TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js'; +import { TerminalChatService } from '../../../chat/browser/terminalChatService.js'; class TestRunInTerminalTool extends RunInTerminalTool { protected override _osBackend: Promise = Promise.resolve(OperatingSystem.Windows); @@ -81,6 +82,7 @@ suite('RunInTerminalTool', () => { fileService: () => fileService, }, store); + instantiationService.stub(ITerminalChatService, store.add(instantiationService.createInstance(TerminalChatService))); instantiationService.stub(IWorkspaceContextService, workspaceContextService); instantiationService.stub(IHistoryService, { getLastActiveWorkspaceRoot: () => undefined @@ -473,7 +475,7 @@ suite('RunInTerminalTool', () => { suite('prepareToolInvocation - custom actions for dropdown', () => { - function assertDropdownActions(result: IPreparedToolInvocation | undefined, items: ({ subCommand: SingleOrMany } | 'commandLine' | '---' | 'configure')[]) { + function assertDropdownActions(result: IPreparedToolInvocation | undefined, items: ({ subCommand: SingleOrMany } | 'commandLine' | '---' | 'configure' | 'sessionApproval')[]) { const actions = result?.confirmationMessages?.terminalCustomActions!; ok(actions, 'Expected custom actions to be defined'); @@ -488,6 +490,9 @@ suite('RunInTerminalTool', () => { if (item === 'configure') { strictEqual(action.label, 'Configure Auto Approve...'); strictEqual(action.data.type, 'configure'); + } else if (item === 'sessionApproval') { + strictEqual(action.label, 'Allow All Commands in this Session'); + strictEqual(action.data.type, 'sessionApproval'); } else if (item === 'commandLine') { strictEqual(action.label, 'Always Allow Exact Command Line'); strictEqual(action.data.type, 'newRule'); @@ -519,6 +524,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'npm run build' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -533,6 +540,8 @@ suite('RunInTerminalTool', () => { assertDropdownActions(result, [ { subCommand: 'foo' }, '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -560,6 +569,8 @@ suite('RunInTerminalTool', () => { assertConfirmationRequired(result, 'Run `bash` command?'); assertDropdownActions(result, [ + 'sessionApproval', + '---', 'configure', ]); }); @@ -575,6 +586,8 @@ suite('RunInTerminalTool', () => { { subCommand: ['npm install', 'npm run build'] }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -593,6 +606,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'foo' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -625,6 +640,8 @@ suite('RunInTerminalTool', () => { { subCommand: ['foo', 'bar'] }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -640,6 +657,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'git status' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -655,6 +674,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'npm test' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -670,6 +691,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'npm run build' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -685,6 +708,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'yarn run test' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -700,6 +725,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'foo' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -715,6 +742,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'npm run abc' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -730,6 +759,8 @@ suite('RunInTerminalTool', () => { { subCommand: ['npm run build', 'git status'] }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -745,6 +776,8 @@ suite('RunInTerminalTool', () => { { subCommand: ['git push', 'echo'] }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -760,6 +793,8 @@ suite('RunInTerminalTool', () => { { subCommand: ['git status', 'git log'] }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -775,6 +810,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'foo' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -787,6 +824,8 @@ suite('RunInTerminalTool', () => { assertConfirmationRequired(result); assertDropdownActions(result, [ + 'sessionApproval', + '---', 'configure', ]); }); @@ -802,6 +841,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'npm test' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -817,6 +858,8 @@ suite('RunInTerminalTool', () => { { subCommand: 'foo' }, 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -831,6 +874,8 @@ suite('RunInTerminalTool', () => { assertDropdownActions(result, [ 'commandLine', '---', + 'sessionApproval', + '---', 'configure', ]); }); @@ -847,6 +892,8 @@ suite('RunInTerminalTool', () => { assertConfirmationRequired(result); assertDropdownActions(result, [ + 'sessionApproval', + '---', 'configure', ]); }); @@ -989,6 +1036,34 @@ suite('RunInTerminalTool', () => { }); }); + suite('session auto approval', () => { + test('should auto approve all commands when session has auto approval enabled', async () => { + const sessionId = 'test-session-123'; + const terminalChatService = instantiationService.get(ITerminalChatService); + + const context: IToolInvocationPreparationContext = { + parameters: { + command: 'rm dangerous-file.txt', + explanation: 'Remove a file', + isBackground: false + } as IRunInTerminalInputParams, + chatSessionId: sessionId + } as IToolInvocationPreparationContext; + + let result = await runInTerminalTool.prepareToolInvocation(context, CancellationToken.None); + assertConfirmationRequired(result); + + terminalChatService.setChatSessionAutoApproval(sessionId, true); + + result = await runInTerminalTool.prepareToolInvocation(context, CancellationToken.None); + assertAutoApproved(result); + + const terminalData = result!.toolSpecificData as IChatTerminalToolInvocationData; + ok(terminalData.autoApproveInfo, 'Expected autoApproveInfo to be defined'); + ok(terminalData.autoApproveInfo.value.includes('Auto approved for this session'), 'Expected session approval message'); + }); + }); + suite('TerminalProfileFetcher', () => { suite('getCopilotProfile', () => { (isWindows ? test : test.skip)('should return custom profile when configured', async () => {