Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b45d417
autopilot mode + /yolo commands
justschen Feb 21, 2026
7373f28
fix tests
justschen Feb 21, 2026
17a55b4
revert fix
justschen Feb 21, 2026
1728c43
fix disposable leak
justschen Feb 21, 2026
5b598d3
Merge branch 'main' into justin/whimsicott
justschen Feb 21, 2026
1d5276c
address a few comments, make sure it works when switching sessions
justschen Feb 21, 2026
06e406b
make some tests
justschen Feb 21, 2026
90eafa8
fix tests
justschen Feb 21, 2026
a4c2e3f
Merge branch 'main' into justin/whimsicott
justschen Feb 21, 2026
7799adb
Merge branch 'main' into justin/whimsicott
justschen Feb 23, 2026
2bbaf61
Merge branch 'main' into justin/whimsicott
justschen Feb 23, 2026
b476330
Merge branch 'main' into justin/whimsicott
justschen Feb 24, 2026
f6bee64
Merge branch 'main' into justin/whimsicott
justschen Feb 24, 2026
cad63e2
some reverts to cleaner state
justschen Feb 24, 2026
7bd80e7
add secondary toolbar, permissions
justschen Feb 25, 2026
712d8cd
Merge branch 'main' into justin/whimsicott
justschen Feb 26, 2026
0cc7d20
don't use query selector, surface toolbar in the template
justschen Feb 26, 2026
a2847a1
UI polish: context usage widget, secondary toolbar layout, theme tweaks
daviddossett Feb 26, 2026
d2e4f30
Merge branch 'main' into justin/whimsicott
justschen Feb 26, 2026
bd778df
update names
justschen Feb 26, 2026
a014c3e
update api for tool call limits
justschen Feb 26, 2026
f1b6205
add true autopilot
justschen Feb 27, 2026
b47a039
Merge branch 'main' into justin/whimsicott
justschen Feb 27, 2026
524942b
Merge branch 'main' into justin/whimsicott
justschen Feb 27, 2026
6e45063
move error retry logic to extension
justschen Feb 28, 2026
1333e22
address some more comments
justschen Mar 1, 2026
c475dda
make sure to hide tool
justschen Mar 2, 2026
4cf070d
bump version #
justschen Mar 2, 2026
d0072c1
better tool description
justschen Mar 2, 2026
7bbe648
Merge branch 'main' into justin/whimsicott
justschen Mar 2, 2026
ce7599c
fix conflict
justschen Mar 2, 2026
5561c52
enterprise restrictions
justschen Mar 3, 2026
c21c3a2
revert some stuff, fix sessions window containers
justschen Mar 4, 2026
63ccea8
fix actions
justschen Mar 4, 2026
d0c2f26
Merge branch 'main' into justin/whimsicott
justschen Mar 4, 2026
85e9eb8
fix delegate vs. session target
justschen Mar 4, 2026
d7e7821
Merge branch 'main' into justin/whimsicott
justschen Mar 4, 2026
044aaab
Merge branch 'main' into justin/whimsicott
justschen Mar 4, 2026
ddbd4ca
fix compile + add setting
justschen Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion extensions/theme-2026/themes/2026-dark.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"input.background": "#191A1B",
"input.border": "#333536FF",
"input.foreground": "#bfbfbf",
"input.placeholderForeground": "#777777",
"input.placeholderForeground": "#555555",
"inputOption.activeBackground": "#3994BC33",
"inputOption.activeForeground": "#bfbfbf",
"inputOption.activeBorder": "#2A2B2CFF",
Expand Down
1 change: 1 addition & 0 deletions src/vs/platform/actions/common/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ export class MenuId {
static readonly ChatExecute = new MenuId('ChatExecute');
static readonly ChatExecuteQueue = new MenuId('ChatExecuteQueue');
static readonly ChatInput = new MenuId('ChatInput');
static readonly ChatInputSecondary = new MenuId('ChatInputSecondary');
static readonly ChatInputSide = new MenuId('ChatInputSide');
static readonly ChatModePicker = new MenuId('ChatModePicker');
static readonly ChatEditingWidgetToolbar = new MenuId('ChatEditingWidgetToolbar');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ const _allApiProposals = {
},
chatParticipantPrivate: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatParticipantPrivate.d.ts',
version: 14
version: 15
},
chatPromptFiles: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatPromptFiles.d.ts',
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3440,6 +3440,7 @@ export namespace ChatAgentRequest {
editedFileEvents: request.editedFileEvents,
modeInstructions: request.modeInstructions?.content,
modeInstructions2: ChatRequestModeInstructions.to(request.modeInstructions),
permissionLevel: request.permissionLevel,
subAgentInvocationId: request.subAgentInvocationId,
subAgentName: request.subAgentName,
parentRequestId: request.parentRequestId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@ import { KeybindingWeight } from '../../../../../platform/keybinding/common/keyb
import { ILogService } from '../../../../../platform/log/common/log.js';
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';
import { IViewsService } from '../../../../services/views/common/viewsService.js';
import { IsSessionsWindowContext } from '../../../../common/contextkeys.js';
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { getModeNameForTelemetry, IChatMode, IChatModeService } from '../../common/chatModes.js';
import { chatVariableLeader } from '../../common/requestParser/chatParserTypes.js';
import { ChatStopCancellationNoopClassification, ChatStopCancellationNoopEvent, ChatStopCancellationNoopEventName, IChatService } from '../../common/chatService/chatService.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, } from '../../common/constants.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js';
import { ILanguageModelChatMetadata } from '../../common/languageModels.js';
import { ILanguageModelToolsService } from '../../common/tools/languageModelToolsService.js';
import { isInClaudeAgentsFolder } from '../../common/promptSyntax/config/promptFileLocations.js';
Expand Down Expand Up @@ -440,6 +441,44 @@ export class OpenModelPickerAction extends Action2 {
}
}
}

export class OpenPermissionPickerAction extends Action2 {
static readonly ID = 'workbench.action.chat.openPermissionPicker';

constructor() {
super({
id: OpenPermissionPickerAction.ID,
title: localize2('interactive.openPermissionPicker.label', "Open Permission Picker"),
tooltip: localize('setPermissionLevel', "Set Permissions"),
category: CHAT_CATEGORY,
f1: false,
precondition: ChatContextKeys.enabled,
menu: {
id: MenuId.ChatInputSecondary,
order: 10,
group: 'navigation',
when:
ContextKeyExpr.and(
ChatContextKeys.enabled,
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
ChatContextKeys.chatModeKind.notEqualsTo(ChatModeKind.Ask),
ChatContextKeys.inQuickChat.negate(),
ChatContextKeys.lockedToCodingAgent.negate(),
IsSessionsWindowContext.negate(),
)
}
});
}

override async run(accessor: ServicesAccessor): Promise<void> {
const widgetService = accessor.get(IChatWidgetService);
const widget = widgetService.lastFocusedWidget;
if (widget) {
widget.input.openPermissionPicker();
}
}
}

export class OpenModePickerAction extends Action2 {
static readonly ID = 'workbench.action.chat.openModePicker';

Expand Down Expand Up @@ -508,6 +547,18 @@ export class OpenSessionTargetPickerAction extends Action2 {
ChatContextKeys.enabled,
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
ChatContextKeys.inQuickChat.negate(),
ChatContextKeys.chatSessionIsEmpty,
IsSessionsWindowContext),
group: 'navigation',
},
{
id: MenuId.ChatInputSecondary,
order: 0,
when: ContextKeyExpr.and(
ChatContextKeys.enabled,
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
ChatContextKeys.inQuickChat.negate(),
IsSessionsWindowContext.negate(),
ChatContextKeys.chatSessionIsEmpty),
group: 'navigation',
},
Expand Down Expand Up @@ -543,7 +594,19 @@ export class OpenDelegationPickerAction extends Action2 {
ChatContextKeys.enabled,
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
ChatContextKeys.inQuickChat.negate(),
ChatContextKeys.chatSessionIsEmpty.negate()),
ChatContextKeys.chatSessionIsEmpty.negate(),
IsSessionsWindowContext),
group: 'navigation',
},
{
id: MenuId.ChatInputSecondary,
order: 0.5,
when: ContextKeyExpr.and(
ChatContextKeys.enabled,
ChatContextKeys.location.isEqualTo(ChatAgentLocation.Chat),
ChatContextKeys.inQuickChat.negate(),
ChatContextKeys.chatSessionIsEmpty.negate(),
IsSessionsWindowContext.negate()),
group: 'navigation',
},
]
Expand Down Expand Up @@ -576,7 +639,18 @@ export class OpenWorkspacePickerAction extends Action2 {
order: 0.6,
when: ContextKeyExpr.and(
ChatContextKeys.inAgentSessionsWelcome,
ChatContextKeys.chatSessionType.isEqualTo('local')
ChatContextKeys.chatSessionType.isEqualTo('local'),
IsSessionsWindowContext
),
group: 'navigation',
},
{
id: MenuId.ChatInputSecondary,
order: 0.6,
when: ContextKeyExpr.and(
ChatContextKeys.inAgentSessionsWelcome,
ChatContextKeys.chatSessionType.isEqualTo('local'),
IsSessionsWindowContext.negate()
),
group: 'navigation',
},
Expand All @@ -594,7 +668,7 @@ export class ChatSessionPrimaryPickerAction extends Action2 {
constructor() {
super({
id: ChatSessionPrimaryPickerAction.ID,
title: localize2('interactive.openChatSessionPrimaryPicker.label', "Open Model Picker"),
title: localize2('interactive.openChatSessionPrimaryPicker.label', "Open Primary Session Picker"),
category: CHAT_CATEGORY,
f1: false,
precondition: ChatContextKeys.enabled,
Expand Down Expand Up @@ -954,6 +1028,7 @@ export function registerChatExecuteActions() {
registerAction2(ToggleChatModeAction);
registerAction2(SwitchToNextModelAction);
registerAction2(OpenModelPickerAction);
registerAction2(OpenPermissionPickerAction);
registerAction2(OpenModePickerAction);
registerAction2(OpenSessionTargetPickerAction);
registerAction2(OpenDelegationPickerAction);
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,12 @@ configurationRegistry.registerConfiguration({
scope: ConfigurationScope.APPLICATION_MACHINE,
tags: ['experimental', 'advanced'],
},
[ChatConfiguration.AutopilotEnabled]: {
type: 'boolean',
markdownDescription: nls.localize('chat.autopilot.enabled', "Controls whether the Autopilot mode is available in the permissions picker. When enabled, Autopilot auto-approves all tool calls and continues until the task is done."),
default: true,
tags: ['experimental'],
},
[ChatConfiguration.GlobalAutoApprove]: {
default: false,
markdownDescription: globalAutoApproveDescription.value,
Expand Down
6 changes: 6 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,12 @@ export interface IChatWidgetViewOptions {
* redirect to a different workspace rather than executing locally.
*/
submitHandler?: (query: string, mode: ChatModeKind) => Promise<boolean>;

/**
* Whether we are running in the sessions window.
* When true, the secondary toolbar (permissions picker) is hidden.
*/
isSessionsWindow?: boolean;
}

export interface IChatViewViewContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { ChatRequestToolReferenceEntry, toToolSetVariableEntry, toToolVariableEntry } from '../../common/attachments/chatVariableEntries.js';
import { IVariableReference } from '../../common/chatModes.js';
import { ConfirmedReason, IChatService, IChatToolInvocation, ToolConfirmKind } from '../../common/chatService/chatService.js';
import { ChatConfiguration } from '../../common/constants.js';
import { ChatConfiguration, isAutoApproveLevel } from '../../common/constants.js';
import { ILanguageModelChatMetadata } from '../../common/languageModels.js';
import { IChatModel, IChatRequestModel } from '../../common/model/chatModel.js';
import { ChatToolInvocation } from '../../common/model/chatProgressTypes/chatToolInvocation.js';
Expand Down Expand Up @@ -641,7 +641,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
this.ensureToolDetails(dto, toolResult, tool.data);

const afterExecuteState = await toolInvocation?.didExecuteTool(toolResult, undefined, () =>
this.shouldAutoConfirmPostExecution(tool.data.id, tool.data.runsInWorkspace, tool.data.source, dto.parameters, dto.context?.sessionResource));
this.shouldAutoConfirmPostExecution(tool.data.id, tool.data.runsInWorkspace, tool.data.source, dto.parameters, dto.context?.sessionResource, dto.chatRequestId));

if (toolInvocation && afterExecuteState?.type === IChatToolInvocation.StateKind.WaitingForPostApproval) {
const postConfirm = await IChatToolInvocation.awaitPostConfirmation(toolInvocation, token);
Expand Down Expand Up @@ -791,7 +791,7 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
}

// No hook decision - use normal auto-confirm logic
const autoConfirmed = await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace, tool.data.source, dto.parameters, sessionResource);
const autoConfirmed = await this.shouldAutoConfirm(tool.data.id, tool.data.runsInWorkspace, tool.data.source, dto.parameters, sessionResource, dto.chatRequestId);
return { autoConfirmed, preparedInvocation };
}

Expand Down Expand Up @@ -996,6 +996,15 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
});
}

/**
* Returns true if enterprise policy has explicitly disabled the global auto-approve setting.
* When this is the case, Bypass Approvals and Autopilot permission levels should not auto-approve tools.
*/
private _isAutoApprovePolicyRestricted(): boolean {
const inspected = this._configurationService.inspect<boolean>(ChatConfiguration.GlobalAutoApprove);
return inspected.policyValue === false;
}

private getEligibleForAutoApprovalSpecialCase(toolData: IToolData): string | undefined {
if (toolData.id === 'vscode_fetchWebPage_internal') {
return 'fetch';
Expand Down Expand Up @@ -1040,12 +1049,22 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
return true;
}

private async shouldAutoConfirm(toolId: string, runsInWorkspace: boolean | undefined, source: ToolDataSource, parameters: unknown, chatSessionResource: URI | undefined): Promise<ConfirmedReason | undefined> {
private async shouldAutoConfirm(toolId: string, runsInWorkspace: boolean | undefined, source: ToolDataSource, parameters: unknown, chatSessionResource: URI | undefined, chatRequestId: string | undefined): Promise<ConfirmedReason | undefined> {
const tool = this._tools.get(toolId);
if (!tool) {
return undefined;
}

// Auto-Approve All permission level bypasses all tool confirmations,
// unless enterprise policy has explicitly disabled global auto-approve.
if (chatSessionResource) {
const model = this._chatService.getSession(chatSessionResource);
const request = model?.getRequests().at(-1);
if (isAutoApproveLevel(request?.modeInfo?.permissionLevel) && !this._isAutoApprovePolicyRestricted()) {
return { type: ToolConfirmKind.ConfirmationNotNeeded, reason: 'auto-approve-all' };
}
}

if (!this.isToolEligibleForAutoApproval(tool.data)) {
return undefined;
}
Expand Down Expand Up @@ -1077,7 +1096,17 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
return undefined;
}

private async shouldAutoConfirmPostExecution(toolId: string, runsInWorkspace: boolean | undefined, source: ToolDataSource, parameters: unknown, chatSessionResource: URI | undefined): Promise<ConfirmedReason | undefined> {
private async shouldAutoConfirmPostExecution(toolId: string, runsInWorkspace: boolean | undefined, source: ToolDataSource, parameters: unknown, chatSessionResource: URI | undefined, chatRequestId: string | undefined): Promise<ConfirmedReason | undefined> {
// Auto-Approve All permission level bypasses all post-execution confirmations,
// unless enterprise policy has explicitly disabled global auto-approve.
if (chatSessionResource) {
const model = this._chatService.getSession(chatSessionResource);
const request = model?.getRequests().at(-1);
if (isAutoApproveLevel(request?.modeInfo?.permissionLevel) && !this._isAutoApprovePolicyRestricted()) {
return { type: ToolConfirmKind.ConfirmationNotNeeded, reason: 'auto-approve-all' };
}
}

if (this._configurationService.getValue<boolean>(ChatConfiguration.GlobalAutoApprove) && await this._checkGlobalAutoApprove()) {
return { type: ToolConfirmKind.Setting, id: ChatConfiguration.GlobalAutoApprove };
}
Expand Down Expand Up @@ -1186,7 +1215,6 @@ export class LanguageModelToolsService extends Disposable implements ILanguageMo
// Clean up any pending tool calls that belong to this request
for (const [toolCallId, invocation] of this._pendingToolCalls) {
if (invocation.chatRequestId === requestId) {
invocation.cancelFromStreaming(ToolConfirmKind.Skipped);
this._pendingToolCalls.delete(toolCallId);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import { IChatRequestVariableEntry } from '../../common/attachments/chatVariable
import { IChatChangesSummaryPart, IChatCodeCitations, IChatErrorDetailsPart, IChatReferences, IChatRendererContent, IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, isRequestVM, isResponseVM, IChatPendingDividerViewModel, isPendingDividerVM } from '../../common/model/chatViewModel.js';
import { getNWords } from '../../common/model/chatWordCounter.js';
import { CodeBlockModelCollection } from '../../common/widget/codeBlockModelCollection.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, CollapsedToolsDisplayMode, ThinkingDisplayMode } from '../../common/constants.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, ChatPermissionLevel, CollapsedToolsDisplayMode, ThinkingDisplayMode } from '../../common/constants.js';
import { ClickAnimation } from '../../../../../base/browser/ui/animations/animations.js';
import { MarkHelpfulActionId, MarkUnhelpfulActionId } from '../actions/chatTitleActions.js';
import { ChatTreeItem, IChatCodeBlockInfo, IChatFileTreeInfo, IChatListItemRendererOptions, IChatWidgetService } from '../chat.js';
Expand Down Expand Up @@ -2335,7 +2335,9 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
}

void this._autoReply.shouldAutoReply().then(shouldAutoReply => {
if (!shouldAutoReply) {
// always autoreply in autopilot mode.
const isAutopilot = isResponseVM(context.element) && context.element.model.request?.modeInfo?.permissionLevel === ChatPermissionLevel.Autopilot;
if (!shouldAutoReply && !isAutopilot) {
// Roll back the in-progress mark if auto-reply is not enabled.
if (stableKey) {
this._autoRepliedQuestionCarousels.delete(stableKey);
Expand Down
8 changes: 6 additions & 2 deletions src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import { IChatTodoListService } from '../../common/tools/chatTodoListService.js'
import { ChatRequestVariableSet, IChatRequestVariableEntry, isPromptFileVariableEntry, isPromptTextVariableEntry, isWorkspaceVariableEntry, PromptFileVariableKind, toPromptFileVariableEntry } from '../../common/attachments/chatVariableEntries.js';
import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM } from '../../common/model/chatViewModel.js';
import { CodeBlockModelCollection } from '../../common/widget/codeBlockModelCollection.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../common/constants.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind, ChatPermissionLevel } from '../../common/constants.js';
import { ILanguageModelToolsService, isToolSet } from '../../common/tools/languageModelToolsService.js';
import { ComputeAutomaticInstructions } from '../../common/promptSyntax/computeAutomaticInstructions.js';
import { IHandOff, PromptHeader } from '../../common/promptSyntax/promptFileParser.js';
Expand Down Expand Up @@ -1552,6 +1552,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
rowContainer.appendChild(this.inputContainer);
this.createInput(this.inputContainer);
this.input.setChatMode(this.inputPart.currentModeObs.get().id);
this.input.setPermissionLevel(this.inputPart.currentModeInfo.permissionLevel ?? ChatPermissionLevel.Default);
this.input.setEditing(true, isEditingSentRequest);
this._onDidChangeActiveInputEditor.fire();
} else {
Expand Down Expand Up @@ -1652,6 +1653,7 @@ export class ChatWidget extends Disposable implements IChatWidget {

if (!isInput) {
this.inputPart.setChatMode(this.input.currentModeObs.get().id);
this.inputPart.setPermissionLevel(this.input.currentModeInfo.permissionLevel ?? ChatPermissionLevel.Default);
const currentModel = this.input.selectedLanguageModel.get();
if (currentModel) {
this.inputPart.switchModel(currentModel.metadata);
Expand Down Expand Up @@ -1735,6 +1737,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
defaultMode: this.viewOptions.defaultMode,
sessionTypePickerDelegate: this.viewOptions.sessionTypePickerDelegate,
workspacePickerDelegate: this.viewOptions.workspacePickerDelegate,
isSessionsWindow: this.viewOptions.isSessionsWindow,
};

if (this.viewModel?.editing) {
Expand Down Expand Up @@ -2139,7 +2142,8 @@ export class ChatWidget extends Disposable implements IChatWidget {
const options: IChatSendRequestOptions = {
attempt: lastRequest.attempt + 1,
location: this.location,
userSelectedModelId: this.input.currentLanguageModel
userSelectedModelId: this.input.currentLanguageModel,
modeInfo: this.input.currentModeInfo,
};
return await this.chatService.resendRequest(lastRequest, options);
}
Expand Down
Loading
Loading