mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-31 00:10:04 +08:00
agent sessions - better recent sessions list (#289900)
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
),
|
||||
}]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
Reference in New Issue
Block a user