mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-20 02:08:47 +00:00
Merge pull request #277522 from microsoft/tyriar/260819
Allow approving terminal tool for session
This commit is contained in:
@@ -30,6 +30,7 @@ import { IKeybindingService } from '../../../../../../platform/keybinding/common
|
|||||||
import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js';
|
import { IMarkdownRenderer } from '../../../../../../platform/markdown/browser/markdownRenderer.js';
|
||||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js';
|
import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js';
|
||||||
import { IPreferencesService } from '../../../../../services/preferences/common/preferences.js';
|
import { IPreferencesService } from '../../../../../services/preferences/common/preferences.js';
|
||||||
|
import { ITerminalChatService } from '../../../../terminal/browser/terminal.js';
|
||||||
import { TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js';
|
import { TerminalContribSettingId } from '../../../../terminal/terminalContribExports.js';
|
||||||
import { migrateLegacyTerminalToolSpecificData } from '../../../common/chat.js';
|
import { migrateLegacyTerminalToolSpecificData } from '../../../common/chat.js';
|
||||||
import { ChatContextKeys } from '../../../common/chatContextKeys.js';
|
import { ChatContextKeys } from '../../../common/chatContextKeys.js';
|
||||||
@@ -42,7 +43,7 @@ import { ChatCustomConfirmationWidget, IChatConfirmationButton } from '../chatCo
|
|||||||
import { EditorPool } from '../chatContentCodePools.js';
|
import { EditorPool } from '../chatContentCodePools.js';
|
||||||
import { IChatContentPartRenderContext } from '../chatContentParts.js';
|
import { IChatContentPartRenderContext } from '../chatContentParts.js';
|
||||||
import { ChatMarkdownContentPart } from '../chatMarkdownContentPart.js';
|
import { ChatMarkdownContentPart } from '../chatMarkdownContentPart.js';
|
||||||
import { openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js';
|
import { disableSessionAutoApprovalCommandId, openTerminalSettingsLinkCommandId } from './chatTerminalToolProgressPart.js';
|
||||||
import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js';
|
import { BaseChatToolInvocationSubPart } from './chatToolInvocationSubPart.js';
|
||||||
|
|
||||||
export const enum TerminalToolConfirmationStorageKeys {
|
export const enum TerminalToolConfirmationStorageKeys {
|
||||||
@@ -61,7 +62,8 @@ export type TerminalNewAutoApproveButtonData = (
|
|||||||
{ type: 'enable' } |
|
{ type: 'enable' } |
|
||||||
{ type: 'configure' } |
|
{ type: 'configure' } |
|
||||||
{ type: 'skip' } |
|
{ type: 'skip' } |
|
||||||
{ type: 'newRule'; rule: ITerminalNewAutoApproveRule | ITerminalNewAutoApproveRule[] }
|
{ type: 'newRule'; rule: ITerminalNewAutoApproveRule | ITerminalNewAutoApproveRule[] } |
|
||||||
|
{ type: 'sessionApproval' }
|
||||||
);
|
);
|
||||||
|
|
||||||
export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationSubPart {
|
export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationSubPart {
|
||||||
@@ -87,6 +89,7 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS
|
|||||||
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
|
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
|
||||||
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
@IPreferencesService private readonly preferencesService: IPreferencesService,
|
||||||
@IStorageService private readonly storageService: IStorageService,
|
@IStorageService private readonly storageService: IStorageService,
|
||||||
|
@ITerminalChatService private readonly terminalChatService: ITerminalChatService,
|
||||||
@ITextModelService textModelService: ITextModelService,
|
@ITextModelService textModelService: ITextModelService,
|
||||||
@IHoverService hoverService: IHoverService,
|
@IHoverService hoverService: IHoverService,
|
||||||
) {
|
) {
|
||||||
@@ -302,6 +305,19 @@ export class ChatTerminalToolConfirmationSubPart extends BaseChatToolInvocationS
|
|||||||
doComplete = false;
|
doComplete = false;
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -861,6 +861,7 @@ MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const openTerminalSettingsLinkCommandId = '_chat.openTerminalSettingsLink';
|
export const openTerminalSettingsLinkCommandId = '_chat.openTerminalSettingsLink';
|
||||||
|
export const disableSessionAutoApprovalCommandId = '_chat.disableSessionAutoApproval';
|
||||||
|
|
||||||
CommandsRegistry.registerCommand(openTerminalSettingsLinkCommandId, async (accessor, scopeRaw: string) => {
|
CommandsRegistry.registerCommand(openTerminalSettingsLinkCommandId, async (accessor, scopeRaw: string) => {
|
||||||
const preferencesService = accessor.get(IPreferencesService);
|
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 {
|
class ToggleChatTerminalOutputAction extends Action implements IAction {
|
||||||
private _expanded = false;
|
private _expanded = false;
|
||||||
|
|||||||
@@ -203,6 +203,20 @@ export interface ITerminalChatService {
|
|||||||
* @returns The most recent progress part or undefined if none exist
|
* @returns The most recent progress part or undefined if none exist
|
||||||
*/
|
*/
|
||||||
getMostRecentProgressPart(): IChatTerminalToolProgressPart | undefined;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ
|
|||||||
private readonly _hasToolTerminalContext: IContextKey<boolean>;
|
private readonly _hasToolTerminalContext: IContextKey<boolean>;
|
||||||
private readonly _hasHiddenToolTerminalContext: IContextKey<boolean>;
|
private readonly _hasHiddenToolTerminalContext: IContextKey<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ILogService private readonly _logService: ILogService,
|
@ILogService private readonly _logService: ILogService,
|
||||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||||
@@ -83,6 +89,11 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ
|
|||||||
this._terminalInstancesByToolSessionId.delete(terminalToolSessionId);
|
this._terminalInstancesByToolSessionId.delete(terminalToolSessionId);
|
||||||
this._toolSessionIdByTerminalInstance.delete(instance);
|
this._toolSessionIdByTerminalInstance.delete(instance);
|
||||||
this._terminalInstanceListenersByToolSessionId.deleteAndDispose(terminalToolSessionId);
|
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._persistToStorage();
|
||||||
this._updateHasToolTerminalContextKeys();
|
this._updateHasToolTerminalContextKeys();
|
||||||
}
|
}
|
||||||
@@ -279,4 +290,16 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ
|
|||||||
const hiddenTerminalCount = this.getToolSessionTerminalInstances(true).length;
|
const hiddenTerminalCount = this.getToolSessionTerminalInstances(true).length;
|
||||||
this._hasHiddenToolTerminalContext.set(hiddenTerminalCount > 0);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -177,6 +177,18 @@ export function generateAutoApproveActions(commandLine: string, subCommands: str
|
|||||||
actions.push(new Separator());
|
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
|
// Always show configure option
|
||||||
actions.push({
|
actions.push({
|
||||||
label: localize('autoApprove.configure', 'Configure Auto Approve...'),
|
label: localize('autoApprove.configure', 'Configure Auto Approve...'),
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export interface ICommandLineAnalyzerOptions {
|
|||||||
os: OperatingSystem;
|
os: OperatingSystem;
|
||||||
treeSitterLanguage: TreeSitterCommandParserLanguage;
|
treeSitterLanguage: TreeSitterCommandParserLanguage;
|
||||||
terminalToolSessionId: string;
|
terminalToolSessionId: string;
|
||||||
|
chatSessionId: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICommandLineAnalyzerResult {
|
export interface ICommandLineAnalyzerResult {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import type { SingleOrMany } from '../../../../../../../base/common/types.js';
|
|||||||
import { localize } from '../../../../../../../nls.js';
|
import { localize } from '../../../../../../../nls.js';
|
||||||
import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js';
|
import { IConfigurationService } from '../../../../../../../platform/configuration/common/configuration.js';
|
||||||
import { IInstantiationService } from '../../../../../../../platform/instantiation/common/instantiation.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 { IStorageService, StorageScope } from '../../../../../../../platform/storage/common/storage.js';
|
||||||
import { TerminalToolConfirmationStorageKeys } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js';
|
import { TerminalToolConfirmationStorageKeys } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolConfirmationSubPart.js';
|
||||||
import { openTerminalSettingsLinkCommandId } from '../../../../../chat/browser/chatContentParts/toolInvocationParts/chatTerminalToolProgressPart.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,
|
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||||
@IInstantiationService instantiationService: IInstantiationService,
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
@IStorageService private readonly _storageService: IStorageService,
|
@IStorageService private readonly _storageService: IStorageService,
|
||||||
|
@ITerminalChatService private readonly _terminalChatService: ITerminalChatService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._commandLineAutoApprover = this._register(instantiationService.createInstance(CommandLineAutoApprover));
|
this._commandLineAutoApprover = this._register(instantiationService.createInstance(CommandLineAutoApprover));
|
||||||
}
|
}
|
||||||
|
|
||||||
async analyze(options: ICommandLineAnalyzerOptions): Promise<ICommandLineAnalyzerResult> {
|
async analyze(options: ICommandLineAnalyzerOptions): Promise<ICommandLineAnalyzerResult> {
|
||||||
|
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;
|
let subCommands: string[] | undefined;
|
||||||
try {
|
try {
|
||||||
subCommands = await this._treeSitterCommandParser.extractSubCommands(options.treeSitterLanguage, options.commandLine);
|
subCommands = await this._treeSitterCommandParser.extractSubCommands(options.treeSitterLanguage, options.commandLine);
|
||||||
|
|||||||
@@ -412,6 +412,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl {
|
|||||||
shell,
|
shell,
|
||||||
treeSitterLanguage: isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash,
|
treeSitterLanguage: isPowerShell(shell, os) ? TreeSitterCommandParserLanguage.PowerShell : TreeSitterCommandParserLanguage.Bash,
|
||||||
terminalToolSessionId,
|
terminalToolSessionId,
|
||||||
|
chatSessionId: context.chatSessionId,
|
||||||
};
|
};
|
||||||
const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions)));
|
const commandLineAnalyzerResults = await Promise.all(this._commandLineAnalyzers.map(e => e.analyze(commandLineAnalyzerOptions)));
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ suite('CommandLineFileWriteAnalyzer', () => {
|
|||||||
shell: 'bash',
|
shell: 'bash',
|
||||||
os: OperatingSystem.Linux,
|
os: OperatingSystem.Linux,
|
||||||
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
||||||
terminalToolSessionId: 'test'
|
terminalToolSessionId: 'test',
|
||||||
|
chatSessionId: 'test',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await analyzer.analyze(options);
|
const result = await analyzer.analyze(options);
|
||||||
@@ -146,7 +147,8 @@ suite('CommandLineFileWriteAnalyzer', () => {
|
|||||||
shell: 'bash',
|
shell: 'bash',
|
||||||
os: OperatingSystem.Linux,
|
os: OperatingSystem.Linux,
|
||||||
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
||||||
terminalToolSessionId: 'test'
|
terminalToolSessionId: 'test',
|
||||||
|
chatSessionId: 'test',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await analyzer.analyze(options);
|
const result = await analyzer.analyze(options);
|
||||||
@@ -182,7 +184,8 @@ suite('CommandLineFileWriteAnalyzer', () => {
|
|||||||
shell: 'pwsh',
|
shell: 'pwsh',
|
||||||
os: OperatingSystem.Windows,
|
os: OperatingSystem.Windows,
|
||||||
treeSitterLanguage: TreeSitterCommandParserLanguage.PowerShell,
|
treeSitterLanguage: TreeSitterCommandParserLanguage.PowerShell,
|
||||||
terminalToolSessionId: 'test'
|
terminalToolSessionId: 'test',
|
||||||
|
chatSessionId: 'test',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await analyzer.analyze(options);
|
const result = await analyzer.analyze(options);
|
||||||
@@ -257,7 +260,8 @@ suite('CommandLineFileWriteAnalyzer', () => {
|
|||||||
shell: 'bash',
|
shell: 'bash',
|
||||||
os: OperatingSystem.Linux,
|
os: OperatingSystem.Linux,
|
||||||
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
||||||
terminalToolSessionId: 'test'
|
terminalToolSessionId: 'test',
|
||||||
|
chatSessionId: 'test',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await analyzer.analyze(options);
|
const result = await analyzer.analyze(options);
|
||||||
@@ -288,7 +292,8 @@ suite('CommandLineFileWriteAnalyzer', () => {
|
|||||||
shell: 'bash',
|
shell: 'bash',
|
||||||
os: OperatingSystem.Linux,
|
os: OperatingSystem.Linux,
|
||||||
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
||||||
terminalToolSessionId: 'test'
|
terminalToolSessionId: 'test',
|
||||||
|
chatSessionId: 'test',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await analyzer.analyze(options);
|
const result = await analyzer.analyze(options);
|
||||||
@@ -316,7 +321,8 @@ suite('CommandLineFileWriteAnalyzer', () => {
|
|||||||
shell: 'bash',
|
shell: 'bash',
|
||||||
os: OperatingSystem.Linux,
|
os: OperatingSystem.Linux,
|
||||||
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
treeSitterLanguage: TreeSitterCommandParserLanguage.Bash,
|
||||||
terminalToolSessionId: 'test'
|
terminalToolSessionId: 'test',
|
||||||
|
chatSessionId: 'test',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await analyzer.analyze(options);
|
const result = await analyzer.analyze(options);
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ import { TerminalToolConfirmationStorageKeys } from '../../../../chat/browser/ch
|
|||||||
import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js';
|
import { IChatService, type IChatTerminalToolInvocationData } from '../../../../chat/common/chatService.js';
|
||||||
import { LocalChatSessionUri } from '../../../../chat/common/chatUri.js';
|
import { LocalChatSessionUri } from '../../../../chat/common/chatUri.js';
|
||||||
import { ILanguageModelToolsService, IPreparedToolInvocation, IToolInvocationPreparationContext, type ToolConfirmationAction } from '../../../../chat/common/languageModelToolsService.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 { ITerminalProfileResolverService } from '../../../../terminal/common/terminal.js';
|
||||||
import { RunInTerminalTool, type IRunInTerminalInputParams } from '../../browser/tools/runInTerminalTool.js';
|
import { RunInTerminalTool, type IRunInTerminalInputParams } from '../../browser/tools/runInTerminalTool.js';
|
||||||
import { ShellIntegrationQuality } from '../../browser/toolTerminalCreator.js';
|
import { ShellIntegrationQuality } from '../../browser/toolTerminalCreator.js';
|
||||||
import { terminalChatAgentToolsConfiguration, TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js';
|
import { terminalChatAgentToolsConfiguration, TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js';
|
||||||
|
import { TerminalChatService } from '../../../chat/browser/terminalChatService.js';
|
||||||
|
|
||||||
class TestRunInTerminalTool extends RunInTerminalTool {
|
class TestRunInTerminalTool extends RunInTerminalTool {
|
||||||
protected override _osBackend: Promise<OperatingSystem> = Promise.resolve(OperatingSystem.Windows);
|
protected override _osBackend: Promise<OperatingSystem> = Promise.resolve(OperatingSystem.Windows);
|
||||||
@@ -81,6 +82,7 @@ suite('RunInTerminalTool', () => {
|
|||||||
fileService: () => fileService,
|
fileService: () => fileService,
|
||||||
}, store);
|
}, store);
|
||||||
|
|
||||||
|
instantiationService.stub(ITerminalChatService, store.add(instantiationService.createInstance(TerminalChatService)));
|
||||||
instantiationService.stub(IWorkspaceContextService, workspaceContextService);
|
instantiationService.stub(IWorkspaceContextService, workspaceContextService);
|
||||||
instantiationService.stub(IHistoryService, {
|
instantiationService.stub(IHistoryService, {
|
||||||
getLastActiveWorkspaceRoot: () => undefined
|
getLastActiveWorkspaceRoot: () => undefined
|
||||||
@@ -473,7 +475,7 @@ suite('RunInTerminalTool', () => {
|
|||||||
|
|
||||||
suite('prepareToolInvocation - custom actions for dropdown', () => {
|
suite('prepareToolInvocation - custom actions for dropdown', () => {
|
||||||
|
|
||||||
function assertDropdownActions(result: IPreparedToolInvocation | undefined, items: ({ subCommand: SingleOrMany<string> } | 'commandLine' | '---' | 'configure')[]) {
|
function assertDropdownActions(result: IPreparedToolInvocation | undefined, items: ({ subCommand: SingleOrMany<string> } | 'commandLine' | '---' | 'configure' | 'sessionApproval')[]) {
|
||||||
const actions = result?.confirmationMessages?.terminalCustomActions!;
|
const actions = result?.confirmationMessages?.terminalCustomActions!;
|
||||||
ok(actions, 'Expected custom actions to be defined');
|
ok(actions, 'Expected custom actions to be defined');
|
||||||
|
|
||||||
@@ -488,6 +490,9 @@ suite('RunInTerminalTool', () => {
|
|||||||
if (item === 'configure') {
|
if (item === 'configure') {
|
||||||
strictEqual(action.label, 'Configure Auto Approve...');
|
strictEqual(action.label, 'Configure Auto Approve...');
|
||||||
strictEqual(action.data.type, 'configure');
|
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') {
|
} else if (item === 'commandLine') {
|
||||||
strictEqual(action.label, 'Always Allow Exact Command Line');
|
strictEqual(action.label, 'Always Allow Exact Command Line');
|
||||||
strictEqual(action.data.type, 'newRule');
|
strictEqual(action.data.type, 'newRule');
|
||||||
@@ -519,6 +524,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'npm run build' },
|
{ subCommand: 'npm run build' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -533,6 +540,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
assertDropdownActions(result, [
|
assertDropdownActions(result, [
|
||||||
{ subCommand: 'foo' },
|
{ subCommand: 'foo' },
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -560,6 +569,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
|
|
||||||
assertConfirmationRequired(result, 'Run `bash` command?');
|
assertConfirmationRequired(result, 'Run `bash` command?');
|
||||||
assertDropdownActions(result, [
|
assertDropdownActions(result, [
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -575,6 +586,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: ['npm install', 'npm run build'] },
|
{ subCommand: ['npm install', 'npm run build'] },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -593,6 +606,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'foo' },
|
{ subCommand: 'foo' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -625,6 +640,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: ['foo', 'bar'] },
|
{ subCommand: ['foo', 'bar'] },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -640,6 +657,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'git status' },
|
{ subCommand: 'git status' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -655,6 +674,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'npm test' },
|
{ subCommand: 'npm test' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -670,6 +691,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'npm run build' },
|
{ subCommand: 'npm run build' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -685,6 +708,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'yarn run test' },
|
{ subCommand: 'yarn run test' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -700,6 +725,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'foo' },
|
{ subCommand: 'foo' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -715,6 +742,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'npm run abc' },
|
{ subCommand: 'npm run abc' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -730,6 +759,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: ['npm run build', 'git status'] },
|
{ subCommand: ['npm run build', 'git status'] },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -745,6 +776,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: ['git push', 'echo'] },
|
{ subCommand: ['git push', 'echo'] },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -760,6 +793,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: ['git status', 'git log'] },
|
{ subCommand: ['git status', 'git log'] },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -775,6 +810,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'foo' },
|
{ subCommand: 'foo' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -787,6 +824,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
|
|
||||||
assertConfirmationRequired(result);
|
assertConfirmationRequired(result);
|
||||||
assertDropdownActions(result, [
|
assertDropdownActions(result, [
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -802,6 +841,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'npm test' },
|
{ subCommand: 'npm test' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -817,6 +858,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
{ subCommand: 'foo' },
|
{ subCommand: 'foo' },
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -831,6 +874,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
assertDropdownActions(result, [
|
assertDropdownActions(result, [
|
||||||
'commandLine',
|
'commandLine',
|
||||||
'---',
|
'---',
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'configure',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -847,6 +892,8 @@ suite('RunInTerminalTool', () => {
|
|||||||
|
|
||||||
assertConfirmationRequired(result);
|
assertConfirmationRequired(result);
|
||||||
assertDropdownActions(result, [
|
assertDropdownActions(result, [
|
||||||
|
'sessionApproval',
|
||||||
|
'---',
|
||||||
'configure',
|
'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('TerminalProfileFetcher', () => {
|
||||||
suite('getCopilotProfile', () => {
|
suite('getCopilotProfile', () => {
|
||||||
(isWindows ? test : test.skip)('should return custom profile when configured', async () => {
|
(isWindows ? test : test.skip)('should return custom profile when configured', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user