Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/chat/browser/agentHost/agentHostSessionConfigPicker.ts
13405 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import '../media/agentHostSessionConfigPicker.css';
7
import * as dom from '../../../../../base/browser/dom.js';
8
import { Gesture, EventType as TouchEventType } from '../../../../../base/browser/touch.js';
9
import { renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels.js';
10
import { ActionListItemKind, IActionListDelegate, IActionListItem } from '../../../../../platform/actionWidget/browser/actionList.js';
11
import { IActionWidgetService } from '../../../../../platform/actionWidget/browser/actionWidget.js';
12
import { BaseActionViewItem } from '../../../../../base/browser/ui/actionbar/actionViewItems.js';
13
import { Delayer } from '../../../../../base/common/async.js';
14
import { Codicon } from '../../../../../base/common/codicons.js';
15
import { MarkdownString } from '../../../../../base/common/htmlContent.js';
16
import { Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
17
import { autorun, observableValue } from '../../../../../base/common/observable.js';
18
import Severity from '../../../../../base/common/severity.js';
19
import { ThemeIcon } from '../../../../../base/common/themables.js';
20
import { localize, localize2 } from '../../../../../nls.js';
21
import { IActionViewItemService, type IActionViewItemFactory } from '../../../../../platform/actions/browser/actionViewItemService.js';
22
import { Action2, MenuId, MenuItemAction, registerAction2 } from '../../../../../platform/actions/common/actions.js';
23
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
24
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
25
import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js';
26
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
27
import type { SessionConfigPropertySchema, SessionConfigValueItem } from '../../../../../platform/agentHost/common/state/protocol/commands.js';
28
import { ChatConfiguration } from '../../../../../workbench/contrib/chat/common/constants.js';
29
import { ChatContextKeyExprs } from '../../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
30
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../../workbench/common/contributions.js';
31
import { type IChatInputPickerOptions } from '../../../../../workbench/contrib/chat/browser/widget/input/chatInputPickerActionItem.js';
32
import { Menus } from '../../../../browser/menus.js';
33
import { ActiveSessionProviderIdContext } from '../../../../common/contextkeys.js';
34
import { ISessionsProvidersService } from '../../../../services/sessions/browser/sessionsProvidersService.js';
35
import { ISessionsManagementService } from '../../../../services/sessions/common/sessionsManagement.js';
36
import type { ISessionsProvider } from '../../../../services/sessions/common/sessionsProvider.js';
37
import { type IAgentHostSessionsProvider, isAgentHostProvider, LOCAL_AGENT_HOST_PROVIDER_ID, REMOTE_AGENT_HOST_PROVIDER_RE } from '../../../../common/agentHostSessionsProvider.js';
38
import { PermissionPicker } from '../../../copilotChatSessions/browser/permissionPicker.js';
39
import { AgentHostModePicker } from './agentHostModePicker.js';
40
import { AgentHostPermissionPickerActionItem } from './agentHostPermissionPickerActionItem.js';
41
import { AgentHostPermissionPickerDelegate, isWellKnownAutoApproveSchema, isWellKnownModeSchema } from './agentHostPermissionPickerDelegate.js';
42
import { SessionConfigKey } from '../../../../../platform/agentHost/common/sessionConfigKeys.js';
43
44
const IsActiveSessionRemoteAgentHost = ContextKeyExpr.regex(ActiveSessionProviderIdContext.key, REMOTE_AGENT_HOST_PROVIDER_RE);
45
const IsActiveSessionLocalAgentHost = ContextKeyExpr.equals(ActiveSessionProviderIdContext.key, LOCAL_AGENT_HOST_PROVIDER_ID);
46
47
registerAction2(class extends Action2 {
48
constructor() {
49
super({
50
id: 'sessions.agentHost.sessionConfigPicker',
51
title: localize2('agentHostSessionConfigPicker', "Session Configuration"),
52
f1: false,
53
menu: [{
54
id: Menus.NewSessionRepositoryConfig,
55
group: 'navigation',
56
order: 3,
57
when: ContextKeyExpr.or(IsActiveSessionLocalAgentHost, IsActiveSessionRemoteAgentHost),
58
}],
59
});
60
}
61
62
override async run(): Promise<void> { }
63
});
64
65
interface IConfigPickerItem {
66
readonly value: string;
67
readonly label: string;
68
readonly description?: string;
69
}
70
71
function getConfigIcon(property: string, value: unknown | undefined): ThemeIcon | undefined {
72
if (property === 'isolation') {
73
if (value === 'folder') {
74
return Codicon.folder;
75
}
76
if (value === 'worktree') {
77
return Codicon.worktree;
78
}
79
}
80
if (property === 'branch') {
81
return Codicon.gitBranch;
82
}
83
if (property === 'autoApprove') {
84
if (value === 'autopilot') {
85
return Codicon.rocket;
86
}
87
if (value === 'autoApprove') {
88
return Codicon.warning;
89
}
90
return Codicon.shield;
91
}
92
return undefined;
93
}
94
95
function toActionItems(property: string, items: readonly IConfigPickerItem[], currentValue: unknown | undefined, policyRestricted?: boolean): IActionListItem<IConfigPickerItem>[] {
96
return items.map(item => ({
97
kind: ActionListItemKind.Action,
98
label: item.label,
99
description: item.description,
100
group: { title: '', icon: getConfigIcon(property, item.value) },
101
disabled: policyRestricted && (item.value === 'autoApprove' || item.value === 'autopilot'),
102
item: { ...item, label: item.value === currentValue ? `${item.label} ${localize('selected', "(Selected)")}` : item.label },
103
}));
104
}
105
106
function renderPickerTrigger(slot: HTMLElement, disabled: boolean, disposables: DisposableStore, onOpen: () => void): HTMLElement {
107
const trigger = dom.append(slot, disabled ? dom.$('span.action-label') : dom.$('a.action-label'));
108
if (disabled) {
109
trigger.setAttribute('aria-readonly', 'true');
110
} else {
111
trigger.role = 'button';
112
trigger.tabIndex = 0;
113
trigger.setAttribute('aria-haspopup', 'listbox');
114
disposables.add(Gesture.addTarget(trigger));
115
for (const eventType of [dom.EventType.CLICK, TouchEventType.Tap]) {
116
disposables.add(dom.addDisposableListener(trigger, eventType, e => {
117
dom.EventHelper.stop(e, true);
118
onOpen();
119
}));
120
}
121
disposables.add(dom.addDisposableListener(trigger, dom.EventType.KEY_DOWN, e => {
122
if (e.key === 'Enter' || e.key === ' ') {
123
dom.EventHelper.stop(e, true);
124
onOpen();
125
}
126
}));
127
}
128
slot.classList.toggle('disabled', disabled);
129
130
return trigger;
131
}
132
133
// Track whether auto-approve warnings have been shown this VS Code session
134
const shownAutoApproveWarnings = new Set<string /* enum value */>();
135
136
function hasShownAutoApproveWarning(value: string): boolean {
137
if (shownAutoApproveWarnings.has(value)) {
138
return true;
139
}
140
// Confirming Autopilot implies the user accepted the Bypass risks too
141
if (value === 'autoApprove' && shownAutoApproveWarnings.has('autopilot')) {
142
return true;
143
}
144
return false;
145
}
146
147
/**
148
* Filters out autopilot if disabled, and marks bypass/autopilot as disabled
149
* if enterprise policy restricts auto-approval. Returns the filtered items
150
* and policy state.
151
*/
152
function applyAutoApproveFiltering(
153
items: readonly IConfigPickerItem[],
154
property: string,
155
configurationService: IConfigurationService,
156
): { readonly items: readonly IConfigPickerItem[]; readonly policyRestricted: boolean } {
157
if (property !== SessionConfigKey.AutoApprove) {
158
return { items, policyRestricted: false };
159
}
160
const isAutopilotEnabled = configurationService.getValue<boolean>(ChatConfiguration.AutopilotEnabled) !== false;
161
const policyRestricted = configurationService.inspect<boolean>(ChatConfiguration.GlobalAutoApprove).policyValue === false;
162
const filtered = isAutopilotEnabled ? items : items.filter(item => item.value !== 'autopilot');
163
return { items: filtered, policyRestricted };
164
}
165
166
/**
167
* Shows a confirmation dialog for elevated auto-approve levels.
168
* Returns true if confirmed or if the warning was already shown this session.
169
*/
170
async function confirmAutoApproveLevel(value: string, dialogService: IDialogService): Promise<boolean> {
171
if (hasShownAutoApproveWarning(value)) {
172
return true;
173
}
174
175
const isAutopilot = value === 'autopilot';
176
const result = await dialogService.prompt({
177
type: Severity.Warning,
178
message: isAutopilot
179
? localize('agentHostAutoApprove.autopilot.warning.title', "Enable Autopilot?")
180
: localize('agentHostAutoApprove.bypass.warning.title', "Enable Bypass Approvals?"),
181
buttons: [
182
{
183
label: localize('agentHostAutoApprove.warning.confirm', "Enable"),
184
run: () => true,
185
},
186
{
187
label: localize('agentHostAutoApprove.warning.cancel', "Cancel"),
188
run: () => false,
189
},
190
],
191
custom: {
192
icon: isAutopilot ? Codicon.rocket : Codicon.warning,
193
markdownDetails: [{
194
markdown: new MarkdownString(
195
localize(
196
'agentHostAutoApprove.warning.detailWithDefaultSetting',
197
"{0}\n\nTo make this the starting permission level for new chat sessions, change the [{1}](command:workbench.action.openSettings?%5B%22{1}%22%5D) setting.",
198
isAutopilot
199
? localize('agentHostAutoApprove.autopilot.warning.detail', "Autopilot will auto-approve all tool calls and continue working autonomously until the task is complete. This includes terminal commands, file edits, and external tool calls. The agent will make decisions on your behalf without asking for confirmation.\n\nYou can stop the agent at any time by clicking the stop button. This applies to the current session only.")
200
: localize('agentHostAutoApprove.bypass.warning.detail', "Bypass Approvals will auto-approve all tool calls without asking for confirmation. This includes file edits, terminal commands, and external tool calls."),
201
ChatConfiguration.DefaultPermissionLevel,
202
),
203
{ isTrusted: { enabledCommands: ['workbench.action.openSettings'] } },
204
),
205
}],
206
},
207
});
208
209
if (result.result !== true) {
210
return false;
211
}
212
213
shownAutoApproveWarnings.add(value);
214
return true;
215
}
216
217
/**
218
* Applies warning/info CSS classes to a trigger element for auto-approve levels.
219
*/
220
function applyAutoApproveTriggerStyles(trigger: HTMLElement, property: string | undefined, value: unknown | undefined): void {
221
if (property === SessionConfigKey.AutoApprove) {
222
trigger.classList.toggle('warning', value === 'autopilot');
223
trigger.classList.toggle('info', value === 'autoApprove');
224
}
225
}
226
227
class AgentHostSessionConfigPicker extends Disposable {
228
229
private readonly _renderDisposables = this._register(new DisposableStore());
230
private readonly _providerListeners = this._register(new DisposableMap<string>());
231
private readonly _filterDelayer = this._register(new Delayer<readonly IActionListItem<IConfigPickerItem>[]>(200));
232
private _container: HTMLElement | undefined;
233
234
constructor(
235
@IActionWidgetService private readonly _actionWidgetService: IActionWidgetService,
236
@IConfigurationService private readonly _configurationService: IConfigurationService,
237
@IDialogService private readonly _dialogService: IDialogService,
238
@ISessionsManagementService private readonly _sessionsManagementService: ISessionsManagementService,
239
@ISessionsProvidersService private readonly _sessionsProvidersService: ISessionsProvidersService,
240
) {
241
super();
242
243
this._register(autorun(reader => {
244
const session = this._sessionsManagementService.activeSession.read(reader);
245
if (session) {
246
session.loading.read(reader);
247
}
248
this._renderConfigPickers();
249
}));
250
251
this._register(this._sessionsProvidersService.onDidChangeProviders(e => {
252
for (const provider of e.removed) {
253
this._providerListeners.deleteAndDispose(provider.id);
254
}
255
this._watchProviders(e.added);
256
this._renderConfigPickers();
257
}));
258
this._watchProviders(this._sessionsProvidersService.getProviders());
259
}
260
261
private _watchProviders(providers: readonly ISessionsProvider[]): void {
262
for (const provider of providers) {
263
if (!isAgentHostProvider(provider) || this._providerListeners.has(provider.id)) {
264
continue;
265
}
266
this._providerListeners.set(provider.id, provider.onDidChangeSessionConfig(() => this._renderConfigPickers()));
267
}
268
}
269
270
render(container: HTMLElement): void {
271
this._container = dom.append(container, dom.$('.sessions-chat-agent-host-config'));
272
this._renderConfigPickers();
273
}
274
275
private _renderConfigPickers(): void {
276
if (!this._container) {
277
return;
278
}
279
280
this._renderDisposables.clear();
281
dom.clearNode(this._container);
282
283
const session = this._sessionsManagementService.activeSession.get();
284
const provider = session ? this._getProvider(session.providerId) : undefined;
285
const resolvedConfig = session && provider?.getSessionConfig(session.sessionId);
286
if (!session || !provider || !resolvedConfig) {
287
return;
288
}
289
290
// In the running-session flow only `sessionMutable` properties can
291
// actually be changed (non-mutable ones would no-op in
292
// `setSessionConfigValue`). In the new-session flow any property is
293
// changeable because changes trigger a full config re-resolve — so
294
// non-mutable properties like `isolation` must remain visible and
295
// interactive there.
296
const isNewSession = provider.getCreateSessionConfig(session.sessionId) !== undefined;
297
298
for (const [property, schema] of Object.entries(resolvedConfig.schema.properties)) {
299
if (property === SessionConfigKey.BranchNameHint) {
300
continue;
301
}
302
// Only render pickers for properties we know how to present. Today
303
// that's string properties with an `enum` — anything else (objects,
304
// arrays, free-form strings, numbers, booleans) has no enumerable
305
// choice set and is edited through the JSONC settings editor instead.
306
if (schema.type !== 'string' || !schema.enum || schema.enum.length === 0) {
307
continue;
308
}
309
// In a running session, skip non-mutable properties — they can't
310
// be changed and would render as dead pills.
311
if (!isNewSession && !schema.sessionMutable) {
312
continue;
313
}
314
// When the autoApprove property uses the well-known schema, the
315
// workbench `PermissionPickerActionItem` (registered separately for
316
// `Menus.NewSessionControl`) handles it — skip it here to avoid
317
// double-rendering. Non-conforming schemas still fall through to
318
// the generic per-property picker below.
319
if (property === SessionConfigKey.AutoApprove && isWellKnownAutoApproveSchema(schema)) {
320
continue;
321
}
322
// When the mode property uses the well-known schema, the dedicated
323
// {@link AgentHostModePicker} (registered separately for
324
// `Menus.NewSessionConfig`) handles it. Non-conforming schemas
325
// still fall through to the generic per-property picker below.
326
if (property === SessionConfigKey.Mode && isWellKnownModeSchema(schema)) {
327
continue;
328
}
329
const value = resolvedConfig.values[property] ?? schema.default;
330
const slot = dom.append(this._container, dom.$('.sessions-chat-picker-slot'));
331
const trigger = renderPickerTrigger(slot, !!schema.readOnly, this._renderDisposables, () => this._showPicker(provider, session.sessionId, property, schema, trigger));
332
this._renderTrigger(trigger, property, schema, value);
333
}
334
}
335
336
private _renderTrigger(trigger: HTMLElement, property: string, schema: SessionConfigPropertySchema, value: unknown | undefined): void {
337
dom.clearNode(trigger);
338
const icon = getConfigIcon(property, value);
339
if (icon) {
340
dom.append(trigger, renderIcon(icon));
341
}
342
const labelSpan = dom.append(trigger, dom.$('span.sessions-chat-dropdown-label'));
343
const label = this._getLabel(schema, value);
344
labelSpan.textContent = label;
345
trigger.setAttribute('aria-label', schema.readOnly
346
? localize('agentHostSessionConfig.triggerAriaReadOnly', "{0}: {1}, Read-Only", schema.title, label)
347
: localize('agentHostSessionConfig.triggerAria', "{0}: {1}", schema.title, label));
348
if (!schema.readOnly) {
349
dom.append(trigger, renderIcon(Codicon.chevronDown));
350
}
351
applyAutoApproveTriggerStyles(trigger, property, value);
352
}
353
354
private async _showPicker(provider: IAgentHostSessionsProvider, sessionId: string, property: string, schema: SessionConfigPropertySchema, trigger: HTMLElement): Promise<void> {
355
if (schema.readOnly || this._actionWidgetService.isVisible) {
356
return;
357
}
358
const rawItems = await this._getItems(provider, sessionId, property, schema);
359
const { items, policyRestricted } = applyAutoApproveFiltering(rawItems, property, this._configurationService);
360
if (items.length === 0) {
361
return;
362
}
363
364
const isAutoApproveProperty = property === SessionConfigKey.AutoApprove;
365
const currentValue = provider.getSessionConfig(sessionId)?.values[property];
366
const actionItems = toActionItems(property, items, currentValue, policyRestricted);
367
368
const delegate: IActionListDelegate<IConfigPickerItem> = {
369
onSelect: async item => {
370
this._actionWidgetService.hide();
371
372
if (isAutoApproveProperty && (item.value === 'autoApprove' || item.value === 'autopilot')) {
373
const confirmed = await confirmAutoApproveLevel(item.value, this._dialogService);
374
if (!confirmed) {
375
return;
376
}
377
}
378
379
provider.setSessionConfigValue(sessionId, property, item.value).catch(() => { /* best-effort */ });
380
},
381
onFilter: schema.enumDynamic
382
? query => this._filterDelayer.trigger(async () => toActionItems(property, await this._getItems(provider, sessionId, property, schema, query), provider.getSessionConfig(sessionId)?.values[property]))
383
: undefined,
384
onHide: () => trigger.focus(),
385
};
386
387
this._actionWidgetService.show<IConfigPickerItem>(
388
`agentHostSessionConfig.${property}`,
389
false,
390
actionItems,
391
delegate,
392
trigger,
393
undefined,
394
[],
395
{
396
getAriaLabel: item => item.label ?? '',
397
getWidgetAriaLabel: () => localize('agentHostSessionConfig.ariaLabel', "{0} Picker", schema.title),
398
},
399
actionItems.length > 10 ? { showFilter: true, filterPlaceholder: localize('agentHostSessionConfig.filter', "Filter options...") } : undefined,
400
);
401
}
402
403
private async _getItems(provider: IAgentHostSessionsProvider, sessionId: string, property: string, schema: SessionConfigPropertySchema, query?: string): Promise<readonly IConfigPickerItem[]> {
404
const dynamicItems = schema.enumDynamic
405
? await provider.getSessionConfigCompletions(sessionId, property, query)
406
: undefined;
407
if (dynamicItems?.length) {
408
return dynamicItems.map(item => this._fromCompletionItem(item));
409
}
410
411
return (schema.enum ?? []).map((value, index) => ({
412
value,
413
label: schema.enumLabels?.[index] ?? value,
414
description: schema.enumDescriptions?.[index],
415
}));
416
}
417
418
private _fromCompletionItem(item: SessionConfigValueItem): IConfigPickerItem {
419
return {
420
value: item.value,
421
label: item.label,
422
description: item.description,
423
};
424
}
425
426
private _getLabel(schema: SessionConfigPropertySchema, value: unknown | undefined): string {
427
if (typeof value === 'string') {
428
const index = schema.enum?.indexOf(value) ?? -1;
429
return index >= 0 ? schema.enumLabels?.[index] ?? value : value;
430
}
431
return schema.title;
432
}
433
434
private _getProvider(providerId: string): IAgentHostSessionsProvider | undefined {
435
const provider = this._sessionsProvidersService.getProvider(providerId);
436
return provider && isAgentHostProvider(provider) ? provider : undefined;
437
}
438
}
439
440
interface IConfigPickerWidget extends IDisposable {
441
render(container: HTMLElement): void;
442
}
443
444
class PickerActionViewItem extends BaseActionViewItem {
445
constructor(private readonly _picker: IConfigPickerWidget, disposable?: IDisposable) {
446
super(undefined, { id: '', label: '', enabled: true, class: undefined, tooltip: '', run: () => { } });
447
if (disposable) {
448
this._register(disposable);
449
}
450
}
451
452
override render(container: HTMLElement): void {
453
this._picker.render(container);
454
}
455
456
override dispose(): void {
457
this._picker.dispose();
458
super.dispose();
459
}
460
}
461
462
class AgentHostSessionConfigPickerContribution extends Disposable implements IWorkbenchContribution {
463
static readonly ID = 'sessions.contrib.agentHostSessionConfigPicker';
464
465
constructor(
466
@IActionViewItemService actionViewItemService: IActionViewItemService,
467
@IInstantiationService private readonly _instantiationService: IInstantiationService,
468
) {
469
super();
470
this._register(actionViewItemService.register(
471
Menus.NewSessionRepositoryConfig,
472
'sessions.agentHost.sessionConfigPicker',
473
() => new PickerActionViewItem(this._instantiationService.createInstance(AgentHostSessionConfigPicker)),
474
));
475
this._register(actionViewItemService.register(
476
Menus.NewSessionConfig,
477
NEW_SESSION_MODE_PICKER_ID,
478
() => new PickerActionViewItem(this._instantiationService.createInstance(AgentHostModePicker)),
479
));
480
this._register(actionViewItemService.register(
481
MenuId.ChatInput,
482
RUNNING_SESSION_MODE_PICKER_ID,
483
() => new PickerActionViewItem(this._instantiationService.createInstance(AgentHostModePicker)),
484
));
485
this._register(actionViewItemService.register(
486
Menus.NewSessionControl,
487
NEW_SESSION_APPROVE_PICKER_ID,
488
() => this._createNewSessionPermissionPicker(),
489
));
490
this._register(actionViewItemService.register(
491
MenuId.ChatInputSecondary,
492
RUNNING_SESSION_CONFIG_PICKER_ID,
493
this._createRunningSessionPermissionPickerFactory(),
494
));
495
}
496
497
/**
498
* On the new-chat page (left of the toolbar), use the sessions
499
* {@link PermissionPicker} so the styling matches the surrounding sessions
500
* pickers (font size, padding, icon size).
501
*/
502
private _createNewSessionPermissionPicker(): PickerActionViewItem {
503
const delegate = this._instantiationService.createInstance(AgentHostPermissionPickerDelegate);
504
const picker = this._instantiationService.createInstance(PermissionPicker, delegate);
505
return new PickerActionViewItem(picker, delegate);
506
}
507
508
/**
509
* Inside a running chat widget (`ChatInputSecondary`), use the workbench
510
* {@link PermissionPickerActionItem} so it matches the rest of the
511
* chat-input secondary toolbar (which is what the extension-host CLI
512
* already uses).
513
*/
514
private _createRunningSessionPermissionPickerFactory(): IActionViewItemFactory {
515
return (action, _options, instantiationService) => {
516
if (!(action instanceof MenuItemAction)) {
517
return undefined;
518
}
519
const pickerOptions: IChatInputPickerOptions = {
520
hideChevrons: observableValue('hideChevrons', false),
521
};
522
return instantiationService.createInstance(
523
AgentHostPermissionPickerActionItem,
524
action,
525
pickerOptions,
526
);
527
};
528
}
529
}
530
531
// ---- New session auto-approve picker (left side, NewSessionControl) ----
532
533
const NEW_SESSION_APPROVE_PICKER_ID = 'sessions.agentHost.newSessionApprovePicker';
534
535
registerAction2(class extends Action2 {
536
constructor() {
537
super({
538
id: NEW_SESSION_APPROVE_PICKER_ID,
539
title: localize2('agentHostNewSessionApprovePicker', "Session Approvals"),
540
f1: false,
541
menu: [{
542
id: Menus.NewSessionControl,
543
group: 'navigation',
544
order: 1,
545
when: ContextKeyExpr.or(IsActiveSessionLocalAgentHost, IsActiveSessionRemoteAgentHost),
546
}],
547
});
548
}
549
550
override async run(): Promise<void> { }
551
});
552
553
554
// ---- New session mode picker (NewSessionConfig) ----
555
556
const NEW_SESSION_MODE_PICKER_ID = 'sessions.agentHost.newSessionModePicker';
557
558
registerAction2(class extends Action2 {
559
constructor() {
560
super({
561
id: NEW_SESSION_MODE_PICKER_ID,
562
title: localize2('agentHostNewSessionModePicker', "Agent Mode"),
563
f1: false,
564
menu: [{
565
id: Menus.NewSessionConfig,
566
group: 'navigation',
567
order: 0,
568
when: ContextKeyExpr.or(IsActiveSessionLocalAgentHost, IsActiveSessionRemoteAgentHost),
569
}],
570
});
571
}
572
573
override async run(): Promise<void> { }
574
});
575
576
577
// ---- Running session config picker (ChatInputSecondary) ----
578
579
const RUNNING_SESSION_CONFIG_PICKER_ID = 'sessions.agentHost.runningSessionConfigPicker';
580
581
registerAction2(class extends Action2 {
582
constructor() {
583
super({
584
id: RUNNING_SESSION_CONFIG_PICKER_ID,
585
title: localize2('agentHostRunningSessionConfigPicker', "Session Approvals"),
586
f1: false,
587
menu: [{
588
id: MenuId.ChatInputSecondary,
589
group: 'navigation',
590
order: 10,
591
when: ChatContextKeyExprs.isAgentHostSession,
592
}],
593
});
594
}
595
596
override async run(): Promise<void> { }
597
});
598
599
600
// ---- Running session mode picker (ChatInput, beside the model picker) ----
601
602
const RUNNING_SESSION_MODE_PICKER_ID = 'sessions.agentHost.runningSessionModePicker';
603
604
registerAction2(class extends Action2 {
605
constructor() {
606
super({
607
id: RUNNING_SESSION_MODE_PICKER_ID,
608
title: localize2('agentHostRunningSessionModePicker', "Agent Mode"),
609
f1: false,
610
menu: [{
611
id: MenuId.ChatInput,
612
group: 'navigation',
613
// `OpenModelPickerAction` (the "Auto" model picker) is at order 3
614
// in the same menu — sit just before it so the mode pill renders
615
// to the left of "Pick Model".
616
order: 2,
617
when: ChatContextKeyExprs.isAgentHostSession,
618
}],
619
});
620
}
621
622
override async run(): Promise<void> { }
623
});
624
625
626
registerWorkbenchContribution2(AgentHostSessionConfigPickerContribution.ID, AgentHostSessionConfigPickerContribution, WorkbenchPhase.AfterRestored);
627
628