agent sessions - split by "Active" and "History" (#290232)

* agent sessions - split by "Active" and "History"

* comments

* comments

* .

* .
This commit is contained in:
Benjamin Pasero
2026-01-25 17:02:52 +01:00
committed by GitHub
parent 38def400cf
commit b5dcdba622
11 changed files with 133 additions and 116 deletions

View File

@@ -25,6 +25,7 @@
"github.copilot.chat.claudeCode.enabled": true,
"github.copilot.chat.languageContext.typescript.enabled": true,
"diffEditor.renderSideBySide": false,
"diffEditor.hideUnchangedRegions.enabled": true
"diffEditor.hideUnchangedRegions.enabled": true,
"chat.viewSessions.showPendingOnly": false
}
}

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, HideAgentSessionsAction, MarkAgentSessionSectionReadAction, ShowAllAgentSessionsAction, ShowPendingAgentSessionsAction, UnarchiveAgentSessionSectionAction } 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, ShowActiveAgentSessionsAction, UnarchiveAgentSessionSectionAction } from './agentSessionsActions.js';
import { AgentSessionsQuickAccessProvider, AGENT_SESSIONS_QUICK_ACCESS_PREFIX } from './agentSessionsQuickAccess.js';
import { AuxiliaryBarMaximizedContext } from '../../../../common/contextkeys.js';
@@ -44,7 +44,7 @@ registerAction2(ShowAgentSessionsSidebar);
registerAction2(HideAgentSessionsSidebar);
registerAction2(ToggleAgentSessionsSidebar);
registerAction2(ShowAllAgentSessionsAction);
registerAction2(ShowPendingAgentSessionsAction);
registerAction2(ShowActiveAgentSessionsAction);
registerAction2(HideAgentSessionsAction);
registerAction2(SetAgentSessionsOrientationStackedAction);
registerAction2(SetAgentSessionsOrientationSideBySideAction);

View File

@@ -55,7 +55,15 @@ export class ShowAllAgentSessionsAction extends Action2 {
title: localize2('chat.showSessions.all', "All"),
toggled: ContextKeyExpr.and(
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsShowPendingOnly}`, false)
ContextKeyExpr.or(
// Stacked: based on setting
ContextKeyExpr.and(
ChatContextKeys.agentSessionsViewerOrientation.isEqualTo(AgentSessionsViewerOrientation.Stacked),
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsShowActiveOnly}`, false)
),
// Side by side: always checked (active not applicable)
ChatContextKeys.agentSessionsViewerOrientation.isEqualTo(AgentSessionsViewerOrientation.SideBySide)
)
),
menu: {
id: showSessionsSubmenu,
@@ -69,24 +77,25 @@ export class ShowAllAgentSessionsAction extends Action2 {
const configurationService = accessor.get(IConfigurationService);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, true);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsShowPendingOnly, false);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsShowActiveOnly, false);
}
}
export class ShowPendingAgentSessionsAction extends Action2 {
export class ShowActiveAgentSessionsAction extends Action2 {
constructor() {
super({
id: 'workbench.action.chat.showPendingAgentSessions',
title: localize2('chat.showSessions.pending', "Pending"),
id: 'workbench.action.chat.showActiveAgentSessions',
title: localize2('chat.showSessions.active', "Active"),
toggled: ContextKeyExpr.and(
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsEnabled}`, true),
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsShowPendingOnly}`, true)
ContextKeyExpr.equals(`config.${ChatConfiguration.ChatViewSessionsShowActiveOnly}`, true)
),
menu: {
id: showSessionsSubmenu,
group: 'navigation',
order: 2
order: 2,
when: ChatContextKeys.agentSessionsViewerOrientation.isEqualTo(AgentSessionsViewerOrientation.Stacked) // side-by-side does not support this mode
}
});
}
@@ -95,7 +104,7 @@ export class ShowPendingAgentSessionsAction extends Action2 {
const configurationService = accessor.get(IConfigurationService);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsEnabled, true);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsShowPendingOnly, true);
await configurationService.updateValue(ChatConfiguration.ChatViewSessionsShowActiveOnly, true);
}
}
@@ -271,7 +280,7 @@ export class ArchiveAgentSessionSectionAction extends Action2 {
order: 1,
when: ContextKeyExpr.and(
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Done)
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.History)
),
}, {
id: MenuId.AgentSessionSectionContext,
@@ -279,7 +288,7 @@ export class ArchiveAgentSessionSectionAction extends Action2 {
order: 2,
when: ContextKeyExpr.and(
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Archived),
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.Done)
ChatContextKeys.agentSessionSection.notEqualsTo(AgentSessionSection.History)
),
}]
});

View File

@@ -133,8 +133,8 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
const collapseByDefault = (element: unknown) => {
if (isAgentSessionSection(element)) {
if (element.section === AgentSessionSection.Done) {
return true; // Done section is always collapsed
if (element.section === AgentSessionSection.History) {
return true; // History section is always collapsed
}
if (element.section === AgentSessionSection.Archived && this.options.filter.getExcludes().archived) {
return true; // Archived section is collapsed when archived are excluded
@@ -324,12 +324,12 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
}
break;
}
case AgentSessionSection.Done: {
const shouldCollapseDone = !this.sessionsListFindIsOpen; // always expand when find is open
case AgentSessionSection.History: {
const shouldCollapseHistory = !this.sessionsListFindIsOpen; // always expand when find is open
if (shouldCollapseDone && !child.collapsed) {
if (shouldCollapseHistory && !child.collapsed) {
this.sessionsList.collapse(child.element);
} else if (!shouldCollapseDone && child.collapsed) {
} else if (!shouldCollapseHistory && child.collapsed) {
this.sessionsList.expand(child.element);
}
break;

View File

@@ -17,7 +17,7 @@ import { IAgentSessionsFilter, IAgentSessionsFilterExcludes } from './agentSessi
export enum AgentSessionsGrouping {
Default = 'default',
Pending = 'pending',
Active = 'active',
}
export interface IAgentSessionsFilterOptions extends Partial<IAgentSessionsFilter> {

View File

@@ -155,9 +155,9 @@ export const enum AgentSessionSection {
Older = 'older',
Archived = 'archived',
// Pending/Done Grouping
Pending = 'pending',
Done = 'done',
// Active/History Grouping
Active = 'active',
History = 'history',
}
export interface IAgentSessionSection {

View File

@@ -655,8 +655,8 @@ export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsM
const result: AgentSessionListItem[] = [];
const sortedSessions = sessions.sort(this.sorter.compare.bind(this.sorter));
const groupedSessions = this.filter?.groupResults?.() === AgentSessionsGrouping.Pending
? groupAgentSessionsByPending(sortedSessions)
const groupedSessions = this.filter?.groupResults?.() === AgentSessionsGrouping.Active
? groupAgentSessionsByActive(sortedSessions)
: groupAgentSessionsByDefault(sortedSessions);
for (const { sessions, section, label } of groupedSessions.values()) {
@@ -673,7 +673,6 @@ export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsM
const DAY_THRESHOLD = 24 * 60 * 60 * 1000;
const WEEK_THRESHOLD = 7 * DAY_THRESHOLD;
export const WINDOW_SESSION_START_TIME = Date.now();
export const AgentSessionSectionLabels = {
[AgentSessionSection.InProgress]: localize('agentSessions.inProgressSection', "In Progress"),
@@ -682,8 +681,8 @@ 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"),
[AgentSessionSection.Active]: localize('agentSessions.activeSection', "Active"),
[AgentSessionSection.History]: localize('agentSessions.historySection', "History"),
};
export function groupAgentSessionsByDefault(sessions: IAgentSession[]): Map<AgentSessionSection, IAgentSessionSection> {
@@ -728,9 +727,9 @@ export function groupAgentSessionsByDefault(sessions: IAgentSession[]): Map<Agen
]);
}
export function groupAgentSessionsByPending(sessions: IAgentSession[]): Map<AgentSessionSection, IAgentSessionSection> {
const pendingSessions = new Set<IAgentSession>();
const doneSessions = new Set<IAgentSession>();
export function groupAgentSessionsByActive(sessions: IAgentSession[]): Map<AgentSessionSection, IAgentSessionSection> {
const activeSessions = new Set<IAgentSession>();
const historySessions = new Set<IAgentSession>();
const now = Date.now();
const startOfToday = new Date(now).setHours(0, 0, 0, 0);
@@ -744,17 +743,17 @@ export function groupAgentSessionsByPending(sessions: IAgentSession[]): Map<Agen
}
if (session.isArchived()) {
doneSessions.add(session);
historySessions.add(session);
} else {
if (
isSessionInProgressStatus(session.status) || // in-progress
!session.isRead() || // unread
(getAgentChangesSummary(session.changes) && hasValidDiff(session.changes)) || // has changes
sessionTime >= WINDOW_SESSION_START_TIME // newer than this window session
sessionTime >= startOfYesterday // from today or yesterday
) {
pendingSessions.add(session);
activeSessions.add(session);
} else {
doneSessions.add(session);
historySessions.add(session);
}
}
}
@@ -764,15 +763,15 @@ export function groupAgentSessionsByPending(sessions: IAgentSession[]): Map<Agen
if (
mostRecentSession && !mostRecentSession.session.isArchived() &&
mostRecentSession.time >= startOfYesterday &&
!pendingSessions.has(mostRecentSession.session)
!activeSessions.has(mostRecentSession.session)
) {
doneSessions.delete(mostRecentSession.session);
pendingSessions.add(mostRecentSession.session);
historySessions.delete(mostRecentSession.session);
activeSessions.add(mostRecentSession.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.size), sessions: [...doneSessions] }],
[AgentSessionSection.Active, { section: AgentSessionSection.Active, label: AgentSessionSectionLabels[AgentSessionSection.Active], sessions: [...activeSessions] }],
[AgentSessionSection.History, { section: AgentSessionSection.History, label: localize('agentSessions.historySectionWithCount', "History ({0})", historySessions.size), sessions: [...historySessions] }],
]);
}

View File

@@ -412,10 +412,10 @@ 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]: {
[ChatConfiguration.ChatViewSessionsShowActiveOnly]: {
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#`'),
markdownDescription: nls.localize('chat.viewSessions.showActiveOnly', "When enabled, only show active 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',

View File

@@ -129,7 +129,7 @@ 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.sessionsViewerShowActiveOnly = this.configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsShowActiveOnly) ?? 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);
@@ -234,18 +234,18 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
return e.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION);
})(() => this.updateViewPaneClasses(true)));
// Sessions viewer show pending only setting changes
// Sessions viewer show active only setting changes
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => {
return e.affectsConfiguration(ChatConfiguration.ChatViewSessionsShowPendingOnly);
return e.affectsConfiguration(ChatConfiguration.ChatViewSessionsShowActiveOnly);
})(() => {
const oldSessionsViewerShowPendingOnly = this.sessionsViewerShowPendingOnly;
const oldSessionsViewerShowActiveOnly = this.sessionsViewerShowActiveOnly;
if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) {
this.sessionsViewerShowPendingOnly = false; // side by side always shows all
this.sessionsViewerShowActiveOnly = false; // side by side always shows all
} else {
this.sessionsViewerShowPendingOnly = this.configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsShowPendingOnly) ?? true;
this.sessionsViewerShowActiveOnly = this.configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsShowActiveOnly) ?? true;
}
if (oldSessionsViewerShowPendingOnly !== this.sessionsViewerShowPendingOnly) {
if (oldSessionsViewerShowActiveOnly !== this.sessionsViewerShowActiveOnly) {
this.sessionsControl?.update();
}
}));
@@ -342,7 +342,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
private sessionsNewButtonContainer: HTMLElement | undefined;
private sessionsControlContainer: HTMLElement | undefined;
private sessionsControl: AgentSessionsControl | undefined;
private sessionsViewerShowPendingOnly: boolean;
private sessionsViewerShowActiveOnly: boolean;
private sessionsViewerVisible: boolean;
private sessionsViewerOrientation = AgentSessionsViewerOrientation.Stacked;
private sessionsViewerOrientationConfiguration: 'stacked' | 'sideBySide' = 'sideBySide';
@@ -374,7 +374,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
// Sessions Filter
const sessionsFilter = this._register(this.instantiationService.createInstance(AgentSessionsFilter, {
filterMenuId: MenuId.AgentSessionsViewerFilterSubMenu,
groupResults: () => this.sessionsViewerShowPendingOnly ? AgentSessionsGrouping.Pending : AgentSessionsGrouping.Default,
groupResults: () => this.sessionsViewerShowActiveOnly ? AgentSessionsGrouping.Active : AgentSessionsGrouping.Default,
}));
this._register(Event.runAndSubscribe(sessionsFilter.onDidChange, () => {
sessionsToolbarContainer.classList.toggle('filtered', !sessionsFilter.isDefault());
@@ -861,12 +861,12 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
this.sessionsViewerOrientationContext.set(AgentSessionsViewerOrientation.Stacked);
}
// Update show pending only state based on orientation change
// Update show active only state based on orientation change
if (oldSessionsViewerOrientation !== this.sessionsViewerOrientation) {
if (this.sessionsViewerOrientation === AgentSessionsViewerOrientation.SideBySide) {
this.sessionsViewerShowPendingOnly = false; // side by side always shows all
this.sessionsViewerShowActiveOnly = false; // side by side always shows all
} else {
this.sessionsViewerShowPendingOnly = this.configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsShowPendingOnly) ?? true;
this.sessionsViewerShowActiveOnly = this.configurationService.getValue<boolean>(ChatConfiguration.ChatViewSessionsShowActiveOnly) ?? true;
}
const updatePromise = this.sessionsControl.update();

View File

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

View File

@@ -6,7 +6,7 @@
import assert from 'assert';
import { URI } from '../../../../../../base/common/uri.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
import { AgentSessionsDataSource, AgentSessionListItem, groupAgentSessionsByPending, IAgentSessionsFilter, WINDOW_SESSION_START_TIME } from '../../../browser/agentSessions/agentSessionsViewer.js';
import { AgentSessionsDataSource, AgentSessionListItem, groupAgentSessionsByActive, 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';
@@ -379,30 +379,30 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: now - ONE_DAY }),
];
const result = groupAgentSessionsByPending(sessions);
const result = groupAgentSessionsByActive(sessions);
assert.strictEqual(result.size, 2);
assert.ok(result.has(AgentSessionSection.Pending));
assert.ok(result.has(AgentSessionSection.Done));
assert.ok(result.has(AgentSessionSection.Active));
assert.ok(result.has(AgentSessionSection.History));
});
test('In-progress sessions appear in Pending', () => {
// Use session times before WINDOW_SESSION_START_TIME to isolate the in-progress logic
const beforeWindowSession = WINDOW_SESSION_START_TIME - ONE_DAY;
// Use session times before start of yesterday to isolate the in-progress logic
const beforeYesterday = Date.now() - 2 * ONE_DAY;
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.InProgress, startTime: beforeWindowSession }),
createMockSession({ id: '2', status: ChatSessionStatus.NeedsInput, startTime: beforeWindowSession - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 2000 }),
createMockSession({ id: '4', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - ONE_DAY }),
createMockSession({ id: '1', status: ChatSessionStatus.InProgress, startTime: beforeYesterday }),
createMockSession({ id: '2', status: ChatSessionStatus.NeedsInput, startTime: beforeYesterday - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeYesterday - 2000 }),
createMockSession({ id: '4', status: ChatSessionStatus.Completed, startTime: beforeYesterday - ONE_DAY }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
const result = groupAgentSessionsByActive(sessions);
const pendingSection = result.get(AgentSessionSection.Active);
const doneSection = result.get(AgentSessionSection.History);
assert.ok(pendingSection);
assert.ok(doneSection);
// In-progress sessions in Pending, plus session 1 is also the most recent non-archived
// In-progress sessions in Pending
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);
@@ -412,93 +412,101 @@ suite('AgentSessionsDataSource', () => {
});
test('Unread sessions appear in Pending', () => {
// Use session times before WINDOW_SESSION_START_TIME to isolate the unread logic
const beforeWindowSession = WINDOW_SESSION_START_TIME - ONE_DAY;
// Use session times before start of yesterday to isolate the unread logic
const beforeYesterday = Date.now() - 2 * ONE_DAY;
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: beforeWindowSession, isRead: false }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 2000 }),
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: beforeYesterday, isRead: false }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: beforeYesterday - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeYesterday - 2000 }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
const result = groupAgentSessionsByActive(sessions);
const pendingSection = result.get(AgentSessionSection.Active);
const doneSection = result.get(AgentSessionSection.History);
assert.ok(pendingSection);
assert.ok(doneSection);
// Unread session 1 is in Pending (also most recent)
// Unread session 1 is in Pending
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.strictEqual(pendingSection.sessions.length, 1);
// Read sessions 2, 3 are in Done
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', () => {
// Use session times before WINDOW_SESSION_START_TIME to isolate the changes logic
const beforeWindowSession = WINDOW_SESSION_START_TIME - ONE_DAY;
// Use session times before start of yesterday to isolate the changes logic
const beforeYesterday = Date.now() - 2 * ONE_DAY;
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: beforeWindowSession, hasChanges: true }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 2000, hasChanges: true }),
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: beforeYesterday, hasChanges: true }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: beforeYesterday - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeYesterday - 2000, hasChanges: true }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
const result = groupAgentSessionsByActive(sessions);
const pendingSection = result.get(AgentSessionSection.Active);
const doneSection = result.get(AgentSessionSection.History);
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'));
assert.strictEqual(pendingSection.sessions.length, 2);
// 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', () => {
// Use session times clearly before WINDOW_SESSION_START_TIME to test only the "most recent" logic
const beforeWindowSession = WINDOW_SESSION_START_TIME - ONE_DAY;
test('Most recent non-archived session from today or yesterday appears in Pending', () => {
// Use session times from yesterday to test the "most recent from today/yesterday" logic
const yesterday = Date.now() - ONE_DAY;
const beforeYesterday = Date.now() - 2 * ONE_DAY;
const sessions = [
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: beforeWindowSession }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 2000 }),
// Session from yesterday - should be in Pending as most recent from today/yesterday
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: yesterday }),
// Sessions from before yesterday - should be in Done
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: beforeYesterday }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeYesterday - 1000 }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
const result = groupAgentSessionsByActive(sessions);
const pendingSection = result.get(AgentSessionSection.Active);
const doneSection = result.get(AgentSessionSection.History);
assert.ok(pendingSection);
assert.ok(doneSection);
// Most recent non-archived (session 1) in Pending
// Session from yesterday (session 1) in Pending
assert.strictEqual(pendingSection.sessions.length, 1);
assert.strictEqual(pendingSection.sessions[0].label, 'Session 1');
// Other sessions in Done
// Sessions from before yesterday in Done
assert.strictEqual(doneSection.sessions.length, 2);
});
test('Sessions created during current window session appear in Pending', () => {
test('Sessions from today or yesterday appear in Pending', () => {
const now = Date.now();
const beforeWindowSession = WINDOW_SESSION_START_TIME - ONE_DAY;
const yesterday = Date.now() - ONE_DAY;
const beforeYesterday = Date.now() - 2 * ONE_DAY;
const sessions = [
// Session created during current window session should be in Pending
// Sessions from today and yesterday should be in Pending
createMockSession({ id: '1', status: ChatSessionStatus.Completed, startTime: now }),
// Session created before window session should be in Done
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 1000 }),
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeWindowSession - 2000 }),
createMockSession({ id: '2', status: ChatSessionStatus.Completed, startTime: yesterday }),
// Sessions from before yesterday should be in Done
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: beforeYesterday }),
];
const result = groupAgentSessionsByPending(sessions);
const pendingSection = result.get(AgentSessionSection.Pending);
const doneSection = result.get(AgentSessionSection.Done);
const result = groupAgentSessionsByActive(sessions);
const pendingSection = result.get(AgentSessionSection.Active);
const doneSection = result.get(AgentSessionSection.History);
assert.ok(pendingSection);
assert.ok(doneSection);
// Session 1 created during window session should be in Pending
// Sessions from today and yesterday should be in Pending
assert.ok(pendingSection.sessions.some(s => s.label === 'Session 1'));
// Sessions 2 and 3 created before window session should be in Done
// (Session 2 is also made pending by "most recent from today/yesterday" logic)
assert.ok(pendingSection.sessions.some(s => s.label === 'Session 2'));
assert.strictEqual(pendingSection.sessions.length, 2);
// Sessions from before yesterday should be in Done
assert.ok(doneSection.sessions.some(s => s.label === 'Session 3'));
assert.strictEqual(doneSection.sessions.length, 1);
});
test('Archived sessions go to Done even if unread or have changes', () => {
@@ -509,9 +517,9 @@ suite('AgentSessionsDataSource', () => {
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);
const result = groupAgentSessionsByActive(sessions);
const pendingSection = result.get(AgentSessionSection.Active);
const doneSection = result.get(AgentSessionSection.History);
assert.ok(pendingSection);
assert.ok(doneSection);
@@ -532,7 +540,7 @@ suite('AgentSessionsDataSource', () => {
createMockSession({ id: '3', status: ChatSessionStatus.Completed, startTime: now - 2 * ONE_DAY }),
];
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Pending });
const filter = createMockFilter({ groupBy: AgentSessionsGrouping.Active });
const sorter = createMockSorter();
const dataSource = new AgentSessionsDataSource(filter, sorter);
@@ -542,8 +550,8 @@ suite('AgentSessionsDataSource', () => {
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);
assert.strictEqual((result[0] as IAgentSessionSection).section, AgentSessionSection.Active);
assert.strictEqual((result[1] as IAgentSessionSection).section, AgentSessionSection.History);
});
});
});