agent sessions - better recent sessions list (#289900)

This commit is contained in:
Benjamin Pasero
2026-01-23 16:48:37 +01:00
committed by GitHub
parent 1e4e3a8628
commit 17fe11effb
13 changed files with 396 additions and 154 deletions

View File

@@ -16,7 +16,7 @@ import { IAgentSessionsService, AgentSessionsService } from './agentSessionsServ
import { LocalAgentsSessionsProvider } from './localAgentSessionsProvider.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, MarkAgentSessionSectionReadAction, UnarchiveAgentSessionSectionAction, ToggleChatViewSessionsAction } from './agentSessionsActions.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, ShowPendingAgentSessionsAction, UnarchiveAgentSessionSectionAction } from './agentSessionsActions.js';
import { AgentSessionsQuickAccessProvider, AGENT_SESSIONS_QUICK_ACCESS_PREFIX } from './agentSessionsQuickAccess.js';
import { AuxiliaryBarMaximizedContext } from '../../../../common/contextkeys.js';
@@ -43,7 +43,9 @@ registerAction2(FindAgentSessionInViewerAction);
registerAction2(ShowAgentSessionsSidebar);
registerAction2(HideAgentSessionsSidebar);
registerAction2(ToggleAgentSessionsSidebar);
registerAction2(ToggleChatViewSessionsAction);
registerAction2(ShowAllAgentSessionsAction);
registerAction2(ShowPendingAgentSessionsAction);
registerAction2(HideAgentSessionsAction);
registerAction2(SetAgentSessionsOrientationStackedAction);
registerAction2(SetAgentSessionsOrientationSideBySideAction);

View File

@@ -38,17 +38,29 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../../pla
//#region Chat View
export class ToggleChatViewSessionsAction extends Action2 {
const showSessionsSubmenu = new MenuId('chatShowSessionsSubmenu');
MenuRegistry.appendMenuItem(MenuId.ChatWelcomeContext, {
submenu: showSessionsSubmenu,
title: localize2('chat.showSessions', "Show Sessions"),
group: '0_sessions',
order: 1,
when: ChatContextKeys.inChatEditor.negate()
});
export class ShowAllAgentSessionsAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.toggleChatViewSessions',
title: localize2('chat.toggleChatViewSessions.label', "Show Sessions"),
toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),
id: 'workbench.action.chat.showAllAgentSessions',
title: localize2('chat.showSessions.all', "All"),
toggled: ContextKeyExpr.and(
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsShowPendingOnly}`, false)
),
menu: {
id: MenuId.ChatWelcomeContext,
group: '0_sessions',
order: 1,
when: ChatContextKeys.inChatEditor.negate()
id: showSessionsSubmenu,
group: 'navigation',
order: 1
}
});
}
@@ -56,8 +68,56 @@ export class ToggleChatViewSessionsAction extends Action2 {
async run(accessor: ServicesAccessor): Promise<void> {
const configurationService = accessor.get(IConfigurationService);
const chatViewSessionsEnabled = configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsEnabled);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, !chatViewSessionsEnabled);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, true);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsShowPendingOnly, false);
}
}
export class ShowPendingAgentSessionsAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.showPendingAgentSessions',
title: localize2('chat.showSessions.pending', "Pending"),
toggled: ContextKeyExpr.and(
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsShowPendingOnly}`, true)
),
menu: {
id: showSessionsSubmenu,
group: 'navigation',
order: 2
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const configurationService = accessor.get(IConfigurationService);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, true);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsShowPendingOnly, true);
}
}
export class HideAgentSessionsAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.hideAgentSessions',
title: localize2('chat.showSessions.none', "None"),
toggled: ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, false),
menu: {
id: showSessionsSubmenu,
group: 'navigation',
order: 3
}
});
}
async run(accessor: ServicesAccessor): Promise<void> {
const configurationService = accessor.get(IConfigurationService);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, false);
}
}
@@ -209,12 +269,18 @@ export class ArchiveAgentSessionSectionAction extends Action2 {
id: MenuId.AgentSessionSectionToolbar,
group: 'navigation',
order: 1,
when: ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
when: ContextKeyExpr.and(
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Done)
),
}, {
id: MenuId.AgentSessionSectionContext,
group: '1_edit',
order: 2,
when: ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
when: ContextKeyExpr.and(
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Done)
),
}]
});
}

View File

@@ -131,6 +131,19 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
private createList(container: HTMLElement): void {
this.sessionsContainer = append(container, $('.agent-sessions-viewer'));
const collapseByDefault = (element: unknown) => {
if (isAgentSessionSection(element)) {
if (element.section === AgentSessionSection.Done) {
return true; // Done section is always collapsed
}
if (element.section === AgentSessionSection.Archived && this.options.filter.getExcludes().archived) {
return true; // Archived section is collapsed when archived are excluded
}
}
return false;
};
const sorter = new AgentSessionsSorter(this.options);
const list = this.sessionsList = this._register(this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree,
'AgentSessionsView',
@@ -152,9 +165,9 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
defaultFindMode: TreeFindMode.Filter,
keyboardNavigationLabelProvider: new AgentSessionsKeyboardNavigationLabelProvider(),
overrideStyles: this.options.overrideStyles,
expandOnlyOnTwistieClick: (element: unknown) => !(isAgentSessionSection(element) && element.section === AgentSessionSection.Archived && this.options.filter.getExcludes().archived),
twistieAdditionalCssClass: () => 'force-no-twistie',
collapseByDefault: (element: unknown) => isAgentSessionSection(element) && element.section === AgentSessionSection.Archived && this.options.filter.getExcludes().archived,
collapseByDefault: (element: unknown) => collapseByDefault(element),
expandOnlyOnTwistieClick: element => !collapseByDefault(element),
renderIndentGuides: RenderIndentGuides.None,
}
)) as WorkbenchCompressibleAsyncDataTree<IAgentSessionsModel, AgentSessionListItem, FuzzyScore>;
@@ -165,7 +178,7 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
this._register(this.options.filter.onDidChange(async () => {
if (this.visible) {
this.updateArchivedSectionCollapseState();
this.updateSectionCollapseStates();
list.updateChildren();
}
}));
@@ -206,7 +219,7 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
this._register(list.onDidChangeFindOpenState(open => {
this.sessionsListFindIsOpen = open;
this.updateArchivedSectionCollapseState();
this.updateSectionCollapseStates();
}));
}
@@ -287,27 +300,41 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
this.sessionsList?.openFind();
}
private updateArchivedSectionCollapseState(): void {
private updateSectionCollapseStates(): void {
if (!this.sessionsList) {
return;
}
const model = this.agentSessionsService.model;
for (const child of this.sessionsList.getNode(model).children) {
if (!isAgentSessionSection(child.element) || child.element.section !== AgentSessionSection.Archived) {
if (!isAgentSessionSection(child.element)) {
continue;
}
const shouldCollapseArchived =
!this.sessionsListFindIsOpen && // always expand when find is open
this.options.filter.getExcludes().archived; // only collapse when archived are excluded from filter
switch (child.element.section) {
case AgentSessionSection.Archived: {
const shouldCollapseArchived =
!this.sessionsListFindIsOpen && // always expand when find is open
this.options.filter.getExcludes().archived; // only collapse when archived are excluded from filter
if (shouldCollapseArchived && !child.collapsed) {
this.sessionsList.collapse(child.element);
} else if (!shouldCollapseArchived && child.collapsed) {
this.sessionsList.expand(child.element);
if (shouldCollapseArchived && !child.collapsed) {
this.sessionsList.collapse(child.element);
} else if (!shouldCollapseArchived && child.collapsed) {
this.sessionsList.expand(child.element);
}
break;
}
case AgentSessionSection.Done: {
const shouldCollapseDone = !this.sessionsListFindIsOpen; // always expand when find is open
if (shouldCollapseDone && !child.collapsed) {
this.sessionsList.collapse(child.element);
} else if (!shouldCollapseDone && child.collapsed) {
this.sessionsList.expand(child.element);
}
break;
}
}
break;
}
}

View File

@@ -15,6 +15,11 @@ import { AgentSessionProviders, getAgentSessionProviderName } from './agentSessi
import { AgentSessionStatus, IAgentSession } from './agentSessionsModel.js';
import { IAgentSessionsFilter, IAgentSessionsFilterExcludes } from './agentSessionsViewer.js';
export enum AgentSessionsGrouping {
Default = 'default',
Pending = 'pending',
}
export interface IAgentSessionsFilterOptions extends Partial<IAgentSessionsFilter> {
readonly filterMenuId: MenuId;
@@ -22,7 +27,7 @@ export interface IAgentSessionsFilterOptions extends Partial<IAgentSessionsFilte
readonly limitResults?: () => number | undefined;
notifyResults?(count: number): void;
readonly groupResults?: () => boolean | undefined;
readonly groupResults?: () => AgentSessionsGrouping | undefined;
overrideExclude?(session: IAgentSession): boolean | undefined;
}

View File

@@ -146,12 +146,18 @@ interface IAgentSessionState {
}
export const enum AgentSessionSection {
// Default Grouping
InProgress = 'inProgress',
Today = 'today',
Yesterday = 'yesterday',
Week = 'week',
Older = 'older',
Archived = 'archived',
// Pending/Done Grouping
Pending = 'pending',
Done = 'done',
}
export interface IAgentSessionSection {

View File

@@ -15,7 +15,7 @@ import { IQuickInputButton, IQuickInputService, IQuickPickItem, IQuickPickSepara
import { openSession } from './agentSessionsOpener.js';
import { IAgentSession, isLocalAgentSessionItem } from './agentSessionsModel.js';
import { IAgentSessionsService } from './agentSessionsService.js';
import { AgentSessionsSorter, groupAgentSessions } from './agentSessionsViewer.js';
import { AgentSessionsSorter, groupAgentSessionsByDefault } from './agentSessionsViewer.js';
import { AGENT_SESSION_DELETE_ACTION_ID, AGENT_SESSION_RENAME_ACTION_ID } from './agentSessions.js';
interface ISessionPickItem extends IQuickPickItem {
@@ -129,7 +129,7 @@ export class AgentSessionsPicker {
const sessions = this.agentSessionsService.model.sessions.sort(this.sorter.compare.bind(this.sorter));
const items: (ISessionPickItem | IQuickPickSeparator)[] = [];
const groupedSessions = groupAgentSessions(sessions);
const groupedSessions = groupAgentSessionsByDefault(sessions);
for (const group of groupedSessions.values()) {
if (group.sessions.length > 0) {

View File

@@ -10,7 +10,7 @@ import { IInstantiationService } from '../../../../../platform/instantiation/com
import { IMatch, matchesFuzzy } from '../../../../../base/common/filters.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { IAgentSessionsService } from './agentSessionsService.js';
import { AgentSessionsSorter, groupAgentSessions } from './agentSessionsViewer.js';
import { AgentSessionsSorter, groupAgentSessionsByDefault } from './agentSessionsViewer.js';
import { IAgentSession } from './agentSessionsModel.js';
import { openSession } from './agentSessionsOpener.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
@@ -40,7 +40,7 @@ export class AgentSessionsQuickAccessProvider extends PickerQuickAccessProvider<
const picks: Array<IPickerQuickAccessItem | IQuickPickSeparator> = [];
const sessions = this.agentSessionsService.model.sessions.sort(this.sorter.compare.bind(this.sorter));
const groupedSessions = groupAgentSessions(sessions);
const groupedSessions = groupAgentSessionsByDefault(sessions);
for (const group of groupedSessions.values()) {
if (group.sessions.length > 0) {

View File

@@ -40,6 +40,7 @@ import { Event } from '../../../../../base/common/event.js';
import { renderAsPlaintext } from '../../../../../base/browser/markdownRenderer.js';
import { MarkdownString, IMarkdownString } from '../../../../../base/common/htmlContent.js';
import { AgentSessionHoverWidget } from './agentSessionHoverWidget.js';
import { AgentSessionsGrouping } from './agentSessionsFilter.js';
export type AgentSessionListItem = IAgentSession | IAgentSessionSection;
@@ -549,9 +550,9 @@ export interface IAgentSessionsFilter {
/**
* Whether to show section headers to group sessions.
* When false, sessions are shown as a flat list.
* When undefined, sessions are shown as a flat list.
*/
readonly groupResults?: () => boolean | undefined;
readonly groupResults?: () => AgentSessionsGrouping | undefined;
/**
* A callback to notify the filter about the number of
@@ -641,7 +642,9 @@ export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsM
const result: AgentSessionListItem[] = [];
const sortedSessions = sessions.sort(this.sorter.compare.bind(this.sorter));
const groupedSessions = groupAgentSessions(sortedSessions);
const groupedSessions = this.filter?.groupResults?.() === AgentSessionsGrouping.Pending
? groupAgentSessionsByPending(sortedSessions)
: groupAgentSessionsByDefault(sortedSessions);
for (const { sessions, section, label } of groupedSessions.values()) {
if (sessions.length === 0) {
@@ -665,9 +668,11 @@ export const AgentSessionSectionLabels = {
[AgentSessionSection.Week]: localize('agentSessions.weekSection', "Last Week"),
[AgentSessionSection.Older]: localize('agentSessions.olderSection', "Older"),
[AgentSessionSection.Archived]: localize('agentSessions.archivedSection', "Archived"),
[AgentSessionSection.Pending]: localize('agentSessions.pendingSection', "Pending"),
[AgentSessionSection.Done]: localize('agentSessions.doneSection', "Done"),
};
export function groupAgentSessions(sessions: IAgentSession[]): Map<AgentSessionSection, IAgentSessionSection> {
export function groupAgentSessionsByDefault(sessions: IAgentSession[]): Map<AgentSessionSection, IAgentSessionSection> {
const now = Date.now();
const startOfToday = new Date(now).setHours(0, 0, 0, 0);
const startOfYesterday = startOfToday - DAY_THRESHOLD;
@@ -709,6 +714,36 @@ export function groupAgentSessions(sessions: IAgentSession[]): Map<AgentSessionS
]);
}
export function groupAgentSessionsByPending(sessions: IAgentSession[]): Map<AgentSessionSection, IAgentSessionSection> {
const pendingSessions: IAgentSession[] = [];
const doneSessions: IAgentSession[] = [];
let mostRecentNonArchived: IAgentSession | undefined;
for (const session of sessions) {
if (session.isArchived()) {
doneSessions.push(session);
} else {
mostRecentNonArchived ??= session;
if (
isSessionInProgressStatus(session.status) || // in-progress
!session.isRead() || // unread
(getAgentChangesSummary(session.changes) && hasValidDiff(session.changes)) || // has changes
session === mostRecentNonArchived // most recent non-archived (helps restore the session after restart when chat is cleared)
) {
pendingSessions.push(session);
} else {
doneSessions.push(session);
}
}
}
return new Map<AgentSessionSection, IAgentSessionSection>([
[AgentSessionSection.Pending, { section: AgentSessionSection.Pending, label: AgentSessionSectionLabels[AgentSessionSection.Pending], sessions: pendingSessions }],
[AgentSessionSection.Done, { section: AgentSessionSection.Done, label: localize('agentSessions.doneSectionWithCount', "Done ({0})", doneSessions.length), sessions: doneSessions }],
]);
}
export class AgentSessionsIdentityProvider implements IIdentityProvider<IAgentSessionsModel | AgentSessionListItem> {
getId(element: IAgentSessionsModel | AgentSessionListItem): string {

View File

@@ -409,6 +409,11 @@ configurationRegistry.registerConfiguration({
default: true,
description: nls.localize('chat.viewSessions.enabled', "Show chat agent sessions when chat is empty or to the side when chat view is wide enough."),
},
[ChatConfiguration.ChatViewSessionsShowPendingOnly]: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('chat.viewSessions.showPendingOnly', "When enabled, only show pending sessions in the stacked sessions view. When disabled, show all sessions. This setting requires {0} to be enabled.", '`#chat.viewSessions.enabled#`'),
},
[ChatConfiguration.ChatViewSessionsOrientation]: {
type: 'string',
enum: ['stacked', 'sideBySide'],

View File

@@ -48,7 +48,6 @@ import { IChatSessionsService, localChatSessionType } from '../../../common/chat
import { LocalChatSessionUri, getChatSessionType } from '../../../common/model/chatUri.js';
import { ChatAgentLocation, ChatConfiguration, ChatModeKind } from '../../../common/constants.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';
import { ChatViewWelcomeController, IViewWelcomeDelegate } from '../../viewsWelcome/chatViewWelcomeController.js';
@@ -58,7 +57,7 @@ import { IProgressService } from '../../../../../../platform/progress/common/pro
import { ChatViewId } from '../../chat.js';
import { IActivityService, ProgressBadge } from '../../../../../services/activity/common/activity.js';
import { disposableTimeout } from '../../../../../../base/common/async.js';
import { AgentSessionsFilter } from '../../agentSessions/agentSessionsFilter.js';
import { AgentSessionsFilter, AgentSessionsGrouping } from '../../agentSessions/agentSessionsFilter.js';
import { IAgentSessionsService } from '../../agentSessions/agentSessionsService.js';
import { HoverPosition } from '../../../../../../base/browser/ui/hover/hoverWidget.js';
import { IAgentSession } from '../../agentSessions/agentSessionsModel.js';
@@ -68,7 +67,6 @@ interface IChatViewPaneState extends Partial<IChatModelInputState> {
sessionId?: string;
sessionsSidebarWidth?: number;
sessionsStackedHeight?: number;
}
type ChatViewPaneOpenedClassification = {
@@ -131,10 +129,9 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
) {
this.viewState.sessionId = undefined; // clear persisted session on fresh start
}
this.sessionsViewerShowPendingOnly = this.configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsShowPendingOnly) ?? true;
this.sessionsViewerVisible = false; // will be updated from layout code
this.sessionsViewerSidebarWidth = Math.max(ChatViewPane.SESSIONS_SIDEBAR_MIN_WIDTH, this.viewState.sessionsSidebarWidth ?? ChatViewPane.SESSIONS_SIDEBAR_DEFAULT_WIDTH);
this.sessionsViewerStackedHeight = this.viewState.sessionsStackedHeight ?? ChatViewPane.SESSIONS_STACKED_DEFAULT_HEIGHT;
// Contextkeys
this.chatViewLocationContext = ChatContextKeys.panelLocation.bindTo(contextKeyService);
@@ -237,6 +234,22 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
return e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION);
})(() => this.updateViewPaneClasses(true)));
// Sessions viewer show pending only setting changes
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => {
return e.affectsConfiguration(ChatConfiguration.ChatViewSessionsShowPendingOnly);
})(() => {
const oldSessionsViewerShowPendingOnly = this.sessionsViewerShowPendingOnly;
if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) {
this.sessionsViewerShowPendingOnly = false; // side by side always shows all
} else {
this.sessionsViewerShowPendingOnly = this.configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsShowPendingOnly) ?? true;
}
if (oldSessionsViewerShowPendingOnly !== this.sessionsViewerShowPendingOnly) {
this.sessionsControl?.update();
}
}));
// Entitlement changes
this._register(this.chatEntitlementService.onDidChangeSentiment(() => {
this.updateViewPaneClasses(true);
@@ -320,8 +333,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
private static readonly SESSIONS_SIDEBAR_MIN_WIDTH = 200;
private static readonly SESSIONS_SIDEBAR_SNAP_THRESHOLD = this.SESSIONS_SIDEBAR_MIN_WIDTH / 2; // snap to hide when dragged below half of minimum width
private static readonly SESSIONS_SIDEBAR_DEFAULT_WIDTH = 300;
private static readonly SESSIONS_STACKED_MIN_HEIGHT = AgentSessionsListDelegate.ITEM_HEIGHT;
private static readonly SESSIONS_STACKED_DEFAULT_HEIGHT = 5 * AgentSessionsListDelegate.ITEM_HEIGHT;
private static readonly CHAT_WIDGET_DEFAULT_WIDTH = 300;
private static readonly SESSIONS_SIDEBAR_VIEW_MIN_WIDTH = this.CHAT_WIDGET_DEFAULT_WIDTH + this.SESSIONS_SIDEBAR_DEFAULT_WIDTH;
@@ -331,7 +342,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
private sessionsNewButtonContainer: HTMLElement | undefined;
private sessionsControlContainer: HTMLElement | undefined;
private sessionsControl: AgentSessionsControl | undefined;
private sessionsCount = 0;
private sessionsViewerShowPendingOnly: boolean;
private sessionsViewerVisible: boolean;
private sessionsViewerOrientation = AgentSessionsViewerOrientation.Stacked;
private sessionsViewerOrientationConfiguration: 'stacked' | 'sideBySide' = 'sideBySide';
@@ -339,12 +350,10 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
private sessionsViewerVisibilityContext: IContextKey<boolean>;
private sessionsViewerPositionContext: IContextKey<AgentSessionsViewerPosition>;
private sessionsViewerSidebarWidth: number;
private sessionsViewerStackedHeight: number;
private sessionsViewerSash: Sash | undefined;
private readonly sessionsViewerSashDisposables = this._register(new MutableDisposable<DisposableStore>());
private createSessionsControl(parent: HTMLElement): AgentSessionsControl {
const that = this;
const sessionsContainer = this.sessionsContainer = parent.appendChild($('.agent-sessions-container'));
// Sessions Title
@@ -365,10 +374,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
// Sessions Filter
const sessionsFilter = this._register(this.instantiationService.createInstance(AgentSessionsFilter, {
filterMenuId: MenuId.AgentSessionsViewerFilterSubMenu,
groupResults() { return true; },
notifyResults(count: number) {
that.notifySessionsControlCountChanged(count);
},
groupResults: () => this.sessionsViewerShowPendingOnly ? AgentSessionsGrouping.Pending : AgentSessionsGrouping.Default,
}));
this._register(Event.runAndSubscribe(sessionsFilter.onDidChange, () => {
sessionsToolbarContainer.classList.toggle('filtered', !sessionsFilter.isDefault());
@@ -442,17 +448,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
}
}
private notifySessionsControlCountChanged(newSessionsCount?: number): void {
const countChanged = typeof newSessionsCount === 'number' && newSessionsCount !== this.sessionsCount;
this.sessionsCount = newSessionsCount ?? this.sessionsCount;
const { changed: visibilityChanged, visible } = this.updateSessionsControlVisibility();
if (visibilityChanged || (countChanged && visible)) {
this.relayout();
}
}
private updateSessionsControlVisibility(): { changed: boolean; visible: boolean } {
if (!this.sessionsContainer || !this.viewPaneContainer) {
return { changed: false, visible: false };
@@ -587,7 +582,10 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.Stacked) {
sessionsControl.clearFocus(); // improve visual appearance when switching visibility by clearing focus
}
this.notifySessionsControlCountChanged();
const { changed: visibilityChanged } = this.updateSessionsControlVisibility();
if (visibilityChanged) {
this.relayout();
}
}));
// Track the active chat model and reveal it in the sessions control if side-by-side
@@ -863,29 +861,36 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
this.sessionsViewerOrientationContext.set(AgentSessionsViewerOrientation.Stacked);
}
// Update based on orientation change
// Update show pending only state based on orientation change
if (oldSessionsViewerOrientation !== this.sessionsViewerOrientation) {
if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) {
this.sessionsViewerShowPendingOnly = false; // side by side always shows all
} else {
this.sessionsViewerShowPendingOnly = this.configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsShowPendingOnly) ?? true;
}
const updatePromise = this.sessionsControl.update();
// Switching to side-by-side, reveal the current session after elements have loaded
if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) {
const sessionResource = this._widget?.viewModel?.sessionResource;
if (sessionResource) {
this.sessionsControl?.reveal(sessionResource);
}
updatePromise.then(() => {
const sessionResource = this._widget?.viewModel?.sessionResource;
if (sessionResource) {
this.sessionsControl?.reveal(sessionResource);
}
});
}
}
// Ensure visibility is in sync before we layout
const { visible: sessionsContainerVisible } = this.updateSessionsControlVisibility();
// Handle Sash
if (!sessionsContainerVisible || oldSessionsViewerOrientation !== this.sessionsViewerOrientation /* re-create on orientation change */) {
// Handle Sash (only visible in side-by-side)
if (!sessionsContainerVisible || this.sessionsViewerOrientation === AgentSessionsViewerOrientation.Stacked) {
this.sessionsViewerSashDisposables.clear();
this.sessionsViewerSash = undefined;
}
if (sessionsContainerVisible) {
const needsSashRecreation = !this.sessionsViewerSashDisposables.value || oldSessionsViewerOrientation !== this.sessionsViewerOrientation;
if (needsSashRecreation && this.viewPaneContainer) {
} else if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) {
if (!this.sessionsViewerSashDisposables.value && this.viewPaneContainer) {
this.createSessionsViewerSash(this.viewPaneContainer, height, width);
}
}
@@ -916,12 +921,11 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
// Show stacked
else {
const sessionsHeight = this.computeEffectiveStackedSessionsHeight(availableSessionsHeight) - 1 /* border bottom */;
const sessionsHeight = availableSessionsHeight - 1 /* border bottom */;
this.sessionsControlContainer.style.height = `${sessionsHeight}px`;
this.sessionsControlContainer.style.width = ``;
this.sessionsControl.layout(sessionsHeight, width);
this.sessionsViewerSash?.layout();
heightReduction = this.sessionsContainer.offsetHeight;
widthReduction = 0; // stacked on top of the chat widget
@@ -940,16 +944,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
);
}
private computeEffectiveStackedSessionsHeight(availableHeight: number, sessionsViewerStackedHeight = this.sessionsViewerStackedHeight): number {
return Math.max(
ChatViewPane.SESSIONS_STACKED_MIN_HEIGHT, // never smaller than min height for stacked sessions
Math.min(
sessionsViewerStackedHeight,
availableHeight // never taller than available height
)
);
}
getLastDimensions(orientation: AgentSessionsViewerOrientation): { height: number; width: number } | undefined {
return this.lastDimensionsPerOrientation.get(orientation);
}
@@ -957,14 +951,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
private createSessionsViewerSash(container: HTMLElement, height: number, width: number): void {
const disposables = this.sessionsViewerSashDisposables.value = new DisposableStore();
if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) {
this.createSideBySideSash(container, height, width, disposables);
} else {
this.createStackedSash(container, height, width, disposables);
}
}
private createSideBySideSash(container: HTMLElement, height: number, width: number, disposables: DisposableStore): void {
const sash = this.sessionsViewerSash = disposables.add(new Sash(container, {
getVerticalSashLeft: () => {
const sessionsViewerSidebarWidth = this.computeEffectiveSideBySideSessionsSidebarWidth(this.lastDimensions?.width ?? width);
@@ -1009,49 +995,6 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
}));
}
private createStackedSash(container: HTMLElement, height: number, width: number, disposables: DisposableStore): void {
const sash = this.sessionsViewerSash = disposables.add(new Sash(container, {
getHorizontalSashTop: () => {
if (!this.sessionsContainer || !this.sessionsTitleContainer) {
return 0;
}
const titleHeight = this.sessionsTitleContainer.offsetHeight;
const availableHeight = (this.lastDimensions?.height ?? height) - titleHeight - Math.max(ChatViewPane.MIN_CHAT_WIDGET_HEIGHT, this._widget?.input?.height.get() ?? 0);
const sessionsHeight = this.computeEffectiveStackedSessionsHeight(availableHeight);
return titleHeight + sessionsHeight;
}
}, { orientation: Orientation.HORIZONTAL }));
let sashStartHeight: number | undefined;
disposables.add(sash.onDidStart(() => sashStartHeight = this.sessionsViewerStackedHeight));
disposables.add(sash.onDidEnd(() => sashStartHeight = undefined));
disposables.add(sash.onDidChange(e => {
if (sashStartHeight === undefined || !this.lastDimensions || !this.sessionsTitleContainer) {
return;
}
const titleHeight = this.sessionsTitleContainer.offsetHeight;
const delta = e.currentY - e.startY;
const newHeight = sashStartHeight + delta;
const availableHeight = this.lastDimensions.height - titleHeight - Math.max(ChatViewPane.MIN_CHAT_WIDGET_HEIGHT, this._widget?.input?.height.get() ?? 0);
this.sessionsViewerStackedHeight = this.computeEffectiveStackedSessionsHeight(availableHeight, newHeight);
this.viewState.sessionsStackedHeight = this.sessionsViewerStackedHeight;
this.layoutBody(this.lastDimensions.height, this.lastDimensions.width);
}));
disposables.add(sash.onDidReset(() => {
this.sessionsViewerStackedHeight = ChatViewPane.SESSIONS_STACKED_DEFAULT_HEIGHT;
this.viewState.sessionsStackedHeight = this.sessionsViewerStackedHeight;
this.relayout();
}));
}
//#endregion
override saveState(): void {

View File

@@ -33,6 +33,7 @@ export enum ChatConfiguration {
TodosShowWidget = 'chat.tools.todos.showWidget',
NotifyWindowOnResponseReceived = 'chat.notifyWindowOnResponseReceived',
ChatViewSessionsEnabled = 'chat.viewSessions.enabled',
ChatViewSessionsShowPendingOnly = 'chat.viewSessions.showPendingOnly',
ChatViewSessionsOrientation = 'chat.viewSessions.orientation',
ChatViewTitleEnabled = 'chat.viewTitle.enabled',
SubagentToolCustomAgents = 'chat.customAgentInSubagent.enabled',

View File

@@ -6,12 +6,13 @@
import assert from 'assert';
import { URI } from '../../../../../../base/common/uri.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
import { AgentSessionsDataSource, AgentSessionListItem, IAgentSessionsFilter } from '../../../browser/agentSessions/agentSessionsViewer.js';
import { AgentSessionsDataSource, AgentSessionListItem, groupAgentSessionsByPending, IAgentSessionsFilter } from '../../../browser/agentSessions/agentSessionsViewer.js';
import { AgentSessionSection, IAgentSession, IAgentSessionSection, IAgentSessionsModel, isAgentSessionSection } from '../../../browser/agentSessions/agentSessionsModel.js';
import { ChatSessionStatus, isSessionInProgressStatus } from '../../../common/chatSessionsService.js';
import { ITreeSorter } from '../../../../../../base/browser/ui/tree/tree.js';
import { Codicon } from '../../../../../../base/common/codicons.js';
import { Event } from '../../../../../../base/common/event.js';
import { AgentSessionsGrouping } from '../../../browser/agentSessions/agentSessionsFilter.js';
suite('AgentSessionsDataSource', () => {
@@ -24,6 +25,8 @@ suite('AgentSessionsDataSource', () => {
id: string;
status: ChatSessionStatus;
isArchived: boolean;
isRead: boolean;
hasChanges: boolean;
startTime: number;
endTime: number;
}> = {}): IAgentSession {
@@ -40,9 +43,10 @@ suite('AgentSessionsDataSource', () => {
lastRequestEnded: undefined,
lastRequestStarted: undefined,
},
changes: overrides.hasChanges ? { files: 1, insertions: 10, deletions: 5 } : undefined,
isArchived: () => overrides.isArchived ?? false,
setArchived: () => { },
isRead: () => true,
isRead: () => overrides.isRead ?? true,
setRead: () => { },
};
}
@@ -61,12 +65,12 @@ suite('AgentSessionsDataSource', () => {
}
function createMockFilter(options: {
groupResults: boolean;
groupBy?: AgentSessionsGrouping;
exclude?: (session: IAgentSession) => boolean;
}): IAgentSessionsFilter {
return {
onDidChange: Event.None,
groupResults: () => options.groupResults,
groupResults: () => options.groupBy,
exclude: options.exclude ?? (() => false),
getExcludes: () => ({ providers: [], states: [], archived: false, read: false })
};
@@ -96,7 +100,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '2', startTime: now - ONE_DAY, endTime: now - ONE_DAY }),
];
const filter = createMockFilter({ groupResults: false });
const filter = createMockFilter({ groupBy: undefined });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -116,7 +120,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '3', status: ChatSessionStatus.NeedsInput, startTime: now - 2 * ONE_DAY }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -139,7 +143,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '2', status: ChatSessionStatus.InProgress, startTime: now - ONE_DAY }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -160,7 +164,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - ONE_DAY, endTime: now - ONE_DAY }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -179,7 +183,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - WEEK_THRESHOLD - ONE_DAY, endTime: now - WEEK_THRESHOLD - ONE_DAY }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -197,7 +201,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '2', status: ChatSessionStatus.Completed, isArchived: true, startTime: now - ONE_DAY, endTime: now - ONE_DAY }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -215,7 +219,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - WEEK_THRESHOLD - ONE_DAY, endTime: now - WEEK_THRESHOLD - ONE_DAY }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -235,7 +239,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: 'active', status: ChatSessionStatus.InProgress, startTime: now }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -269,7 +273,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: 'active', status: ChatSessionStatus.InProgress, startTime: now }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -304,7 +308,7 @@ suite('AgentSessionsDataSource', () => {
});
test('empty sessions returns empty result', () => {
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -321,7 +325,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - 1000, endTime: now - 1000 }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -344,7 +348,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: 'week2', status: ChatSessionStatus.Completed, startTime: now - 2 * ONE_DAY, endTime: now - 2 * ONE_DAY }),
];
const filter = createMockFilter({ groupResults: true });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Default });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -365,4 +369,153 @@ suite('AgentSessionsDataSource', () => {
assert.strictEqual(olderSection.sessions[1].label, 'Session old1');
});
});
suite('groupSessionsByPending', () => {
test('groups sessions into Pending and Done sections', () => {
const now = Date.now();
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: now }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - ONE_DAY }),
];
const result = groupAgentSessionsByPending(sessions);
assert.strictEqual(result.size, 2);
assert.ok(result.has(AgentSessionSection.Pending));
assert.ok(result.has(AgentSessionSection.Done));
});
test('In-progress sessions appear in Pending', () => {
const now = Date.now();
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.InProgress, startTime: now }),
createMockSession({ id: '2', status: ChatSessionStatus.NeedsInput, startTime: now - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: now - 2000 }),
createMockSession({ id: '4', status: ChatSessionStatus.Completed, startTime: now - ONE_DAY }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
assert.ok(pendingSection);
assert.ok(doneSection);
// In-progress sessions in Pending, plus session 1 is also the most recent non-archived
assert.ok(pendingSection.sessions.some(s => s.label === 'Session 1'));
assert.ok(pendingSection.sessions.some(s => s.label === 'Session 2'));
assert.strictEqual(pendingSection.sessions.length, 2);
// Completed sessions in Done
assert.ok(doneSection.sessions.some(s => s.label === 'Session 3'));
assert.ok(doneSection.sessions.some(s => s.label === 'Session 4'));
});
test('Unread sessions appear in Pending', () => {
const now = Date.now();
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: now, isRead: false }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: now - 2000 }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
assert.ok(pendingSection);
assert.ok(doneSection);
// Unread session 1 is in Pending (also most recent)
assert.ok(pendingSection.sessions.some(s => s.label === 'Session 1'));
// Read sessions 2, 3 are in Done (session 2 not most recent since session 1 is)
assert.ok(doneSection.sessions.some(s => s.label === 'Session 2'));
assert.ok(doneSection.sessions.some(s => s.label === 'Session 3'));
});
test('Sessions with changes appear in Pending', () => {
const now = Date.now();
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: now, hasChanges: true }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: now - 2000, hasChanges: true }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
assert.ok(pendingSection);
assert.ok(doneSection);
// Sessions with changes in Pending
assert.ok(pendingSection.sessions.some(s => s.label === 'Session 1'));
assert.ok(pendingSection.sessions.some(s => s.label === 'Session 3'));
// Session 2 is in Done (no changes, read)
assert.ok(doneSection.sessions.some(s => s.label === 'Session 2'));
});
test('Most recent non-archived session always appears in Pending', () => {
const now = Date.now();
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: now }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: now - 2000 }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
assert.ok(pendingSection);
assert.ok(doneSection);
// Most recent non-archived (session 1) in Pending
assert.strictEqual(pendingSection.sessions.length, 1);
assert.strictEqual(pendingSection.sessions[0].label, 'Session 1');
// Other sessions in Done
assert.strictEqual(doneSection.sessions.length, 2);
});
test('Archived sessions go to Done even if unread or have changes', () => {
const now = Date.now();
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: now }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - 1000, isArchived: true, isRead: false }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: now - 2000, isArchived: true, hasChanges: true }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
assert.ok(pendingSection);
assert.ok(doneSection);
// Only most recent non-archived in Pending
assert.strictEqual(pendingSection.sessions.length, 1);
assert.strictEqual(pendingSection.sessions[0].label, 'Session 1');
// Archived sessions go to Done regardless of unread/changes
assert.strictEqual(doneSection.sessions.length, 2);
assert.ok(doneSection.sessions.some(s => s.label === 'Session 2'));
assert.ok(doneSection.sessions.some(s => s.label === 'Session 3'));
});
test('works with AgentSessionsDataSource when groupBy is Pending', () => {
const now = Date.now();
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.InProgress, startTime: now }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - ONE_DAY }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: now - 2 * ONE_DAY }),
];
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Pending });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
const mockModel = createMockModel(sessions);
const result = Array.from(dataSource.getChildren(mockModel));
assert.strictEqual(result.length, 2);
assert.ok(isAgentSessionSection(result[0]));
assert.ok(isAgentSessionSection(result[1]));
assert.strictEqual((result[0] as IAgentSessionSection).section, AgentSessionSection.Pending);
assert.strictEqual((result[1] as IAgentSessionSection).section, AgentSessionSection.Done);
});
});
});

View File

@@ -477,7 +477,6 @@ export class AgentSessionsWelcomePage extends EditorPane {
const filter: IAgentSessionsFilter = {
onDidChange: onDidChangeEmitter.event,
limitResults: () => MAX_SESSIONS,
groupResults: () => false,
exclude: (session: IAgentSession) => session.isArchived(),
getExcludes: () => ({
providers: [],