Skip to content

Commit fad8e87

Browse files
authored
Improve discoverability of archived chats (fix #286815) (#286999)
1 parent 3bb929b commit fad8e87

File tree

7 files changed

+108
-43
lines changed

7 files changed

+108
-43
lines changed

src/vs/workbench/contrib/chat/browser/agentSessions/agentSessions.contribution.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { IAgentSessionsService, AgentSessionsService } from './agentSessionsServ
1515
import { LocalAgentsSessionsProvider } from './localAgentSessionsProvider.js';
1616
import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../common/contributions.js';
1717
import { ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
18-
import { ArchiveAgentSessionAction, ArchiveAgentSessionSectionAction, UnarchiveAgentSessionAction, OpenAgentSessionInEditorGroupAction, OpenAgentSessionInNewEditorGroupAction, OpenAgentSessionInNewWindowAction, ShowAgentSessionsSidebar, HideAgentSessionsSidebar, ToggleAgentSessionsSidebar, RefreshAgentSessionsViewerAction, FindAgentSessionInViewerAction, MarkAgentSessionUnreadAction, MarkAgentSessionReadAction, FocusAgentSessionsAction, SetAgentSessionsOrientationStackedAction, SetAgentSessionsOrientationSideBySideAction, ToggleChatViewSessionsAction, PickAgentSessionAction, ArchiveAllAgentSessionsAction, RenameAgentSessionAction, DeleteAgentSessionAction, DeleteAllLocalSessionsAction } from './agentSessionsActions.js';
18+
import { ArchiveAgentSessionAction, ArchiveAgentSessionSectionAction, UnarchiveAgentSessionSectionAction, UnarchiveAgentSessionAction, OpenAgentSessionInEditorGroupAction, OpenAgentSessionInNewEditorGroupAction, OpenAgentSessionInNewWindowAction, ShowAgentSessionsSidebar, HideAgentSessionsSidebar, ToggleAgentSessionsSidebar, RefreshAgentSessionsViewerAction, FindAgentSessionInViewerAction, MarkAgentSessionUnreadAction, MarkAgentSessionReadAction, FocusAgentSessionsAction, SetAgentSessionsOrientationStackedAction, SetAgentSessionsOrientationSideBySideAction, ToggleChatViewSessionsAction, PickAgentSessionAction, ArchiveAllAgentSessionsAction, RenameAgentSessionAction, DeleteAgentSessionAction, DeleteAllLocalSessionsAction } from './agentSessionsActions.js';
1919
import { AgentSessionsQuickAccessProvider, AGENT_SESSIONS_QUICK_ACCESS_PREFIX } from './agentSessionsQuickAccess.js';
2020

2121
//#region Actions and Menus
@@ -24,6 +24,7 @@ registerAction2(FocusAgentSessionsAction);
2424
registerAction2(PickAgentSessionAction);
2525
registerAction2(ArchiveAllAgentSessionsAction);
2626
registerAction2(ArchiveAgentSessionSectionAction);
27+
registerAction2(UnarchiveAgentSessionSectionAction);
2728
registerAction2(ArchiveAgentSessionAction);
2829
registerAction2(UnarchiveAgentSessionAction);
2930
registerAction2(RenameAgentSessionAction);

src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsActions.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,46 @@ export class ArchiveAgentSessionSectionAction extends Action2 {
240240
}
241241
}
242242

243+
export class UnarchiveAgentSessionSectionAction extends Action2 {
244+
245+
constructor() {
246+
super({
247+
id: 'agentSessionSection.unarchive',
248+
title: localize2('unarchiveSection', "Unarchive All"),
249+
icon: Codicon.unarchive,
250+
menu: {
251+
id: MenuId.AgentSessionSectionToolbar,
252+
group: 'navigation',
253+
order: 1,
254+
when: ChatContextKeys.agentSessionSection.isEqualTo(AgentSessionSection.Archived),
255+
}
256+
});
257+
}
258+
259+
async run(accessor: ServicesAccessor, context?: IAgentSessionSection): Promise<void> {
260+
if (!context || !isAgentSessionSection(context)) {
261+
return;
262+
}
263+
264+
const dialogService = accessor.get(IDialogService);
265+
266+
const confirmed = await dialogService.confirm({
267+
message: context.sessions.length === 1
268+
? localize('unarchiveSectionSessions.confirmSingle', "Are you sure you want to unarchive 1 agent session?")
269+
: localize('unarchiveSectionSessions.confirm', "Are you sure you want to unarchive {0} agent sessions?", context.sessions.length),
270+
primaryButton: localize('unarchiveSectionSessions.unarchive', "Unarchive All")
271+
});
272+
273+
if (!confirmed.confirmed) {
274+
return;
275+
}
276+
277+
for (const session of context.sessions) {
278+
session.setArchived(false);
279+
}
280+
}
281+
}
282+
243283
//#endregion
244284

245285
//#region Session Actions

src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsControl.ts

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ import { IEditorService } from '../../../../services/editor/common/editorService
3434
import { ChatEditorInput } from '../widgetHosts/editor/chatEditorInput.js';
3535

3636
export interface IAgentSessionsControlOptions extends IAgentSessionsSorterOptions {
37-
readonly overrideStyles?: IStyleOverride<IListStyles>;
38-
readonly filter?: IAgentSessionsFilter;
37+
readonly overrideStyles: IStyleOverride<IListStyles>;
38+
readonly filter: IAgentSessionsFilter;
3939

4040
getHoverPosition(): HoverPosition;
4141
trackActiveEditorSession(): boolean;
@@ -139,9 +139,9 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
139139
defaultFindMode: TreeFindMode.Filter,
140140
keyboardNavigationLabelProvider: new AgentSessionsKeyboardNavigationLabelProvider(),
141141
overrideStyles: this.options.overrideStyles,
142-
expandOnlyOnTwistieClick: (element: unknown) => !(isAgentSessionSection(element) && element.section === AgentSessionSection.Archived),
142+
expandOnlyOnTwistieClick: (element: unknown) => !(isAgentSessionSection(element) && element.section === AgentSessionSection.Archived && this.options.filter.getExcludes().archived),
143143
twistieAdditionalCssClass: () => 'force-no-twistie',
144-
collapseByDefault: (element: unknown) => isAgentSessionSection(element) && element.section === AgentSessionSection.Archived,
144+
collapseByDefault: (element: unknown) => isAgentSessionSection(element) && element.section === AgentSessionSection.Archived && this.options.filter.getExcludes().archived,
145145
renderIndentGuides: RenderIndentGuides.None,
146146
}
147147
)) as WorkbenchCompressibleAsyncDataTree<IAgentSessionsModel, AgentSessionListItem, FuzzyScore>;
@@ -150,10 +150,14 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
150150

151151
const model = this.agentSessionsService.model;
152152

153-
this._register(Event.any(
154-
this.options.filter?.onDidChange ?? Event.None,
155-
model.onDidChangeSessions
156-
)(() => {
153+
this._register(this.options.filter.onDidChange(async () => {
154+
if (this.visible) {
155+
this.updateArchivedSectionCollapseState();
156+
list.updateChildren();
157+
}
158+
}));
159+
160+
this._register(model.onDidChangeSessions(() => {
157161
if (this.visible) {
158162
list.updateChildren();
159163
}
@@ -226,6 +230,27 @@ export class AgentSessionsControl extends Disposable implements IAgentSessionsCo
226230
this.sessionsList?.openFind();
227231
}
228232

233+
private updateArchivedSectionCollapseState(): void {
234+
if (!this.sessionsList) {
235+
return;
236+
}
237+
238+
const model = this.agentSessionsService.model;
239+
for (const child of this.sessionsList.getNode(model).children) {
240+
if (!isAgentSessionSection(child.element) || child.element.section !== AgentSessionSection.Archived) {
241+
continue;
242+
}
243+
244+
const shouldCollapseArchived = this.options.filter.getExcludes().archived;
245+
if (shouldCollapseArchived && !child.collapsed) {
246+
this.sessionsList.collapse(child.element);
247+
} else if (!shouldCollapseArchived && child.collapsed) {
248+
this.sessionsList.expand(child.element);
249+
}
250+
break;
251+
}
252+
}
253+
229254
refresh(): Promise<void> {
230255
return this.agentSessionsService.model.resolve(undefined);
231256
}

src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsFilter.ts

Lines changed: 17 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../../pla
1313
import { IChatSessionsService } from '../../common/chatSessionsService.js';
1414
import { AgentSessionProviders, getAgentSessionProviderName } from './agentSessions.js';
1515
import { AgentSessionStatus, IAgentSession } from './agentSessionsModel.js';
16-
import { IAgentSessionsFilter } from './agentSessionsViewer.js';
16+
import { IAgentSessionsFilter, IAgentSessionsFilterExcludes } from './agentSessionsViewer.js';
1717

1818
export interface IAgentSessionsFilterOptions extends Partial<IAgentSessionsFilter> {
1919

@@ -27,18 +27,10 @@ export interface IAgentSessionsFilterOptions extends Partial<IAgentSessionsFilte
2727
overrideExclude?(session: IAgentSession): boolean | undefined;
2828
}
2929

30-
interface IAgentSessionsViewExcludes {
31-
readonly providers: readonly string[];
32-
readonly states: readonly AgentSessionStatus[];
33-
34-
readonly archived: boolean;
35-
readonly read: boolean;
36-
}
37-
38-
const DEFAULT_EXCLUDES: IAgentSessionsViewExcludes = Object.freeze({
30+
const DEFAULT_EXCLUDES: IAgentSessionsFilterExcludes = Object.freeze({
3931
providers: [] as const,
4032
states: [] as const,
41-
archived: false as const,
33+
archived: true as const /* archived are never excluded but toggle between expanded and collapsed */,
4234
read: false as const,
4335
});
4436

@@ -81,12 +73,12 @@ export class AgentSessionsFilter extends Disposable implements Required<IAgentSe
8173
const excludedTypesRaw = this.storageService.get(this.STORAGE_KEY, StorageScope.PROFILE);
8274
if (excludedTypesRaw) {
8375
try {
84-
this.excludes = JSON.parse(excludedTypesRaw) as IAgentSessionsViewExcludes;
76+
this.excludes = JSON.parse(excludedTypesRaw) as IAgentSessionsFilterExcludes;
8577
} catch {
86-
this.resetExcludes();
78+
this.excludes = { ...DEFAULT_EXCLUDES };
8779
}
8880
} else {
89-
this.resetExcludes();
81+
this.excludes = { ...DEFAULT_EXCLUDES };
9082
}
9183

9284
this.updateFilterActions();
@@ -96,19 +88,14 @@ export class AgentSessionsFilter extends Disposable implements Required<IAgentSe
9688
}
9789
}
9890

99-
private resetExcludes(): void {
100-
this.excludes = {
101-
providers: [...DEFAULT_EXCLUDES.providers],
102-
states: [...DEFAULT_EXCLUDES.states],
103-
archived: DEFAULT_EXCLUDES.archived,
104-
read: DEFAULT_EXCLUDES.read,
105-
};
106-
}
107-
108-
private storeExcludes(excludes: IAgentSessionsViewExcludes): void {
91+
private storeExcludes(excludes: IAgentSessionsFilterExcludes): void {
10992
this.excludes = excludes;
11093

111-
this.storageService.store(this.STORAGE_KEY, JSON.stringify(this.excludes), StorageScope.PROFILE, StorageTarget.USER);
94+
if (equals(this.excludes, DEFAULT_EXCLUDES)) {
95+
this.storageService.remove(this.STORAGE_KEY, StorageScope.PROFILE);
96+
} else {
97+
this.storageService.store(this.STORAGE_KEY, JSON.stringify(this.excludes), StorageScope.PROFILE, StorageTarget.USER);
98+
}
11299
}
113100

114101
private updateFilterActions(): void {
@@ -257,9 +244,7 @@ export class AgentSessionsFilter extends Disposable implements Required<IAgentSe
257244
});
258245
}
259246
run(): void {
260-
that.resetExcludes();
261-
262-
that.storageService.store(that.STORAGE_KEY, JSON.stringify(that.excludes), StorageScope.PROFILE, StorageTarget.USER);
247+
that.storeExcludes({ ...DEFAULT_EXCLUDES });
263248
}
264249
}));
265250
}
@@ -268,16 +253,16 @@ export class AgentSessionsFilter extends Disposable implements Required<IAgentSe
268253
return equals(this.excludes, DEFAULT_EXCLUDES);
269254
}
270255

256+
getExcludes(): IAgentSessionsFilterExcludes {
257+
return this.excludes;
258+
}
259+
271260
exclude(session: IAgentSession): boolean {
272261
const overrideExclude = this.options?.overrideExclude?.(session);
273262
if (typeof overrideExclude === 'boolean') {
274263
return overrideExclude;
275264
}
276265

277-
if (this.excludes.archived && session.isArchived()) {
278-
return true;
279-
}
280-
281266
if (this.excludes.read && (session.isArchived() || session.isRead())) {
282267
return true;
283268
}

src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewer.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,14 @@ export class AgentSessionsAccessibilityProvider implements IListAccessibilityPro
555555
}
556556
}
557557

558+
export interface IAgentSessionsFilterExcludes {
559+
readonly providers: readonly string[];
560+
readonly states: readonly AgentSessionStatus[];
561+
562+
readonly archived: boolean;
563+
readonly read: boolean;
564+
}
565+
558566
export interface IAgentSessionsFilter {
559567

560568
/**
@@ -584,6 +592,11 @@ export interface IAgentSessionsFilter {
584592
* The logic to exclude sessions from the view.
585593
*/
586594
exclude(session: IAgentSession): boolean;
595+
596+
/**
597+
* Get the current filter excludes for display in the UI.
598+
*/
599+
getExcludes(): IAgentSessionsFilterExcludes;
587600
}
588601

589602
export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsModel, AgentSessionListItem> {

src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionViewModel.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ suite('Agent Sessions', () => {
866866
assert.strictEqual(filter.exclude(session3), false);
867867
});
868868

869-
test('should filter out archived sessions', () => {
869+
test('should filter not out archived sessions', () => {
870870
const storageService = instantiationService.get(IStorageService);
871871
const filter = disposables.add(instantiationService.createInstance(
872872
AgentSessionsFilter,
@@ -896,7 +896,7 @@ suite('Agent Sessions', () => {
896896
storageService.store(`agentSessions.filterExcludes.${MenuId.ViewTitle.id.toLowerCase()}`, JSON.stringify(excludes), StorageScope.PROFILE, StorageTarget.USER);
897897

898898
// After excluding archived, only archived session should be filtered
899-
assert.strictEqual(filter.exclude(archivedSession), true);
899+
assert.strictEqual(filter.exclude(archivedSession), false);
900900
assert.strictEqual(filter.exclude(activeSession), false);
901901
});
902902

src/vs/workbench/contrib/chat/test/browser/agentSessions/agentSessionsDataSource.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ suite('AgentSessionsDataSource', () => {
6565
onDidChange: Event.None,
6666
groupResults: () => options.groupResults,
6767
exclude: options.exclude ?? (() => false),
68+
getExcludes: () => ({ providers: [], states: [], archived: false, read: false })
6869
};
6970
}
7071

0 commit comments

Comments
 (0)