Merge pull request #279024 from microsoft/ben/little-lizard

agent sessions - refactoring and services
This commit is contained in:
Benjamin Pasero
2025-11-24 08:40:20 +01:00
committed by GitHub
11 changed files with 2154 additions and 1055 deletions

View File

@@ -225,6 +225,7 @@ export class MenuId {
static readonly TimelineFilterSubMenu = new MenuId('TimelineFilterSubMenu');
static readonly AgentSessionsTitle = new MenuId('AgentSessionsTitle');
static readonly AgentSessionsFilterSubMenu = new MenuId('AgentSessionsFilterSubMenu');
static readonly AgentSessionItemToolbar = new MenuId('AgentSessionItemToolbar');
static readonly AccountsContext = new MenuId('AccountsContext');
static readonly SidebarTitle = new MenuId('SidebarTitle');
static readonly PanelTitle = new MenuId('PanelTitle');

View File

@@ -5,7 +5,7 @@
import './media/agentsessionsactions.css';
import { localize, localize2 } from '../../../../../nls.js';
import { IAgentSessionViewModel } from './agentSessionViewModel.js';
import { IAgentSession } from './agentSessionsModel.js';
import { Action, IAction } from '../../../../../base/common/actions.js';
import { ActionViewItem, IActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js';
import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js';
@@ -19,10 +19,8 @@ import { AGENT_SESSIONS_VIEW_ID, AgentSessionProviders } from './agentSessions.j
import { AgentSessionsView } from './agentSessionsView.js';
import { URI } from '../../../../../base/common/uri.js';
import { IChatService } from '../../common/chatService.js';
import { IStorageService } from '../../../../../platform/storage/common/storage.js';
import { resetFilter } from './agentSessionsViewFilter.js';
import { CHAT_CATEGORY } from '../actions/chatActions.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js';
import { CHAT_CATEGORY } from '../actions/chatActions.js';
import { NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js';
//#region New Chat Session Actions
@@ -73,14 +71,56 @@ registerAction2(class NewCloudChatAction extends Action2 {
//#endregion
//#region Diff Statistics Action
//#region Item Title Actions
registerAction2(class extends Action2 {
constructor() {
super({
id: 'agentSession.archive',
title: localize('archive', "Archive"),
icon: Codicon.archive,
menu: {
id: MenuId.AgentSessionItemToolbar,
group: 'navigation',
order: 1,
when: ChatContextKeys.isArchivedItem.negate(),
}
});
}
run(accessor: ServicesAccessor, session: IAgentSession): void {
session.setArchived(true);
}
});
registerAction2(class extends Action2 {
constructor() {
super({
id: 'agentSession.unarchive',
title: localize('unarchive', "Unarchive"),
icon: Codicon.discard,
menu: {
id: MenuId.AgentSessionItemToolbar,
group: 'navigation',
order: 1,
when: ChatContextKeys.isArchivedItem,
}
});
}
run(accessor: ServicesAccessor, session: IAgentSession): void {
session.setArchived(false);
}
});
//#endregion
//#region Item Detail Actions
export class AgentSessionShowDiffAction extends Action {
static ID = 'agentSession.showDiff';
constructor(
private readonly session: IAgentSessionViewModel
private readonly session: IAgentSession
) {
super(AgentSessionShowDiffAction.ID, localize('showDiff', "Open Changes"), undefined, true);
}
@@ -89,7 +129,7 @@ export class AgentSessionShowDiffAction extends Action {
// This will be handled by the action view item
}
getSession(): IAgentSessionViewModel {
getSession(): IAgentSession {
return this.session;
}
}
@@ -219,23 +259,4 @@ MenuRegistry.appendMenuItem(MenuId.AgentSessionsTitle, {
icon: Codicon.filter
} satisfies ISubmenuItem);
registerAction2(class extends Action2 {
constructor() {
super({
id: 'agentSessions.filter.resetExcludes',
title: localize('agentSessions.filter.reset', 'Reset'),
menu: {
id: MenuId.AgentSessionsFilterSubMenu,
group: '4_reset',
order: 0,
},
});
}
run(accessor: ServicesAccessor): void {
const storageService = accessor.get(IStorageService);
resetFilter(storageService);
}
});
//#endregion

View File

@@ -11,9 +11,9 @@ import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contex
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
import { ChatSessionStatus, IChatSessionsService } from '../../common/chatSessionsService.js';
import { AgentSessionProviders, getAgentSessionProviderName } from './agentSessions.js';
import { IAgentSessionViewModel } from './agentSessionViewModel.js';
import { IAgentSession } from './agentSessionsModel.js';
export interface IAgentSessionsViewFilterOptions {
export interface IAgentSessionsFilterOptions {
readonly filterMenuId: MenuId;
}
@@ -29,21 +29,9 @@ const DEFAULT_EXCLUDES: IAgentSessionsViewExcludes = Object.freeze({
archived: true as const,
});
const FILTER_STORAGE_KEY = 'agentSessions.filterExcludes';
export class AgentSessionsFilter extends Disposable {
export function resetFilter(storageService: IStorageService): void {
const excludes = {
providers: [...DEFAULT_EXCLUDES.providers],
states: [...DEFAULT_EXCLUDES.states],
archived: DEFAULT_EXCLUDES.archived,
};
storageService.store(FILTER_STORAGE_KEY, JSON.stringify(excludes), StorageScope.PROFILE, StorageTarget.USER);
}
export class AgentSessionsViewFilter extends Disposable {
private static readonly STORAGE_KEY = FILTER_STORAGE_KEY;
private readonly STORAGE_KEY: string;
private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
@@ -53,12 +41,14 @@ export class AgentSessionsViewFilter extends Disposable {
private actionDisposables = this._register(new DisposableStore());
constructor(
private readonly options: IAgentSessionsViewFilterOptions,
private readonly options: IAgentSessionsFilterOptions,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
@IStorageService private readonly storageService: IStorageService,
) {
super();
this.STORAGE_KEY = `agentSessions.filterExcludes.${this.options.filterMenuId.id.toLowerCase()}`;
this.updateExcludes(false);
this.registerListeners();
@@ -68,16 +58,20 @@ export class AgentSessionsViewFilter extends Disposable {
this._register(this.chatSessionsService.onDidChangeItemsProviders(() => this.updateFilterActions()));
this._register(this.chatSessionsService.onDidChangeAvailability(() => this.updateFilterActions()));
this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, AgentSessionsViewFilter.STORAGE_KEY, this._store)(() => this.updateExcludes(true)));
this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, this.STORAGE_KEY, this._store)(() => this.updateExcludes(true)));
}
private updateExcludes(fromEvent: boolean): void {
const excludedTypesRaw = this.storageService.get(AgentSessionsViewFilter.STORAGE_KEY, StorageScope.PROFILE);
this.excludes = excludedTypesRaw ? JSON.parse(excludedTypesRaw) as IAgentSessionsViewExcludes : {
providers: [...DEFAULT_EXCLUDES.providers],
states: [...DEFAULT_EXCLUDES.states],
archived: DEFAULT_EXCLUDES.archived,
};
const excludedTypesRaw = this.storageService.get(this.STORAGE_KEY, StorageScope.PROFILE);
if (excludedTypesRaw) {
try {
this.excludes = JSON.parse(excludedTypesRaw) as IAgentSessionsViewExcludes;
} catch {
this.resetExcludes();
}
} else {
this.resetExcludes();
}
this.updateFilterActions();
@@ -86,10 +80,18 @@ export class AgentSessionsViewFilter extends Disposable {
}
}
private resetExcludes(): void {
this.excludes = {
providers: [...DEFAULT_EXCLUDES.providers],
states: [...DEFAULT_EXCLUDES.states],
archived: DEFAULT_EXCLUDES.archived,
};
}
private storeExcludes(excludes: IAgentSessionsViewExcludes): void {
this.excludes = excludes;
this.storageService.store(AgentSessionsViewFilter.STORAGE_KEY, JSON.stringify(this.excludes), StorageScope.PROFILE, StorageTarget.USER);
this.storageService.store(this.STORAGE_KEY, JSON.stringify(this.excludes), StorageScope.PROFILE, StorageTarget.USER);
}
private updateFilterActions(): void {
@@ -98,6 +100,7 @@ export class AgentSessionsViewFilter extends Disposable {
this.registerProviderActions(this.actionDisposables);
this.registerStateActions(this.actionDisposables);
this.registerArchivedActions(this.actionDisposables);
this.registerResetAction(this.actionDisposables);
}
private registerProviderActions(disposables: DisposableStore): void {
@@ -121,7 +124,7 @@ export class AgentSessionsViewFilter extends Disposable {
disposables.add(registerAction2(class extends Action2 {
constructor() {
super({
id: `agentSessions.filter.toggleExclude:${provider.id}`,
id: `agentSessions.filter.toggleExclude:${provider.id}.${that.options.filterMenuId.id.toLowerCase()}`,
title: provider.label,
menu: {
id: that.options.filterMenuId,
@@ -156,7 +159,7 @@ export class AgentSessionsViewFilter extends Disposable {
disposables.add(registerAction2(class extends Action2 {
constructor() {
super({
id: `agentSessions.filter.toggleExcludeState:${state.id}`,
id: `agentSessions.filter.toggleExcludeState:${state.id}.${that.options.filterMenuId.id.toLowerCase()}`,
title: state.label,
menu: {
id: that.options.filterMenuId,
@@ -183,7 +186,7 @@ export class AgentSessionsViewFilter extends Disposable {
disposables.add(registerAction2(class extends Action2 {
constructor() {
super({
id: 'agentSessions.filter.toggleExcludeArchived',
id: `agentSessions.filter.toggleExcludeArchived.${that.options.filterMenuId.id.toLowerCase()}`,
title: localize('agentSessions.filter.archived', 'Archived'),
menu: {
id: that.options.filterMenuId,
@@ -199,8 +202,30 @@ export class AgentSessionsViewFilter extends Disposable {
}));
}
exclude(session: IAgentSessionViewModel): boolean {
if (this.excludes.archived && session.archived) {
private registerResetAction(disposables: DisposableStore): void {
const that = this;
disposables.add(registerAction2(class extends Action2 {
constructor() {
super({
id: `agentSessions.filter.resetExcludes.${that.options.filterMenuId.id.toLowerCase()}`,
title: localize('agentSessions.filter.reset', "Reset"),
menu: {
id: MenuId.AgentSessionsFilterSubMenu,
group: '4_reset',
order: 0,
},
});
}
run(): void {
that.resetExcludes();
that.storageService.store(that.STORAGE_KEY, JSON.stringify(that.excludes), StorageScope.PROFILE, StorageTarget.USER);
}
}));
}
exclude(session: IAgentSession): boolean {
if (this.excludes.archived && session.isArchived()) {
return true;
}

View File

@@ -12,29 +12,27 @@ import { Disposable } from '../../../../../base/common/lifecycle.js';
import { ResourceMap } from '../../../../../base/common/map.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { URI, UriComponents } from '../../../../../base/common/uri.js';
import { MenuId } from '../../../../../platform/actions/common/actions.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
import { ChatSessionStatus, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js';
import { AgentSessionProviders, getAgentSessionProviderIcon, getAgentSessionProviderName } from './agentSessions.js';
import { AgentSessionsViewFilter } from './agentSessionsViewFilter.js';
//#region Interfaces, Types
export interface IAgentSessionsViewModel {
export interface IAgentSessionsModel {
readonly onWillResolve: Event<void>;
readonly onDidResolve: Event<void>;
readonly onDidChangeSessions: Event<void>;
readonly sessions: IAgentSessionViewModel[];
readonly sessions: IAgentSession[];
resolve(provider: string | string[] | undefined): Promise<void>;
}
export interface IAgentSessionViewModel {
interface IAgentSessionData {
readonly providerType: string;
readonly providerLabel: string;
@@ -42,7 +40,6 @@ export interface IAgentSessionViewModel {
readonly resource: URI;
readonly status: ChatSessionStatus;
readonly archived: boolean;
readonly tooltip?: string | IMarkdownString;
@@ -65,29 +62,44 @@ export interface IAgentSessionViewModel {
};
}
export function isLocalAgentSessionItem(session: IAgentSessionViewModel): boolean {
export interface IAgentSession extends IAgentSessionData {
isArchived(): boolean;
setArchived(archived: boolean): void;
}
interface IInternalAgentSessionData extends IAgentSessionData {
/**
* The `archived` property is provided by the session provider
* and will be used as the initial value if the user has not
* changed the archived state for the session previously. It
* is kept internal to not expose it publicly. Use `isArchived()`
* and `setArchived()` methods instead.
*/
readonly archived: boolean | undefined;
}
interface IInternalAgentSession extends IAgentSession, IInternalAgentSessionData { }
export function isLocalAgentSessionItem(session: IAgentSession): boolean {
return session.providerType === localChatSessionType;
}
export function isAgentSession(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionViewModel {
const session = obj as IAgentSessionViewModel | undefined;
export function isAgentSession(obj: IAgentSessionsModel | IAgentSession): obj is IAgentSession {
const session = obj as IAgentSession | undefined;
return URI.isUri(session?.resource);
}
export function isAgentSessionsViewModel(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionsViewModel {
const sessionsViewModel = obj as IAgentSessionsViewModel | undefined;
export function isAgentSessionsModel(obj: IAgentSessionsModel | IAgentSession): obj is IAgentSessionsModel {
const sessionsModel = obj as IAgentSessionsModel | undefined;
return Array.isArray(sessionsViewModel?.sessions);
return Array.isArray(sessionsModel?.sessions);
}
//#endregion
export interface IAgentSessionsViewModelOptions {
readonly filterMenuId: MenuId;
}
export class AgentSessionsViewModel extends Disposable implements IAgentSessionsViewModel {
export class AgentSessionsModel extends Disposable implements IAgentSessionsModel {
private readonly _onWillResolve = this._register(new Emitter<void>());
readonly onWillResolve = this._onWillResolve.event;
@@ -98,11 +110,8 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
private readonly _onDidChangeSessions = this._register(new Emitter<void>());
readonly onDidChangeSessions = this._onDidChangeSessions.event;
private _sessions: IAgentSessionViewModel[] = [];
get sessions(): IAgentSessionViewModel[] {
return this._sessions.filter(session => !this.filter.exclude(session));
}
private _sessions: IInternalAgentSession[] = [];
get sessions(): IAgentSession[] { return this._sessions; }
private readonly resolver = this._register(new ThrottledDelayer<void>(100));
private readonly providersToResolve = new Set<string | undefined>();
@@ -114,11 +123,9 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
finishedOrFailedTime?: number;
}>();
private readonly filter: AgentSessionsViewFilter;
private readonly cache: AgentSessionsCache;
constructor(
options: IAgentSessionsViewModelOptions,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@@ -126,12 +133,9 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
) {
super();
this.filter = this._register(this.instantiationService.createInstance(AgentSessionsViewFilter, { filterMenuId: options.filterMenuId }));
this.cache = this.instantiationService.createInstance(AgentSessionsCache);
this._sessions = this.cache.loadCachedSessions();
this.resolve(undefined);
this._sessions = this.cache.loadCachedSessions().map(data => this.toAgentSession(data));
this.sessionStates = this.cache.loadSessionStates();
this.registerListeners();
}
@@ -140,8 +144,10 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
this._register(this.chatSessionsService.onDidChangeItemsProviders(({ chatSessionType: provider }) => this.resolve(provider)));
this._register(this.chatSessionsService.onDidChangeAvailability(() => this.resolve(undefined)));
this._register(this.chatSessionsService.onDidChangeSessionItems(provider => this.resolve(provider)));
this._register(this.filter.onDidChange(() => this._onDidChangeSessions.fire()));
this._register(this.storageService.onWillSaveState(() => this.cache.saveCachedSessions(this._sessions)));
this._register(this.storageService.onWillSaveState(() => {
this.cache.saveCachedSessions(this._sessions);
this.cache.saveSessionStates(this.sessionStates);
}));
}
async resolve(provider: string | string[] | undefined): Promise<void> {
@@ -177,7 +183,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
}
const resolvedProviders = new Set<string>();
const sessions = new ResourceMap<IAgentSessionViewModel>();
const sessions = new ResourceMap<IInternalAgentSession>();
for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) {
if (!providersToResolve.includes(undefined) && !providersToResolve.includes(provider.chatSessionType)) {
continue; // skip: not considered for resolving
@@ -243,7 +249,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
});
}
sessions.set(session.resource, {
sessions.set(session.resource, this.toAgentSession({
providerType: provider.chatSessionType,
providerLabel,
resource: session.resource,
@@ -252,7 +258,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
icon,
tooltip: session.tooltip,
status,
archived: session.archived ?? false,
archived: session.archived,
timing: {
startTime: session.timing.startTime,
endTime: session.timing.endTime,
@@ -260,7 +266,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
finishedOrFailedTime
},
statistics: session.statistics,
});
}));
}
}
@@ -281,11 +287,39 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
this._onDidChangeSessions.fire();
}
private toAgentSession(data: IInternalAgentSessionData): IInternalAgentSession {
return {
...data,
isArchived: () => this.isArchived(data),
setArchived: (archived: boolean) => this.setArchived(data, archived)
};
}
//#region States
private readonly sessionStates: ResourceMap<{ archived: boolean }>;
private isArchived(session: IInternalAgentSessionData): boolean {
return this.sessionStates.get(session.resource)?.archived ?? Boolean(session.archived);
}
private setArchived(session: IInternalAgentSessionData, archived: boolean): void {
if (archived === this.isArchived(session)) {
return; // no change
}
this.sessionStates.set(session.resource, { archived });
this._onDidChangeSessions.fire();
}
//#endregion
}
//#region Sessions Cache
interface ISerializedAgentSessionViewModel {
interface ISerializedAgentSession {
readonly providerType: string;
readonly providerLabel: string;
@@ -300,7 +334,7 @@ interface ISerializedAgentSessionViewModel {
readonly tooltip?: string | IMarkdownString;
readonly status: ChatSessionStatus;
readonly archived: boolean;
readonly archived: boolean | undefined;
readonly timing: {
readonly startTime: number;
@@ -314,14 +348,24 @@ interface ISerializedAgentSessionViewModel {
};
}
interface ISerializedAgentSessionState {
readonly resource: UriComponents;
readonly archived: boolean;
}
class AgentSessionsCache {
private static readonly STORAGE_KEY = 'agentSessions.cache';
private static readonly SESSIONS_STORAGE_KEY = 'agentSessions.model.cache';
private static readonly STATE_STORAGE_KEY = 'agentSessions.state.cache';
constructor(@IStorageService private readonly storageService: IStorageService) { }
constructor(
@IStorageService private readonly storageService: IStorageService
) { }
saveCachedSessions(sessions: IAgentSessionViewModel[]): void {
const serialized: ISerializedAgentSessionViewModel[] = sessions
//#region Sessions
saveCachedSessions(sessions: IInternalAgentSessionData[]): void {
const serialized: ISerializedAgentSession[] = sessions
.filter(session =>
// Only consider providers that we own where we know that
// we can also invalidate the data after startup
@@ -351,17 +395,18 @@ class AgentSessionsCache {
statistics: session.statistics,
}));
this.storageService.store(AgentSessionsCache.STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE, StorageTarget.MACHINE);
this.storageService.store(AgentSessionsCache.SESSIONS_STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE, StorageTarget.MACHINE);
}
loadCachedSessions(): IAgentSessionViewModel[] {
const sessionsCache = this.storageService.get(AgentSessionsCache.STORAGE_KEY, StorageScope.WORKSPACE);
loadCachedSessions(): IInternalAgentSessionData[] {
const sessionsCache = this.storageService.get(AgentSessionsCache.SESSIONS_STORAGE_KEY, StorageScope.WORKSPACE);
if (!sessionsCache) {
return [];
}
try {
const cached = JSON.parse(sessionsCache) as ISerializedAgentSessionViewModel[];
const cached = JSON.parse(sessionsCache) as ISerializedAgentSession[];
return cached.map(session => ({
providerType: session.providerType,
providerLabel: session.providerLabel,
@@ -387,6 +432,44 @@ class AgentSessionsCache {
return []; // invalid data in storage, fallback to empty sessions list
}
}
//#endregion
//#region States
private static readonly STATES_SCOPE = StorageScope.APPLICATION; // use application scope to track globally
saveSessionStates(states: ResourceMap<{ archived: boolean }>): void {
const serialized: ISerializedAgentSessionState[] = Array.from(states.entries()).map(([resource, state]) => ({
resource: resource.toJSON(),
archived: state.archived
}));
this.storageService.store(AgentSessionsCache.STATE_STORAGE_KEY, JSON.stringify(serialized), AgentSessionsCache.STATES_SCOPE, StorageTarget.MACHINE);
}
loadSessionStates(): ResourceMap<{ archived: boolean }> {
const states = new ResourceMap<{ archived: boolean }>();
const statesCache = this.storageService.get(AgentSessionsCache.STATE_STORAGE_KEY, AgentSessionsCache.STATES_SCOPE);
if (!statesCache) {
return states;
}
try {
const cached = JSON.parse(statesCache) as ISerializedAgentSessionState[];
for (const entry of cached) {
states.set(URI.revive(entry.resource), { archived: entry.archived });
}
} catch {
// invalid data in storage, fallback to empty states
}
return states;
}
//#endregion
}
//#endregion

View File

@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from '../../../../../base/common/lifecycle.js';
import { createDecorator, IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { AgentSessionsModel, IAgentSessionsModel } from './agentSessionsModel.js';
export interface IAgentSessionsService {
readonly _serviceBrand: undefined;
readonly model: IAgentSessionsModel;
}
export class AgentSessionsService extends Disposable implements IAgentSessionsService {
declare readonly _serviceBrand: undefined;
private _model: IAgentSessionsModel | undefined;
get model(): IAgentSessionsModel {
if (!this._model) {
this._model = this._register(this.instantiationService.createInstance(AgentSessionsModel));
this._model.resolve(undefined /* all providers */);
}
return this._model;
}
constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) {
super();
}
}
export const IAgentSessionsService = createDecorator<IAgentSessionsService>('agentSessions');

View File

@@ -24,7 +24,7 @@ import { IOpenerService } from '../../../../../platform/opener/common/opener.js'
import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js';
import { $, append } from '../../../../../base/browser/dom.js';
import { AgentSessionsViewModel, IAgentSessionViewModel, IAgentSessionsViewModel, isLocalAgentSessionItem } from './agentSessionViewModel.js';
import { IAgentSession, IAgentSessionsModel, isLocalAgentSessionItem } from './agentSessionsModel.js';
import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsDragAndDrop, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js';
import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js';
@@ -52,11 +52,11 @@ import { TreeFindMode } from '../../../../../base/browser/ui/tree/abstractTree.j
import { SIDE_GROUP } from '../../../../services/editor/common/editorService.js';
import { IMarshalledChatSessionContext } from '../actions/chatSessionActions.js';
import { distinct } from '../../../../../base/common/arrays.js';
import { IAgentSessionsService } from './agentSessionsService.js';
import { AgentSessionsFilter } from './agentSessionsFilter.js';
export class AgentSessionsView extends ViewPane {
private sessionsViewModel: IAgentSessionsViewModel | undefined;
constructor(
options: IViewPaneOptions,
@IKeybindingService keybindingService: IKeybindingService,
@@ -75,6 +75,7 @@ export class AgentSessionsView extends ViewPane {
@IChatService private readonly chatService: IChatService,
@IMenuService private readonly menuService: IMenuService,
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
) {
super({ ...options, titleMenuId: MenuId.AgentSessionsTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
}
@@ -96,17 +97,10 @@ export class AgentSessionsView extends ViewPane {
}
private registerListeners(): void {
// Sessions List
const list = assertReturnsDefined(this.list);
this._register(this.onDidChangeBodyVisibility(visible => {
if (!visible || this.sessionsViewModel) {
return;
}
if (!this.sessionsViewModel) {
this.createViewModel();
} else {
this._register(this.onDidChangeBodyVisibility(visible => {
if (visible) {
this.list?.updateChildren();
}
}));
@@ -126,7 +120,7 @@ export class AgentSessionsView extends ViewPane {
}));
}
private async openAgentSession(e: IOpenEvent<IAgentSessionViewModel | undefined>): Promise<void> {
private async openAgentSession(e: IOpenEvent<IAgentSession | undefined>): Promise<void> {
const session = e.element;
if (!session) {
return;
@@ -153,7 +147,7 @@ export class AgentSessionsView extends ViewPane {
await this.chatWidgetService.openSession(session.resource, group, options);
}
private async showContextMenu({ element: session, anchor }: ITreeContextMenuEvent<IAgentSessionViewModel>): Promise<void> {
private async showContextMenu({ element: session, anchor }: ITreeContextMenuEvent<IAgentSession>): Promise<void> {
if (!session) {
return;
}
@@ -272,9 +266,14 @@ export class AgentSessionsView extends ViewPane {
//#region Sessions List
private listContainer: HTMLElement | undefined;
private list: WorkbenchCompressibleAsyncDataTree<IAgentSessionsViewModel, IAgentSessionViewModel, FuzzyScore> | undefined;
private list: WorkbenchCompressibleAsyncDataTree<IAgentSessionsModel, IAgentSession, FuzzyScore> | undefined;
private listFilter: AgentSessionsFilter | undefined;
private createList(container: HTMLElement): void {
this.listFilter = this._register(this.instantiationService.createInstance(AgentSessionsFilter, {
filterMenuId: MenuId.AgentSessionsFilterSubMenu,
}));
this.listContainer = append(container, $('.agent-sessions-viewer'));
this.list = this._register(this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree,
@@ -285,7 +284,7 @@ export class AgentSessionsView extends ViewPane {
[
this.instantiationService.createInstance(AgentSessionRenderer)
],
new AgentSessionsDataSource(),
new AgentSessionsDataSource(this.listFilter),
{
accessibilityProvider: new AgentSessionsAccessibilityProvider(),
dnd: this.instantiationService.createInstance(AgentSessionsDragAndDrop),
@@ -295,27 +294,27 @@ export class AgentSessionsView extends ViewPane {
findWidgetEnabled: true,
defaultFindMode: TreeFindMode.Filter,
keyboardNavigationLabelProvider: new AgentSessionsKeyboardNavigationLabelProvider(),
sorter: new AgentSessionsSorter(),
sorter: this.instantiationService.createInstance(AgentSessionsSorter),
paddingBottom: AgentSessionsListDelegate.ITEM_HEIGHT,
twistieAdditionalCssClass: () => 'force-no-twistie',
}
)) as WorkbenchCompressibleAsyncDataTree<IAgentSessionsViewModel, IAgentSessionViewModel, FuzzyScore>;
}
)) as WorkbenchCompressibleAsyncDataTree<IAgentSessionsModel, IAgentSession, FuzzyScore>;
private createViewModel(): void {
const sessionsViewModel = this.sessionsViewModel = this._register(this.instantiationService.createInstance(AgentSessionsViewModel, { filterMenuId: MenuId.AgentSessionsFilterSubMenu }));
this.list?.setInput(sessionsViewModel);
const model = this.agentSessionsService.model;
this._register(sessionsViewModel.onDidChangeSessions(() => {
this._register(Event.any(
this.listFilter.onDidChange,
model.onDidChangeSessions
)(() => {
if (this.isBodyVisible()) {
this.list?.updateChildren();
}
}));
const didResolveDisposable = this._register(new MutableDisposable());
this._register(sessionsViewModel.onWillResolve(() => {
this._register(model.onWillResolve(() => {
const didResolve = new DeferredPromise<void>();
didResolveDisposable.value = Event.once(sessionsViewModel.onDidResolve)(() => didResolve.complete());
didResolveDisposable.value = Event.once(model.onDidResolve)(() => didResolve.complete());
this.progressService.withProgress(
{
@@ -326,6 +325,8 @@ export class AgentSessionsView extends ViewPane {
() => didResolve.p
);
}));
this.list?.setInput(model);
}
//#endregion
@@ -337,7 +338,7 @@ export class AgentSessionsView extends ViewPane {
}
refresh(): void {
this.sessionsViewModel?.resolve(undefined);
this.agentSessionsService.model.resolve(undefined);
}
//#endregion

View File

@@ -13,7 +13,7 @@ import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compres
import { ICompressibleKeyboardNavigationLabelProvider, ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js';
import { ITreeNode, ITreeElementRenderDetails, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction } from '../../../../../base/browser/ui/tree/tree.js';
import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
import { IAgentSessionViewModel, IAgentSessionsViewModel, isAgentSession, isAgentSessionsViewModel } from './agentSessionViewModel.js';
import { IAgentSession, IAgentSessionsModel, isAgentSession, isAgentSessionsModel } from './agentSessionsModel.js';
import { IconLabel } from '../../../../../base/browser/ui/iconLabel/iconLabel.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { Codicon } from '../../../../../base/common/codicons.js';
@@ -37,6 +37,11 @@ import { AGENT_SESSIONS_VIEW_ID } from './agentSessions.js';
import { IntervalTimer } from '../../../../../base/common/async.js';
import { ActionBar } from '../../../../../base/browser/ui/actionbar/actionbar.js';
import { AgentSessionDiffActionViewItem, AgentSessionShowDiffAction } from './agentSessionsActions.js';
import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js';
import { MenuId } from '../../../../../platform/actions/common/actions.js';
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js';
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
interface IAgentSessionItemTemplate {
readonly element: HTMLElement;
@@ -46,17 +51,19 @@ interface IAgentSessionItemTemplate {
// Column 2 Row 1
readonly title: IconLabel;
readonly titleToolbar: MenuWorkbenchToolBar;
// Column 2 Row 2
readonly toolbar: ActionBar;
readonly detailsToolbar: ActionBar;
readonly description: HTMLElement;
readonly status: HTMLElement;
readonly contextKeyService: IContextKeyService;
readonly elementDisposable: DisposableStore;
readonly disposables: IDisposable;
}
export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSessionViewModel, FuzzyScore, IAgentSessionItemTemplate> {
export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSession, FuzzyScore, IAgentSessionItemTemplate> {
static readonly TEMPLATE_ID = 'agent-session';
@@ -69,6 +76,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
@IHoverService private readonly hoverService: IHoverService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) { }
renderTemplate(container: HTMLElement): IAgentSessionItemTemplate {
@@ -84,9 +92,10 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
h('div.agent-session-main-col', [
h('div.agent-session-title-row', [
h('div.agent-session-title@title'),
h('div.agent-session-title-toolbar@titleToolbar'),
]),
h('div.agent-session-details-row', [
h('div.agent-session-toolbar@toolbar'),
h('div.agent-session-details-toolbar@detailsToolbar'),
h('div.agent-session-description@description'),
h('div.agent-session-status@status')
])
@@ -94,9 +103,13 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
]
);
container.appendChild(elements.item);
const contextKeyService = disposables.add(this.contextKeyService.createScoped(elements.item));
const scopedInstantiationService = disposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])));
const titleToolbar = disposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, elements.titleToolbar, MenuId.AgentSessionItemToolbar, {
menuOptions: { shouldForwardArgs: true },
}));
const toolbar = disposables.add(new ActionBar(elements.toolbar, {
const detailsToolbar = disposables.add(new ActionBar(elements.detailsToolbar, {
actionViewItemProvider: (action, options) => {
if (action.id === AgentSessionShowDiffAction.ID) {
return this.instantiationService.createInstance(AgentSessionDiffActionViewItem, action, options);
@@ -106,23 +119,27 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
},
}));
container.appendChild(elements.item);
return {
element: elements.item,
icon: elements.icon,
title: disposables.add(new IconLabel(elements.title, { supportHighlights: true, supportIcons: true })),
toolbar,
titleToolbar,
detailsToolbar,
description: elements.description,
status: elements.status,
contextKeyService,
elementDisposable,
disposables
};
}
renderElement(session: ITreeNode<IAgentSessionViewModel, FuzzyScore>, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
renderElement(session: ITreeNode<IAgentSession, FuzzyScore>, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
// Clear old state
template.elementDisposable.clear();
template.toolbar.clear();
template.detailsToolbar.clear();
template.description.textContent = '';
// Icon
@@ -131,11 +148,15 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
// Title
template.title.setLabel(session.element.label, undefined, { matches: createMatches(session.filterData) });
// Diff if provided and finished
// Title Actions - Update context keys
ChatContextKeys.isArchivedItem.bindTo(template.contextKeyService).set(session.element.isArchived());
template.titleToolbar.context = session.element;
// Details Actions
const { statistics: diff } = session.element;
if (session.element.status !== ChatSessionStatus.InProgress && diff && (diff.files > 0 || diff.insertions > 0 || diff.deletions > 0)) {
const diffAction = template.elementDisposable.add(new AgentSessionShowDiffAction(session.element));
template.toolbar.push([diffAction], { icon: false, label: true });
template.detailsToolbar.push([diffAction], { icon: false, label: true });
}
// Description otherwise
@@ -150,7 +171,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
this.renderHover(session, template);
}
private getIcon(session: IAgentSessionViewModel): ThemeIcon {
private getIcon(session: IAgentSession): ThemeIcon {
if (session.status === ChatSessionStatus.InProgress) {
return ThemeIcon.modify(Codicon.loading, 'spin');
}
@@ -162,7 +183,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
return session.icon;
}
private renderDescription(session: ITreeNode<IAgentSessionViewModel, FuzzyScore>, template: IAgentSessionItemTemplate): void {
private renderDescription(session: ITreeNode<IAgentSession, FuzzyScore>, template: IAgentSessionItemTemplate): void {
// Support description as string
if (typeof session.element.description === 'string') {
@@ -213,9 +234,9 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
return getDurationString(elapsed);
}
private renderStatus(session: ITreeNode<IAgentSessionViewModel, FuzzyScore>, template: IAgentSessionItemTemplate): void {
private renderStatus(session: ITreeNode<IAgentSession, FuzzyScore>, template: IAgentSessionItemTemplate): void {
const getStatus = (session: IAgentSessionViewModel) => {
const getStatus = (session: IAgentSession) => {
let timeLabel: string | undefined;
if (session.status === ChatSessionStatus.InProgress && session.timing.inProgressTime) {
timeLabel = this.toDuration(session.timing.inProgressTime, Date.now());
@@ -232,7 +253,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
timer.cancelAndSet(() => template.status.textContent = getStatus(session.element), session.element.status === ChatSessionStatus.InProgress ? 1000 /* every second */ : 60 * 1000 /* every minute */);
}
private renderHover(session: ITreeNode<IAgentSessionViewModel, FuzzyScore>, template: IAgentSessionItemTemplate): void {
private renderHover(session: ITreeNode<IAgentSession, FuzzyScore>, template: IAgentSessionItemTemplate): void {
const tooltip = session.element.tooltip;
if (tooltip) {
template.elementDisposable.add(
@@ -258,11 +279,11 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
}
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAgentSessionViewModel>, FuzzyScore>, index: number, templateData: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAgentSession>, FuzzyScore>, index: number, templateData: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
throw new Error('Should never happen since session is incompressible');
}
disposeElement(element: ITreeNode<IAgentSessionViewModel, FuzzyScore>, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
disposeElement(element: ITreeNode<IAgentSession, FuzzyScore>, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
template.elementDisposable.clear();
}
@@ -271,48 +292,56 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
}
}
export class AgentSessionsListDelegate implements IListVirtualDelegate<IAgentSessionViewModel> {
export class AgentSessionsListDelegate implements IListVirtualDelegate<IAgentSession> {
static readonly ITEM_HEIGHT = 44;
getHeight(element: IAgentSessionViewModel): number {
getHeight(element: IAgentSession): number {
return AgentSessionsListDelegate.ITEM_HEIGHT;
}
getTemplateId(element: IAgentSessionViewModel): string {
getTemplateId(element: IAgentSession): string {
return AgentSessionRenderer.TEMPLATE_ID;
}
}
export class AgentSessionsAccessibilityProvider implements IListAccessibilityProvider<IAgentSessionViewModel> {
export class AgentSessionsAccessibilityProvider implements IListAccessibilityProvider<IAgentSession> {
getWidgetAriaLabel(): string {
return localize('agentSessions', "Agent Sessions");
}
getAriaLabel(element: IAgentSessionViewModel): string | null {
getAriaLabel(element: IAgentSession): string | null {
return element.label;
}
}
export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsViewModel, IAgentSessionViewModel> {
export interface IAgentSessionsDataFilter {
exclude(session: IAgentSession): boolean;
}
hasChildren(element: IAgentSessionsViewModel | IAgentSessionViewModel): boolean {
return isAgentSessionsViewModel(element);
export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsModel, IAgentSession> {
constructor(
private readonly filter: IAgentSessionsDataFilter
) { }
hasChildren(element: IAgentSessionsModel | IAgentSession): boolean {
return isAgentSessionsModel(element);
}
getChildren(element: IAgentSessionsViewModel | IAgentSessionViewModel): Iterable<IAgentSessionViewModel> {
if (!isAgentSessionsViewModel(element)) {
getChildren(element: IAgentSessionsModel | IAgentSession): Iterable<IAgentSession> {
if (!isAgentSessionsModel(element)) {
return [];
}
return element.sessions;
return element.sessions.filter(session => !this.filter.exclude(session));
}
}
export class AgentSessionsIdentityProvider implements IIdentityProvider<IAgentSessionsViewModel | IAgentSessionViewModel> {
export class AgentSessionsIdentityProvider implements IIdentityProvider<IAgentSessionsModel | IAgentSession> {
getId(element: IAgentSessionsViewModel | IAgentSessionViewModel): string {
getId(element: IAgentSessionsModel | IAgentSession): string {
if (isAgentSession(element)) {
return element.resource.toString();
}
@@ -321,16 +350,16 @@ export class AgentSessionsIdentityProvider implements IIdentityProvider<IAgentSe
}
}
export class AgentSessionsCompressionDelegate implements ITreeCompressionDelegate<IAgentSessionViewModel> {
export class AgentSessionsCompressionDelegate implements ITreeCompressionDelegate<IAgentSession> {
isIncompressible(element: IAgentSessionViewModel): boolean {
isIncompressible(element: IAgentSession): boolean {
return true;
}
}
export class AgentSessionsSorter implements ITreeSorter<IAgentSessionViewModel> {
export class AgentSessionsSorter implements ITreeSorter<IAgentSession> {
compare(sessionA: IAgentSessionViewModel, sessionB: IAgentSessionViewModel): number {
compare(sessionA: IAgentSession, sessionB: IAgentSession): number {
const aInProgress = sessionA.status === ChatSessionStatus.InProgress;
const bInProgress = sessionB.status === ChatSessionStatus.InProgress;
@@ -341,23 +370,33 @@ export class AgentSessionsSorter implements ITreeSorter<IAgentSessionViewModel>
return 1; // a (finished) comes after b (in-progress)
}
const aArchived = sessionA.isArchived();
const bArchived = sessionB.isArchived();
if (!aArchived && bArchived) {
return -1; // a (non-archived) comes before b (archived)
}
if (aArchived && !bArchived) {
return 1; // a (archived) comes after b (non-archived)
}
// Both in-progress or finished: sort by end or start time (most recent first)
return (sessionB.timing.endTime || sessionB.timing.startTime) - (sessionA.timing.endTime || sessionA.timing.startTime);
}
}
export class AgentSessionsKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider<IAgentSessionViewModel> {
export class AgentSessionsKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider<IAgentSession> {
getKeyboardNavigationLabel(element: IAgentSessionViewModel): string {
getKeyboardNavigationLabel(element: IAgentSession): string {
return element.label;
}
getCompressedNodeKeyboardNavigationLabel(elements: IAgentSessionViewModel[]): { toString(): string | undefined } | undefined {
getCompressedNodeKeyboardNavigationLabel(elements: IAgentSession[]): { toString(): string | undefined } | undefined {
return undefined; // not enabled
}
}
export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAndDrop<IAgentSessionViewModel> {
export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAndDrop<IAgentSession> {
constructor(
@IInstantiationService private readonly instantiationService: IInstantiationService
@@ -366,16 +405,16 @@ export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAnd
}
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
const elements = data.getData() as IAgentSessionViewModel[];
const elements = data.getData() as IAgentSession[];
const uris = coalesce(elements.map(e => e.resource));
this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, uris, originalEvent));
}
getDragURI(element: IAgentSessionViewModel): string | null {
getDragURI(element: IAgentSession): string | null {
return element.resource.toString();
}
getDragLabel?(elements: IAgentSessionViewModel[], originalEvent: DragEvent): string | undefined {
getDragLabel?(elements: IAgentSession[], originalEvent: DragEvent): string | undefined {
if (elements.length === 1) {
return elements[0].label;
}
@@ -383,9 +422,9 @@ export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAnd
return localize('agentSessions.dragLabel', "{0} agent sessions", elements.length);
}
onDragOver(data: IDragAndDropData, targetElement: IAgentSessionViewModel | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
onDragOver(data: IDragAndDropData, targetElement: IAgentSession | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
return false;
}
drop(data: IDragAndDropData, targetElement: IAgentSessionViewModel | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { }
drop(data: IDragAndDropData, targetElement: IAgentSession | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { }
}

View File

@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.agent-sessions-viewer .agent-session-item .agent-session-toolbar {
.agent-sessions-viewer .agent-session-item .agent-session-details-toolbar {
.monaco-action-bar .actions-container .action-item .action-label {
padding: 0;
@@ -31,7 +31,7 @@
}
}
.monaco-list-row.selected .agent-session-item .agent-session-toolbar {
.monaco-list-row.selected .agent-session-item .agent-session-details-toolbar {
.agent-session-diff-files,
.agent-session-diff-added,

View File

@@ -5,7 +5,7 @@
.agent-sessions-viewer {
.monaco-list-row > .monaco-tl-row > .monaco-tl-twistie.force-no-twistie {
.monaco-list-row .force-no-twistie {
display: none !important;
}
@@ -19,6 +19,15 @@
}
}
.monaco-list-row .agent-session-title-toolbar .monaco-toolbar {
visibility: hidden;
}
.monaco-list-row:hover .agent-session-title-toolbar .monaco-toolbar,
.monaco-list-row.focused .agent-session-title-toolbar .monaco-toolbar {
visibility: visible;
}
.agent-session-item {
display: flex;
flex-direction: row;

View File

@@ -132,6 +132,7 @@ import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js';
import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js';
import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js';
import { ChatWidgetService } from './chatWidgetService.js';
import { AgentSessionsService, IAgentSessionsService } from './agentSessions/agentSessionsService.js';
const toolReferenceNameEnumValues: string[] = [];
const toolReferenceNameEnumDescriptions: string[] = [];
@@ -1195,6 +1196,7 @@ registerSingleton(IChatAttachmentResolveService, ChatAttachmentResolveService, I
registerSingleton(IChatTodoListService, ChatTodoListService, InstantiationType.Delayed);
registerSingleton(IChatOutputRendererService, ChatOutputRendererService, InstantiationType.Delayed);
registerSingleton(IChatLayoutService, ChatLayoutService, InstantiationType.Delayed);
registerSingleton(IAgentSessionsService, AgentSessionsService, InstantiationType.Delayed);
registerAction2(ConfigureToolSets);
registerAction2(RenameChatSessionAction);