agent sessions - introduce experiments and adopt for projection & status (#288696)

This commit is contained in:
Benjamin Pasero 2026-01-19 11:41:52 +01:00 committed by GitHub
parent 471da7c9b8
commit a2ad5acb04
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 293 additions and 349 deletions

2
.github/CODENOTIFY vendored
View File

@ -7,7 +7,6 @@ src/vs/base/common/path.ts @bpasero
src/vs/base/common/stream.ts @bpasero
src/vs/base/common/uri.ts @jrieken
src/vs/base/browser/domSanitize.ts @mjbvz
src/vs/base/browser/** @bpasero
src/vs/base/node/pfs.ts @bpasero
src/vs/base/node/unc.ts @bpasero
src/vs/base/parts/contextmenu/** @bpasero
@ -110,7 +109,6 @@ src/vs/workbench/contrib/chat/browser/media/chatViewTitleControl.css @bpasero
src/vs/workbench/contrib/chat/browser/chatManagement/chatUsageWidget.ts @bpasero
src/vs/workbench/contrib/chat/browser/chatManagement/media/chatUsageWidget.css @bpasero
src/vs/workbench/contrib/chat/browser/agentSessions/** @bpasero
src/vs/workbench/contrib/chat/browser/chatSessions/** @bpasero
src/vs/workbench/contrib/localization/** @TylerLeonhardt
src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @TylerLeonhardt
src/vs/workbench/contrib/scm/** @lszomoru

View File

@ -292,7 +292,7 @@ export class MenuId {
static readonly AgentSessionsToolbar = new MenuId('AgentSessionsToolbar');
static readonly AgentSessionItemToolbar = new MenuId('AgentSessionItemToolbar');
static readonly AgentSessionSectionToolbar = new MenuId('AgentSessionSectionToolbar');
static readonly AgentsControlMenu = new MenuId('AgentsControlMenu');
static readonly AgentsTitleBarControlMenu = new MenuId('AgentsTitleBarControlMenu');
static readonly ChatViewSessionTitleNavigationToolbar = new MenuId('ChatViewSessionTitleNavigationToolbar');
static readonly ChatViewSessionTitleToolbar = new MenuId('ChatViewSessionTitleToolbar');

View File

@ -1,71 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from '../../../../base/common/lifecycle.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
/**
* Interface for a command center control that can be registered with the titlebar.
*/
export interface ICommandCenterControl extends IDisposable {
readonly element: HTMLElement;
}
/**
* A registration for a custom command center control.
*/
export interface ICommandCenterControlRegistration {
/**
* The context key that must be truthy for this control to be shown.
* When this context key is true, this control replaces the default command center.
*/
readonly contextKey: string;
/**
* Priority for when multiple controls match. Higher priority wins.
*/
readonly priority: number;
/**
* Factory function to create the control.
*/
create(instantiationService: IInstantiationService): ICommandCenterControl;
}
class CommandCenterControlRegistryImpl {
private readonly registrations: ICommandCenterControlRegistration[] = [];
/**
* Register a custom command center control.
*/
register(registration: ICommandCenterControlRegistration): IDisposable {
this.registrations.push(registration);
// Sort by priority descending
this.registrations.sort((a, b) => b.priority - a.priority);
return {
dispose: () => {
const index = this.registrations.indexOf(registration);
if (index >= 0) {
this.registrations.splice(index, 1);
}
}
};
}
/**
* Get all registered command center controls.
*/
getRegistrations(): readonly ICommandCenterControlRegistration[] {
return this.registrations;
}
}
/**
* Registry for custom command center controls.
* Contrib modules can register controls here, and the titlebar will use them
* when their context key conditions are met.
*/
export const CommandCenterControlRegistry = new CommandCenterControlRegistryImpl();

View File

@ -30,7 +30,6 @@ import { IContextKey, IContextKeyService } from '../../../../platform/contextkey
import { IHostService } from '../../../services/host/browser/host.js';
import { WindowTitle } from './windowTitle.js';
import { CommandCenterControl } from './commandCenterControl.js';
import { CommandCenterControlRegistry } from './commandCenterControlRegistry.js';
import { Categories } from '../../../../platform/action/common/actionCommonCategories.js';
import { WorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';
import { ACCOUNTS_ACTIVITY_ID, GLOBAL_ACTIVITY_ID } from '../../../common/activity.js';
@ -329,14 +328,6 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart {
this._register(this.hostService.onDidChangeActiveWindow(windowId => windowId === targetWindowId ? this.onFocus() : this.onBlur()));
this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e)));
this._register(this.editorGroupsContainer.onDidChangeEditorPartOptions(e => this.onEditorPartConfigurationChange(e)));
// Re-create title when any registered command center control's context key changes
this._register(this.contextKeyService.onDidChangeContext(e => {
const registeredContextKeys = new Set(CommandCenterControlRegistry.getRegistrations().map(r => r.contextKey));
if (registeredContextKeys.size > 0 && e.affectsSome(registeredContextKeys)) {
this.createTitle();
}
}));
}
private onBlur(): void {
@ -585,24 +576,9 @@ export class BrowserTitlebarPart extends Part implements ITitlebarPart {
// Menu Title
else {
// Check if any registered command center control should be shown
let customControlShown = false;
for (const registration of CommandCenterControlRegistry.getRegistrations()) {
if (this.contextKeyService.getContextKeyValue<boolean>(registration.contextKey)) {
const control = registration.create(this.instantiationService);
reset(this.title, control.element);
this.titleDisposables.add(control);
customControlShown = true;
break;
}
}
if (!customControlShown) {
// Normal mode - show regular command center
const commandCenter = this.instantiationService.createInstance(CommandCenterControl, this.windowTitle, this.hoverDelegate);
reset(this.title, commandCenter.element);
this.titleDisposables.add(commandCenter);
}
const commandCenter = this.instantiationService.createInstance(CommandCenterControl, this.windowTitle, this.hoverDelegate);
reset(this.title, commandCenter.element);
this.titleDisposables.add(commandCenter);
}
}

View File

@ -948,10 +948,6 @@ MenuRegistry.appendMenuItem(MenuId.CommandCenter, {
ChatContextKeys.Setup.disabled.negate()
),
ContextKeyExpr.has('config.chat.commandCenter.enabled'),
ContextKeyExpr.or(
ContextKeyExpr.has(`config.${ChatConfiguration.AgentStatusEnabled}`).negate(), // Show when agent status is disabled
ChatContextKeys.agentStatusHasNotifications.negate() // Or when agent status has no notifications
)
),
order: 10003 // to the right of agent controls
});

View File

@ -30,7 +30,6 @@ import { ChatViewPane } from '../widgetHosts/viewPane/chatViewPane.js';
import { ACTION_ID_NEW_CHAT, ACTION_ID_NEW_EDIT_SESSION, CHAT_CATEGORY, handleCurrentEditingSession } from './chatActions.js';
import { clearChatEditor } from './chatClear.js';
import { AgentSessionsViewerOrientation } from '../agentSessions/agentSessions.js';
import { IAgentSessionProjectionService } from '../agentSessions/agentSessionProjectionService.js';
export interface INewEditSessionActionContext {
@ -121,13 +120,6 @@ export function registerNewChatActions() {
async run(accessor: ServicesAccessor, ...args: unknown[]) {
const accessibilityService = accessor.get(IAccessibilityService);
const projectionService = accessor.get(IAgentSessionProjectionService);
// Exit projection mode if active (back button behavior)
if (projectionService.isActive) {
await projectionService.exitProjection();
return;
}
const viewsService = accessor.get(IViewsService);
const executeCommandContext = args[0] as INewEditSessionActionContext | undefined;

View File

@ -3,10 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './experiments/agentSessionsExperiments.contribution.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { localize, localize2 } from '../../../../../nls.js';
import { mainWindow } from '../../../../../base/browser/window.js';
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
import { registerSingleton, InstantiationType } from '../../../../../platform/instantiation/common/extensions.js';
import { Registry } from '../../../../../platform/registry/common/platform.js';
@ -15,20 +14,11 @@ import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { AgentSessionsViewerOrientation, AgentSessionsViewerPosition } from './agentSessions.js';
import { IAgentSessionsService, AgentSessionsService } from './agentSessionsService.js';
import { LocalAgentsSessionsProvider } from './localAgentSessionsProvider.js';
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../common/contributions.js';
import { ISubmenuItem, MenuId, MenuRegistry, registerAction2, SubmenuItemAction } from '../../../../../platform/actions/common/actions.js';
import { ArchiveAgentSessionAction, ArchiveAgentSessionSectionAction, UnarchiveAgentSessionSectionAction, UnarchiveAgentSessionAction, OpenAgentSessionInEditorGroupAction, OpenAgentSessionInNewEditorGroupAction, OpenAgentSessionInNewWindowAction, ShowAgentSessionsSidebar, HideAgentSessionsSidebar, ToggleAgentSessionsSidebar, RefreshAgentSessionsViewerAction, FindAgentSessionInViewerAction, MarkAgentSessionUnreadAction, MarkAgentSessionReadAction, MarkAgentSessionSectionReadAction, FocusAgentSessionsAction, SetAgentSessionsOrientationStackedAction, SetAgentSessionsOrientationSideBySideAction, ShowAllAgentSessionsAction, ShowRecentAgentSessionsAction, HideAgentSessionsAction, PickAgentSessionAction, ArchiveAllAgentSessionsAction, RenameAgentSessionAction, DeleteAgentSessionAction, DeleteAllLocalSessionsAction } from './agentSessionsActions.js';
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../common/contributions.js';
import { ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
import { ArchiveAgentSessionAction, ArchiveAgentSessionSectionAction, UnarchiveAgentSessionAction, OpenAgentSessionInEditorGroupAction, OpenAgentSessionInNewEditorGroupAction, OpenAgentSessionInNewWindowAction, ShowAgentSessionsSidebar, HideAgentSessionsSidebar, ToggleAgentSessionsSidebar, RefreshAgentSessionsViewerAction, FindAgentSessionInViewerAction, MarkAgentSessionUnreadAction, MarkAgentSessionReadAction, FocusAgentSessionsAction, SetAgentSessionsOrientationStackedAction, SetAgentSessionsOrientationSideBySideAction, PickAgentSessionAction, ArchiveAllAgentSessionsAction, RenameAgentSessionAction, DeleteAgentSessionAction, DeleteAllLocalSessionsAction, HideAgentSessionsAction, MarkAgentSessionSectionReadAction, ShowAllAgentSessionsAction, ShowRecentAgentSessionsAction, UnarchiveAgentSessionSectionAction } from './agentSessionsActions.js';
import { AgentSessionsQuickAccessProvider, AGENT_SESSIONS_QUICK_ACCESS_PREFIX } from './agentSessionsQuickAccess.js';
import { IAgentSessionProjectionService, AgentSessionProjectionService } from './agentSessionProjectionService.js';
import { EnterAgentSessionProjectionAction, ExitAgentSessionProjectionAction, ToggleAgentStatusAction, ToggleAgentSessionProjectionAction } from './agentSessionProjectionActions.js';
import { IAgentStatusService, AgentStatusService } from './agentStatusService.js';
import { AgentStatusWidget } from './agentStatusWidget.js';
import { IActionViewItemService } from '../../../../../platform/actions/browser/actionViewItemService.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { ChatConfiguration } from '../../common/constants.js';
import { AuxiliaryBarMaximizedContext } from '../../../../common/contextkeys.js';
import { LayoutSettings } from '../../../../services/layout/browser/layoutService.js';
//#region Actions and Menus
@ -59,12 +49,6 @@ registerAction2(HideAgentSessionsAction);
registerAction2(SetAgentSessionsOrientationStackedAction);
registerAction2(SetAgentSessionsOrientationSideBySideAction);
// Agent Session Projection
registerAction2(EnterAgentSessionProjectionAction);
registerAction2(ExitAgentSessionProjectionAction);
registerAction2(ToggleAgentStatusAction);
registerAction2(ToggleAgentSessionProjectionAction);
// --- Agent Sessions Toolbar
MenuRegistry.appendMenuItem(MenuId.AgentSessionsToolbar, {
@ -193,73 +177,7 @@ Registry.as<IQuickAccessRegistry>(QuickAccessExtensions.Quickaccess).registerQui
//#region Workbench Contributions
registerWorkbenchContribution2(LocalAgentsSessionsProvider.ID, LocalAgentsSessionsProvider, WorkbenchPhase.AfterRestored);
registerSingleton(IAgentSessionsService, AgentSessionsService, InstantiationType.Delayed);
registerSingleton(IAgentStatusService, AgentStatusService, InstantiationType.Delayed);
registerSingleton(IAgentSessionProjectionService, AgentSessionProjectionService, InstantiationType.Delayed);
// Register Agent Status as a menu item in the command center (alongside the search box, not replacing it)
MenuRegistry.appendMenuItem(MenuId.CommandCenter, {
submenu: MenuId.AgentsControlMenu,
title: localize('agentsControl', "Agents"),
icon: Codicon.chatSparkle,
when: ContextKeyExpr.has(`config.${ChatConfiguration.AgentStatusEnabled}`),
order: 10002 // to the right of the chat button
});
// Register a placeholder action to the submenu so it appears (required for submenus)
MenuRegistry.appendMenuItem(MenuId.AgentsControlMenu, {
command: {
id: 'workbench.action.chat.toggle',
title: localize('openChat', "Open Chat"),
},
when: ContextKeyExpr.has(`config.${ChatConfiguration.AgentStatusEnabled}`),
});
/**
* Provides custom rendering for the agent status in the command center.
* Uses IActionViewItemService to render a custom AgentStatusWidget
* for the AgentsControlMenu submenu.
* Also adds a CSS class to the workbench when agent status is enabled.
*/
class AgentStatusRendering extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.agentStatus.rendering';
constructor(
@IActionViewItemService actionViewItemService: IActionViewItemService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService
) {
super();
this._register(actionViewItemService.register(MenuId.CommandCenter, MenuId.AgentsControlMenu, (action, options) => {
if (!(action instanceof SubmenuItemAction)) {
return undefined;
}
return instantiationService.createInstance(AgentStatusWidget, action, options);
}, undefined));
// Add/remove CSS class on workbench based on setting
// Also force enable command center when agent status is enabled
const updateClass = () => {
const enabled = configurationService.getValue<boolean>(ChatConfiguration.AgentStatusEnabled) === true;
mainWindow.document.body.classList.toggle('agent-status-enabled', enabled);
// Force enable command center when agent status is enabled
if (enabled && configurationService.getValue<boolean>(LayoutSettings.COMMAND_CENTER) !== true) {
configurationService.updateValue(LayoutSettings.COMMAND_CENTER, true);
}
};
updateClass();
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(ChatConfiguration.AgentStatusEnabled)) {
updateClass();
}
}));
}
}
// Register the workbench contribution that provides custom rendering for the agent status
registerWorkbenchContribution2(AgentStatusRendering.ID, AgentStatusRendering, WorkbenchPhase.AfterRestored);
//#endregion

View File

@ -68,10 +68,12 @@ export enum AgentSessionsViewerPosition {
}
export interface IAgentSessionsControl {
readonly element: HTMLElement | undefined;
refresh(): void;
openFind(): void;
reveal(sessionResource: URI): void;
setGridMarginOffset(offset: number): void;
}
export const agentSessionReadIndicatorForeground = registerColor(

View File

@ -33,19 +33,17 @@ import { openSession } from './agentSessionsOpener.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { ChatEditorInput } from '../widgetHosts/editor/chatEditorInput.js';
import { IMouseEvent } from '../../../../../base/browser/mouseEvent.js';
import { IChatWidget } from '../chat.js';
export interface IAgentSessionsControlOptions extends IAgentSessionsSorterOptions {
readonly overrideStyles: IStyleOverride<IListStyles>;
readonly filter: IAgentSessionsFilter;
readonly source: AgentSessionsControlSource;
readonly source: string;
getHoverPosition(): HoverPosition;
trackActiveEditorSession(): boolean;
}
export const enum AgentSessionsControlSource {
ChatViewPane = 'chatViewPane',
WelcomeView = 'welcomeView'
notifySessionOpened?(resource: URI, widget: IChatWidget): void;
}
type AgentSessionOpenedClassification = {
@ -57,12 +55,14 @@ type AgentSessionOpenedClassification = {
type AgentSessionOpenedEvent = {
providerType: string;
source: AgentSessionsControlSource;
source: string;
};
export class AgentSessionsControl extends Disposable implements IAgentSessionsControl {
private sessionsContainer: HTMLElement | undefined;
get element(): HTMLElement | undefined { return this.sessionsContainer; }
private sessionsList: WorkbenchCompressibleAsyncDataTree<IAgentSessionsModel, AgentSessionListItem, FuzzyScore> | undefined;
private visible: boolean = true;
@ -213,7 +213,10 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
source: this.options.source
});
await this.instantiationService.invokeFunction(openSession, element, { ...e, expanded: this.options.source === AgentSessionsControlSource.WelcomeView });
const widget = await this.instantiationService.invokeFunction(openSession, element, e);
if (widget) {
this.options.notifySessionOpened?.(element.resource, widget);
}
}
private async showContextMenu({ element, anchor, browserEvent }: ITreeContextMenuEvent<AgentSessionListItem>): Promise<void> {
@ -358,10 +361,4 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
this.sessionsList.setFocus([session]);
this.sessionsList.setSelection([session]);
}
setGridMarginOffset(offset: number): void {
if (this.sessionsContainer) {
this.sessionsContainer.style.marginBottom = `-${offset}px`;
}
}
}

View File

@ -3,39 +3,65 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from '../../../../../base/common/lifecycle.js';
import { IAgentSession, isLocalAgentSessionItem } from './agentSessionsModel.js';
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { IChatEditorOptions } from '../widgetHosts/editor/chatEditor.js';
import { ChatViewPaneTarget, IChatWidgetService } from '../chat.js';
import { ChatViewPaneTarget, IChatWidget, IChatWidgetService } from '../chat.js';
import { ACTIVE_GROUP, SIDE_GROUP } from '../../../../services/editor/common/editorService.js';
import { IEditorOptions } from '../../../../../platform/editor/common/editor.js';
import { IChatSessionsService } from '../../common/chatSessionsService.js';
import { Schemas } from '../../../../../base/common/network.js';
import { IAgentSessionProjectionService } from './agentSessionProjectionService.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { ChatConfiguration } from '../../common/constants.js';
export async function openSession(accessor: ServicesAccessor, session: IAgentSession, openOptions?: { sideBySide?: boolean; editorOptions?: IEditorOptions; expanded?: boolean }): Promise<void> {
const configurationService = accessor.get(IConfigurationService);
const projectionService = accessor.get(IAgentSessionProjectionService);
//#region Session Opener Registry
session.setRead(true); // mark as read when opened
export interface ISessionOpenerParticipant {
handleOpenSession(accessor: ServicesAccessor, session: IAgentSession, openOptions?: ISessionOpenOptions): Promise<boolean>;
}
const agentSessionProjectionEnabled = configurationService.getValue<boolean>(ChatConfiguration.AgentSessionProjectionEnabled) === true;
if (agentSessionProjectionEnabled) {
// Enter Agent Session Projection mode for the session
await projectionService.enterProjection(session);
} else {
// Fall back to opening in chat widget when Agent Session Projection is disabled
await openSessionInChatWidget(accessor, session, openOptions);
export interface ISessionOpenOptions {
readonly sideBySide?: boolean;
readonly editorOptions?: IEditorOptions;
}
class SessionOpenerRegistry {
private readonly participants = new Set<ISessionOpenerParticipant>();
registerParticipant(participant: ISessionOpenerParticipant): IDisposable {
this.participants.add(participant);
return {
dispose: () => {
this.participants.delete(participant);
}
};
}
getParticipants(): readonly ISessionOpenerParticipant[] {
return Array.from(this.participants);
}
}
/**
* Opens a session in the traditional chat widget (side panel or editor).
* Use this when you explicitly want to open in the chat widget rather than agent session projection mode.
*/
export async function openSessionInChatWidget(accessor: ServicesAccessor, session: IAgentSession, openOptions?: { sideBySide?: boolean; editorOptions?: IEditorOptions; expanded?: boolean }): Promise<void> {
export const sessionOpenerRegistry = new SessionOpenerRegistry();
//#endregion
export async function openSession(accessor: ServicesAccessor, session: IAgentSession, openOptions?: ISessionOpenOptions): Promise<IChatWidget | undefined> {
// First, give registered participants a chance to handle the session
for (const participant of sessionOpenerRegistry.getParticipants()) {
const handled = await participant.handleOpenSession(accessor, session, openOptions);
if (handled) {
return undefined; // Participant handled the session, skip default opening
}
}
// Default session opening logic
return openSessionDefault(accessor, session, openOptions);
}
async function openSessionDefault(accessor: ServicesAccessor, session: IAgentSession, openOptions?: ISessionOpenOptions): Promise<IChatWidget | undefined> {
const chatSessionsService = accessor.get(IChatSessionsService);
const chatWidgetService = accessor.get(IChatWidgetService);
@ -52,7 +78,6 @@ export async function openSessionInChatWidget(accessor: ServicesAccessor, sessio
...sessionOptions,
...openOptions?.editorOptions,
revealIfOpened: true, // always try to reveal if already opened
expanded: openOptions?.expanded
};
await chatSessionsService.activateChatSessionItemProvider(session.providerType); // ensure provider is activated before trying to open
@ -70,5 +95,5 @@ export async function openSessionInChatWidget(accessor: ServicesAccessor, sessio
options = { ...options, revealIfOpened: true };
}
await chatWidgetService.openSession(session.resource, target, options);
return chatWidgetService.openSession(session.resource, target, options);
}

View File

@ -0,0 +1,9 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from '../../../../../../nls.js';
import { RawContextKey } from '../../../../../../platform/contextkey/common/contextkey.js';
export const inAgentSessionProjection = new RawContextKey<boolean>('chatInAgentSessionProjection', false, { type: 'boolean', description: localize('chatInAgentSessionProjection', "True when the workbench is in agent session projection mode for reviewing an agent session.") });

View File

@ -3,20 +3,21 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize, localize2 } from '../../../../../nls.js';
import { Action2 } from '../../../../../platform/actions/common/actions.js';
import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../../../../platform/keybinding/common/keybindingsRegistry.js';
import { KeyCode } from '../../../../../base/common/keyCodes.js';
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { ChatConfiguration } from '../../common/constants.js';
import { localize, localize2 } from '../../../../../../nls.js';
import { Action2 } from '../../../../../../platform/actions/common/actions.js';
import { ServicesAccessor } from '../../../../../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../../../../../platform/keybinding/common/keybindingsRegistry.js';
import { KeyCode } from '../../../../../../base/common/keyCodes.js';
import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js';
import { ChatContextKeys } from '../../../common/actions/chatContextKeys.js';
import { IAgentSessionProjectionService } from './agentSessionProjectionService.js';
import { IAgentSession, isMarshalledAgentSessionContext, IMarshalledAgentSessionContext } from './agentSessionsModel.js';
import { IAgentSessionsService } from './agentSessionsService.js';
import { CHAT_CATEGORY } from '../actions/chatActions.js';
import { ToggleTitleBarConfigAction } from '../../../../browser/parts/titlebar/titlebarActions.js';
import { IsCompactTitleBarContext } from '../../../../common/contextkeys.js';
import { IAgentSession, isMarshalledAgentSessionContext, IMarshalledAgentSessionContext } from '../agentSessionsModel.js';
import { IAgentSessionsService } from '../agentSessionsService.js';
import { CHAT_CATEGORY } from '../../actions/chatActions.js';
import { ToggleTitleBarConfigAction } from '../../../../../browser/parts/titlebar/titlebarActions.js';
import { IsCompactTitleBarContext } from '../../../../../common/contextkeys.js';
import { inAgentSessionProjection } from './agentSessionProjection.js';
import { ChatConfiguration } from '../../../common/constants.js';
//#region Enter Agent Session Projection
@ -32,7 +33,7 @@ export class EnterAgentSessionProjectionAction extends Action2 {
precondition: ContextKeyExpr.and(
ChatContextKeys.enabled,
ContextKeyExpr.has(`config.${ChatConfiguration.AgentSessionProjectionEnabled}`),
ChatContextKeys.inAgentSessionProjection.negate()
inAgentSessionProjection.negate()
),
});
}
@ -71,12 +72,12 @@ export class ExitAgentSessionProjectionAction extends Action2 {
f1: true,
precondition: ContextKeyExpr.and(
ChatContextKeys.enabled,
ChatContextKeys.inAgentSessionProjection
inAgentSessionProjection
),
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyCode.Escape,
when: ChatContextKeys.inAgentSessionProjection,
when: inAgentSessionProjection,
},
});
}

View File

@ -3,28 +3,29 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './media/agentSessionProjection.css';
import { Emitter, Event } from '../../../../../base/common/event.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { localize } from '../../../../../nls.js';
import { IContextKey, IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../../platform/log/common/log.js';
import { IEditorGroupsService, IEditorWorkingSet } from '../../../../services/editor/common/editorGroupsService.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
import { IAgentSession } from './agentSessionsModel.js';
import { ChatViewPaneTarget, IChatWidgetService } from '../chat.js';
import { AgentSessionProviders } from './agentSessions.js';
import { IChatSessionsService } from '../../common/chatSessionsService.js';
import { ChatConfiguration } from '../../common/constants.js';
import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js';
import { ACTION_ID_NEW_CHAT } from '../actions/chatActions.js';
import { IChatEditingService, ModifiedFileEntryState } from '../../common/editing/chatEditingService.js';
import { IAgentStatusService } from './agentStatusService.js';
import './media/agentsessionprojection.css';
import { Emitter, Event } from '../../../../../../base/common/event.js';
import { Disposable } from '../../../../../../base/common/lifecycle.js';
import { localize } from '../../../../../../nls.js';
import { IContextKey, IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js';
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../../../platform/log/common/log.js';
import { IEditorGroupsService, IEditorWorkingSet } from '../../../../../services/editor/common/editorGroupsService.js';
import { IEditorService } from '../../../../../services/editor/common/editorService.js';
import { ICommandService } from '../../../../../../platform/commands/common/commands.js';
import { IAgentSession } from '../agentSessionsModel.js';
import { ChatViewPaneTarget, IChatWidgetService } from '../../chat.js';
import { AgentSessionProviders } from '../agentSessions.js';
import { IChatSessionsService } from '../../../common/chatSessionsService.js';
import { IWorkbenchLayoutService } from '../../../../../services/layout/browser/layoutService.js';
import { ACTION_ID_NEW_CHAT } from '../../actions/chatActions.js';
import { IChatEditingService, ModifiedFileEntryState } from '../../../common/editing/chatEditingService.js';
import { IAgentTitleBarStatusService } from './agentTitleBarStatusService.js';
import { ISessionOpenerParticipant, ISessionOpenOptions, sessionOpenerRegistry } from '../agentSessionsOpener.js';
import { ServicesAccessor } from '../../../../../../editor/browser/editorExtensions.js';
import { inAgentSessionProjection } from './agentSessionProjection.js';
import { ChatConfiguration } from '../../../common/constants.js';
//#region Configuration
@ -113,14 +114,34 @@ export class AgentSessionProjectionService extends Disposable implements IAgentS
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@ICommandService private readonly commandService: ICommandService,
@IChatEditingService private readonly chatEditingService: IChatEditingService,
@IAgentStatusService private readonly agentStatusService: IAgentStatusService,
@IAgentTitleBarStatusService private readonly agentTitleBarStatusService: IAgentTitleBarStatusService,
) {
super();
this._inProjectionModeContextKey = ChatContextKeys.inAgentSessionProjection.bindTo(contextKeyService);
this._inProjectionModeContextKey = inAgentSessionProjection.bindTo(contextKeyService);
// Listen for editor close events to exit projection mode when all editors are closed
this._register(this.editorService.onDidCloseEditor(() => this._checkForEmptyEditors()));
// Register as a session opener participant to enter projection mode when sessions are opened
this._register(sessionOpenerRegistry.registerParticipant(this._createSessionOpenerParticipant()));
}
private _createSessionOpenerParticipant(): ISessionOpenerParticipant {
return {
handleOpenSession: async (_accessor: ServicesAccessor, session: IAgentSession, _openOptions?: ISessionOpenOptions): Promise<boolean> => {
// Only handle if projection mode is enabled
if (!this._isEnabled()) {
return false;
}
// Enter projection mode for the session
await this.enterProjection(session);
// Return true to indicate we handled the session (projection mode opens the chat itself)
return true;
}
};
}
private _isEnabled(): boolean {
@ -260,7 +281,7 @@ export class AgentSessionProjectionService extends Disposable implements IAgentS
this.layoutService.mainContainer.classList.add('agent-session-projection-active');
// Update the agent status to show session mode
this.agentStatusService.enterSessionMode(session.resource.toString(), session.label);
this.agentTitleBarStatusService.enterSessionMode(session.resource.toString(), session.label);
if (!wasActive) {
this._onDidChangeProjectionMode.fire(true);
@ -316,7 +337,7 @@ export class AgentSessionProjectionService extends Disposable implements IAgentS
this.layoutService.mainContainer.classList.remove('agent-session-projection-active');
// Update the agent status to exit session mode
this.agentStatusService.exitSessionMode();
this.agentTitleBarStatusService.exitSessionMode();
this._onDidChangeProjectionMode.fire(false);
this._onDidChangeActiveSession.fire(undefined);

View File

@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton, InstantiationType } from '../../../../../../platform/instantiation/common/extensions.js';
import { MenuId, MenuRegistry, registerAction2 } from '../../../../../../platform/actions/common/actions.js';
import { IAgentSessionProjectionService, AgentSessionProjectionService } from './agentSessionProjectionService.js';
import { EnterAgentSessionProjectionAction, ExitAgentSessionProjectionAction, ToggleAgentStatusAction, ToggleAgentSessionProjectionAction } from './agentSessionProjectionActions.js';
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../../common/contributions.js';
import { AgentTitleBarStatusRendering } from './agentTitleBarStatusWidget.js';
import { AgentTitleBarStatusService, IAgentTitleBarStatusService } from './agentTitleBarStatusService.js';
import { Codicon } from '../../../../../../base/common/codicons.js';
import { localize } from '../../../../../../nls.js';
import { ContextKeyExpr } from '../../../../../../platform/contextkey/common/contextkey.js';
import { ChatConfiguration } from '../../../common/constants.js';
// #region Agent Session Projection & Status
registerAction2(EnterAgentSessionProjectionAction);
registerAction2(ExitAgentSessionProjectionAction);
registerAction2(ToggleAgentStatusAction);
registerAction2(ToggleAgentSessionProjectionAction);
registerSingleton(IAgentSessionProjectionService, AgentSessionProjectionService, InstantiationType.Delayed);
registerSingleton(IAgentTitleBarStatusService, AgentTitleBarStatusService, InstantiationType.Delayed);
registerWorkbenchContribution2(AgentTitleBarStatusRendering.ID, AgentTitleBarStatusRendering, WorkbenchPhase.AfterRestored);
// Register Agent Status as a menu item in the command center (alongside the search box, not replacing it)
MenuRegistry.appendMenuItem(MenuId.CommandCenter, {
submenu: MenuId.AgentsTitleBarControlMenu,
title: localize('agentsControl', "Agents"),
icon: Codicon.chatSparkle,
when: ContextKeyExpr.has(`config.${ChatConfiguration.AgentStatusEnabled}`),
order: 10002 // to the right of the chat button
});
// Register a placeholder action to the submenu so it appears (required for submenus)
MenuRegistry.appendMenuItem(MenuId.AgentsTitleBarControlMenu, {
command: {
id: 'workbench.action.chat.toggle',
title: localize('openChat', "Open Chat"),
},
when: ContextKeyExpr.has(`config.${ChatConfiguration.AgentStatusEnabled}`),
});
//#endregion

View File

@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from '../../../../../base/common/event.js';
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { createDecorator } from '../../../../../platform/instantiation/common/instantiation.js';
import { Emitter, Event } from '../../../../../../base/common/event.js';
import { Disposable } from '../../../../../../base/common/lifecycle.js';
import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js';
//#region Agent Status Mode
@ -25,7 +25,7 @@ export interface IAgentStatusSessionInfo {
//#region Agent Status Service Interface
export interface IAgentStatusService {
export interface IAgentTitleBarStatusService {
readonly _serviceBrand: undefined;
/**
@ -66,13 +66,13 @@ export interface IAgentStatusService {
updateSessionTitle(title: string): void;
}
export const IAgentStatusService = createDecorator<IAgentStatusService>('agentStatusService');
export const IAgentTitleBarStatusService = createDecorator<IAgentTitleBarStatusService>('agentTitleBarStatusService');
//#endregion
//#region Agent Status Service Implementation
export class AgentStatusService extends Disposable implements IAgentStatusService {
export class AgentTitleBarStatusService extends Disposable implements IAgentTitleBarStatusService {
declare readonly _serviceBrand: undefined;

View File

@ -3,39 +3,44 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import './media/agentStatusWidget.css';
import { $, addDisposableListener, EventType, reset } from '../../../../../base/browser/dom.js';
import { renderIcon } from '../../../../../base/browser/ui/iconLabel/iconLabels.js';
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { localize } from '../../../../../nls.js';
import { IHoverService } from '../../../../../platform/hover/browser/hover.js';
import { getDefaultHoverDelegate } from '../../../../../base/browser/ui/hover/hoverDelegateFactory.js';
import { AgentStatusMode, IAgentStatusService } from './agentStatusService.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
import './media/agenttitlebarstatuswidget.css';
import { $, addDisposableListener, EventType, reset } from '../../../../../../base/browser/dom.js';
import { renderIcon } from '../../../../../../base/browser/ui/iconLabel/iconLabels.js';
import { Disposable, DisposableStore } from '../../../../../../base/common/lifecycle.js';
import { Codicon } from '../../../../../../base/common/codicons.js';
import { localize } from '../../../../../../nls.js';
import { IHoverService } from '../../../../../../platform/hover/browser/hover.js';
import { getDefaultHoverDelegate } from '../../../../../../base/browser/ui/hover/hoverDelegateFactory.js';
import { AgentStatusMode, IAgentTitleBarStatusService } from './agentTitleBarStatusService.js';
import { ICommandService } from '../../../../../../platform/commands/common/commands.js';
import { IKeybindingService } from '../../../../../../platform/keybinding/common/keybinding.js';
import { ExitAgentSessionProjectionAction } from './agentSessionProjectionActions.js';
import { IAgentSessionsService } from './agentSessionsService.js';
import { AgentSessionStatus, IAgentSession, isSessionInProgressStatus } from './agentSessionsModel.js';
import { BaseActionViewItem, IBaseActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js';
import { IAction, SubmenuAction } from '../../../../../base/common/actions.js';
import { ILabelService } from '../../../../../platform/label/common/label.js';
import { IWorkspaceContextService } from '../../../../../platform/workspace/common/workspace.js';
import { IBrowserWorkbenchEnvironmentService } from '../../../../services/environment/browser/environmentService.js';
import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';
import { IEditorService } from '../../../../services/editor/common/editorService.js';
import { Verbosity } from '../../../../common/editor.js';
import { Schemas } from '../../../../../base/common/network.js';
import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { openSession } from './agentSessionsOpener.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js';
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
import { HiddenItemStrategy, WorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js';
import { createActionViewItem } from '../../../../../platform/actions/browser/menuEntryActionViewItem.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
import { FocusAgentSessionsAction } from './agentSessionsActions.js';
import { IAgentSessionsService } from '../agentSessionsService.js';
import { AgentSessionStatus, IAgentSession, isSessionInProgressStatus } from '../agentSessionsModel.js';
import { BaseActionViewItem, IBaseActionViewItemOptions } from '../../../../../../base/browser/ui/actionbar/actionViewItems.js';
import { IAction, SubmenuAction } from '../../../../../../base/common/actions.js';
import { ILabelService } from '../../../../../../platform/label/common/label.js';
import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js';
import { IBrowserWorkbenchEnvironmentService } from '../../../../../services/environment/browser/environmentService.js';
import { IEditorGroupsService } from '../../../../../services/editor/common/editorGroupsService.js';
import { IEditorService } from '../../../../../services/editor/common/editorService.js';
import { Verbosity } from '../../../../../common/editor.js';
import { Schemas } from '../../../../../../base/common/network.js';
import { renderAsPlaintext } from '../../../../../../base/browser/markdownRenderer.js';
import { openSession } from '../agentSessionsOpener.js';
import { IInstantiationService } from '../../../../../../platform/instantiation/common/instantiation.js';
import { IMenuService, MenuId, SubmenuItemAction } from '../../../../../../platform/actions/common/actions.js';
import { IContextKeyService } from '../../../../../../platform/contextkey/common/contextkey.js';
import { HiddenItemStrategy, WorkbenchToolBar } from '../../../../../../platform/actions/browser/toolbar.js';
import { createActionViewItem } from '../../../../../../platform/actions/browser/menuEntryActionViewItem.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../../../platform/storage/common/storage.js';
import { FocusAgentSessionsAction } from '../agentSessionsActions.js';
import { IWorkbenchContribution } from '../../../../../common/contributions.js';
import { IActionViewItemService } from '../../../../../../platform/actions/browser/actionViewItemService.js';
import { IConfigurationService } from '../../../../../../platform/configuration/common/configuration.js';
import { mainWindow } from '../../../../../../base/browser/window.js';
import { LayoutSettings } from '../../../../../services/layout/browser/layoutService.js';
import { ChatConfiguration } from '../../../common/constants.js';
// Action triggered when clicking the main pill - change this to modify the primary action
const ACTION_ID = 'workbench.action.quickchat.toggle';
@ -53,7 +58,7 @@ const TITLE_DIRTY = '\u25cf ';
*
* The command center search box and navigation controls remain visible alongside this control.
*/
export class AgentStatusWidget extends BaseActionViewItem {
export class AgentTitleBarStatusWidget extends BaseActionViewItem {
private static readonly _quickOpenCommandId = 'workbench.action.quickOpenWithModes';
@ -73,7 +78,7 @@ export class AgentStatusWidget extends BaseActionViewItem {
action: IAction,
options: IBaseActionViewItemOptions | undefined,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IAgentStatusService private readonly agentStatusService: IAgentStatusService,
@IAgentTitleBarStatusService private readonly agentTitleBarStatusService: IAgentTitleBarStatusService,
@IHoverService private readonly hoverService: IHoverService,
@ICommandService private readonly commandService: ICommandService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@ -93,11 +98,11 @@ export class AgentStatusWidget extends BaseActionViewItem {
this._commandCenterMenu = this._register(this.menuService.createMenu(MenuId.CommandCenterCenter, this.contextKeyService));
// Re-render when control mode or session info changes
this._register(this.agentStatusService.onDidChangeMode(() => {
this._register(this.agentTitleBarStatusService.onDidChangeMode(() => {
this._render();
}));
this._register(this.agentStatusService.onDidChangeSessionInfo(() => {
this._register(this.agentTitleBarStatusService.onDidChangeSessionInfo(() => {
this._render();
}));
@ -140,8 +145,8 @@ export class AgentStatusWidget extends BaseActionViewItem {
}
// Compute current render state to avoid unnecessary DOM rebuilds
const mode = this.agentStatusService.mode;
const sessionInfo = this.agentStatusService.sessionInfo;
const mode = this.agentTitleBarStatusService.mode;
const sessionInfo = this.agentTitleBarStatusService.sessionInfo;
const { activeSessions, unreadSessions, attentionNeededSessions } = this._getSessionStats();
// Get attention session info for state computation
@ -184,7 +189,7 @@ export class AgentStatusWidget extends BaseActionViewItem {
// Clear previous disposables for dynamic content
this._dynamicDisposables.clear();
if (this.agentStatusService.mode === AgentStatusMode.Session) {
if (this.agentTitleBarStatusService.mode === AgentStatusMode.Session) {
// Agent Session Projection mode - show session title + close button
this._renderSessionMode(this._dynamicDisposables);
} else {
@ -352,7 +357,7 @@ export class AgentStatusWidget extends BaseActionViewItem {
// Session title (center)
const titleLabel = $('span.agent-status-title');
const sessionInfo = this.agentStatusService.sessionInfo;
const sessionInfo = this.agentTitleBarStatusService.sessionInfo;
titleLabel.textContent = sessionInfo?.title ?? localize('agentSessionProjection', "Agent Session Projection");
pill.appendChild(titleLabel);
@ -362,7 +367,7 @@ export class AgentStatusWidget extends BaseActionViewItem {
// Setup pill hover
const hoverDelegate = getDefaultHoverDelegate('mouse');
disposables.add(this.hoverService.setupManagedHover(hoverDelegate, pill, () => {
const sessionInfo = this.agentStatusService.sessionInfo;
const sessionInfo = this.agentTitleBarStatusService.sessionInfo;
return sessionInfo ? localize('agentSessionProjectionTooltip', "Agent Session Projection: {0}", sessionInfo.title) : localize('agentSessionProjection', "Agent Session Projection");
}));
@ -389,7 +394,7 @@ export class AgentStatusWidget extends BaseActionViewItem {
for (const [, actions] of this._commandCenterMenu.getActions({ shouldForwardArgs: true })) {
for (const action of actions) {
// Filter out the quick open action - we provide our own search UI
if (action.id === AgentStatusWidget._quickOpenCommandId) {
if (action.id === AgentTitleBarStatusWidget._quickOpenCommandId) {
continue;
}
// For submenus (like debug toolbar), add the submenu actions
@ -824,3 +829,47 @@ export class AgentStatusWidget extends BaseActionViewItem {
// #endregion
}
/**
* Provides custom rendering for the agent status in the command center.
* Uses IActionViewItemService to render a custom AgentStatusWidget
* for the AgentsControlMenu submenu.
* Also adds a CSS class to the workbench when agent status is enabled.
*/
export class AgentTitleBarStatusRendering extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.agentStatus.rendering';
constructor(
@IActionViewItemService actionViewItemService: IActionViewItemService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService
) {
super();
this._register(actionViewItemService.register(MenuId.CommandCenter, MenuId.AgentsTitleBarControlMenu, (action, options) => {
if (!(action instanceof SubmenuItemAction)) {
return undefined;
}
return instantiationService.createInstance(AgentTitleBarStatusWidget, action, options);
}, undefined));
// Add/remove CSS class on workbench based on setting
// Also force enable command center when agent status is enabled
const updateClass = () => {
const enabled = configurationService.getValue<boolean>(ChatConfiguration.AgentStatusEnabled) === true;
mainWindow.document.body.classList.toggle('agent-status-enabled', enabled);
// Force enable command center when agent status is enabled
if (enabled && configurationService.getValue<boolean>(LayoutSettings.COMMAND_CENTER) !== true) {
configurationService.updateValue(LayoutSettings.COMMAND_CENTER, true);
}
};
updateClass();
this._register(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(ChatConfiguration.AgentStatusEnabled)) {
updateClass();
}
}));
}
}

View File

@ -3,10 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* ========================================
Agent Session Projection Mode - Tab and Editor styling
======================================== */
/* Style all tabs with the same background as the agent status */
.monaco-workbench.agent-session-projection-active .part.editor > .content .editor-group-container > .title .tabs-container > .tab {
background-color: color-mix(in srgb, var(--vscode-progressBar-background) 15%, transparent) !important;

View File

@ -3,10 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* ========================================
Agent Status Widget - Titlebar control
======================================== */
/* Hide command center search box when agent status enabled */
.agent-status-enabled .command-center .action-item.command-center-center {
display: none !important;

View File

@ -19,7 +19,6 @@ import { ChatViewId, ChatViewPaneTarget, IChatWidget, IChatWidgetService, IQuick
import { ChatEditor, IChatEditorOptions } from '../widgetHosts/editor/chatEditor.js';
import { ChatEditorInput } from '../widgetHosts/editor/chatEditorInput.js';
import { ChatViewPane } from '../widgetHosts/viewPane/chatViewPane.js';
import { IWorkbenchLayoutService } from '../../../../services/layout/browser/layoutService.js';
export class ChatWidgetService extends Disposable implements IChatWidgetService {
@ -41,7 +40,6 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService
@ILayoutService private readonly layoutService: ILayoutService,
@IEditorService private readonly editorService: IEditorService,
@IChatService private readonly chatService: IChatService,
@IWorkbenchLayoutService private readonly workbenchLayoutService: IWorkbenchLayoutService,
) {
super();
}
@ -118,9 +116,6 @@ export class ChatWidgetService extends Disposable implements IChatWidgetService
if (!options?.preserveFocus) {
chatView.focusInput();
}
if (options?.expanded) {
this.workbenchLayoutService.setAuxiliaryBarMaximized(true);
}
}
return chatView?.widget;
}

View File

@ -43,7 +43,6 @@ export interface IChatEditorOptions extends IEditorOptions {
preferred?: string;
fallback?: string;
};
expanded?: boolean;
}
export class ChatEditor extends EditorPane {

View File

@ -47,7 +47,7 @@ import { IChatModelReference, IChatService } from '../../../common/chatService/c
import { IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js';
import { LocalChatSessionUri, getChatSessionType } from '../../../common/model/chatUri.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../../common/constants.js';
import { AgentSessionsControl, AgentSessionsControlSource } from '../../agentSessions/agentSessionsControl.js';
import { AgentSessionsControl } from '../../agentSessions/agentSessionsControl.js';
import { AgentSessionsListDelegate } from '../../agentSessions/agentSessionsViewer.js';
import { ACTION_ID_NEW_CHAT } from '../../actions/chatActions.js';
import { ChatWidget } from '../../widget/chatWidget.js';
@ -394,7 +394,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
// Sessions Control
this.sessionsControlContainer = append(sessionsContainer, $('.agent-sessions-control-container'));
const sessionsControl = this.sessionsControl = this._register(this.instantiationService.createInstance(AgentSessionsControl, this.sessionsControlContainer, {
source: AgentSessionsControlSource.ChatViewPane,
source: 'chatViewPane',
filter: sessionsFilter,
overrideStyles: this.getLocationBasedColors().listOverrideStyles,
getHoverPosition: () => {

View File

@ -108,10 +108,6 @@ export namespace ChatContextKeys {
export const hasAgentSessionChanges = new RawContextKey<boolean>('agentSessionHasChanges', false, { type: 'boolean', description: localize('agentSessionHasChanges', "True when the current agent session item has changes.") });
export const isKatexMathElement = new RawContextKey<boolean>('chatIsKatexMathElement', false, { type: 'boolean', description: localize('chatIsKatexMathElement', "True when focusing a KaTeX math element.") });
export const inAgentSessionProjection = new RawContextKey<boolean>('chatInAgentSessionProjection', false, { type: 'boolean', description: localize('chatInAgentSessionProjection', "True when the workbench is in agent session projection mode for reviewing an agent session.") });
export const agentStatusHasNotifications = new RawContextKey<boolean>('agentStatusHasNotifications', false, { type: 'boolean', description: localize('agentStatusHasNotifications', "True when the agent status widget has unread or in-progress sessions.") });
}
export namespace ChatContextKeyExprs {

View File

@ -40,7 +40,7 @@ import { AgentSessionsWelcomeEditorOptions, AgentSessionsWelcomeInput } from './
import { IChatService } from '../../chat/common/chatService/chatService.js';
import { IChatModel } from '../../chat/common/model/chatModel.js';
import { ISessionTypePickerDelegate } from '../../chat/browser/chat.js';
import { AgentSessionsControl, AgentSessionsControlSource, IAgentSessionsControlOptions } from '../../chat/browser/agentSessions/agentSessionsControl.js';
import { AgentSessionsControl, IAgentSessionsControlOptions } from '../../chat/browser/agentSessions/agentSessionsControl.js';
import { IAgentSessionsFilter } from '../../chat/browser/agentSessions/agentSessionsViewer.js';
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
import { IResolvedWalkthrough, IWalkthroughsService } from '../../welcomeGettingStarted/browser/gettingStartedService.js';
@ -280,7 +280,8 @@ export class AgentSessionsWelcomePage extends EditorPane {
filter,
getHoverPosition: () => HoverPosition.BELOW,
trackActiveEditorSession: () => false,
source: AgentSessionsControlSource.WelcomeView,
source: 'welcomeView',
notifySessionOpened: () => this.layoutService.setAuxiliaryBarMaximized(true) // TODO@osortega what if the session did not open in the 2nd sidebar?
};
this.sessionsControl = this.sessionsControlDisposables.add(this.instantiationService.createInstance(
@ -433,7 +434,7 @@ export class AgentSessionsWelcomePage extends EditorPane {
// Set margin offset for 2-column layout: actual height - visual height
// Visual height = ceil(n/2) * 52, so offset = floor(n/2) * 52
const marginOffset = Math.floor(visibleSessions / 2) * 52;
this.sessionsControl.setGridMarginOffset(marginOffset);
this.sessionsControl.element!.style.marginBottom = `-${marginOffset}px`;
}
override focus(): void {