Merge pull request #304424 from microsoft/mrleemurray/systematic-teal-swift

Sessions: Refactor and enhance session status widget in titlebar
This commit is contained in:
Lee Murray
2026-03-24 12:02:07 +00:00
committed by GitHub
10 changed files with 187 additions and 271 deletions
+1 -182
View File
@@ -13,8 +13,7 @@ import { localize } from '../../nls.js';
import { ThemeIcon } from '../../base/common/themables.js';
import { Codicon } from '../../base/common/codicons.js';
import { IAgentSessionsService } from '../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
import { AgentSessionStatus, getAgentChangesSummary, IAgentSession } from '../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
import { ICommandService } from '../../platform/commands/common/commands.js';
import { getAgentChangesSummary } from '../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
import { IPaneCompositePartService } from '../../workbench/services/panecomposite/browser/panecomposite.js';
import { ViewContainerLocation } from '../../workbench/common/views.js';
import { URI } from '../../base/common/uri.js';
@@ -23,186 +22,6 @@ import { Event } from '../../base/common/event.js';
// Duplicated from vs/sessions/contrib/changes/browser/changesView.ts to avoid a layering import.
const CHANGES_VIEW_CONTAINER_ID = 'workbench.view.agentSessions.changesContainer';
/**
* Collapsed widget shown in the bottom-left corner when the sidebar is hidden.
* Shows session status counts (active, errors, completed) and a new session button.
*/
export class CollapsedSidebarWidget extends Disposable {
private readonly element: HTMLElement;
private readonly indicatorContainer: HTMLElement;
private readonly indicatorDisposables = this._register(new DisposableStore());
private readonly hoverDelegate = this._register(createInstantHoverDelegate());
constructor(
parent: HTMLElement,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
@IHoverService private readonly hoverService: IHoverService,
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
@ICommandService private readonly commandService: ICommandService,
) {
super();
this.element = append(parent, $('.collapsed-panel-widget.collapsed-sidebar-widget'));
// Sidebar toggle button (leftmost)
this._register(this.createSidebarToggleButton());
// New session button (next to panel toggle)
this._register(this.createNewSessionButton());
// Session status indicators (rightmost)
this.indicatorContainer = append(this.element, $('.collapsed-panel-button.collapsed-sidebar-status'));
// Listen for session changes
this._register(this.agentSessionsService.model.onDidChangeSessions(() => this.rebuildIndicators()));
// Initial build
this.rebuildIndicators();
this.hide();
}
private createNewSessionButton(): DisposableStore {
const store = new DisposableStore();
const btn = append(this.element, $('.collapsed-panel-button.collapsed-sidebar-new-session'));
append(btn, $(ThemeIcon.asCSSSelector(Codicon.newSession)));
store.add(this.hoverService.setupManagedHover(this.hoverDelegate, btn, localize('newSession', "New Session")));
store.add(addDisposableListener(btn, EventType.CLICK, () => {
this.commandService.executeCommand('workbench.action.sessions.newChat');
}));
return store;
}
private createSidebarToggleButton(): DisposableStore {
const store = new DisposableStore();
const btn = append(this.element, $('.collapsed-panel-button.collapsed-sidebar-panel-toggle'));
let iconElement: HTMLElement | undefined;
const updateIcon = () => {
const sidebarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART);
const icon = sidebarVisible ? Codicon.layoutSidebarLeft : Codicon.layoutSidebarLeftOff;
iconElement?.remove();
iconElement = append(btn, $(ThemeIcon.asCSSSelector(icon)));
};
updateIcon();
store.add(this.hoverService.setupManagedHover(this.hoverDelegate, btn, localize('toggleSidebar', "Toggle Side Bar")));
store.add(addDisposableListener(btn, EventType.CLICK, () => {
this.commandService.executeCommand('workbench.action.agentToggleSidebarVisibility');
}));
store.add(this.layoutService.onDidChangePartVisibility(e => {
if (e.partId === Parts.SIDEBAR_PART) {
updateIcon();
}
}));
return store;
}
private rebuildIndicators(): void {
this.indicatorDisposables.clear();
this.indicatorContainer.textContent = '';
const sessions = this.agentSessionsService.model.sessions;
const counts = this.countSessionsByStatus(sessions);
const tooltipParts: string[] = [];
// In-progress (matches agentSessionsViewer: sessionInProgress)
if (counts.inProgress > 0) {
this.appendStatusSegment(Codicon.sessionInProgress, `${counts.inProgress}`, 'collapsed-sidebar-indicator-active');
tooltipParts.push(localize('sessionsInProgress', "{0} session(s) in progress", counts.inProgress));
}
// Needs input (matches agentSessionsViewer: circleFilled)
if (counts.needsInput > 0) {
this.appendStatusSegment(Codicon.circleFilled, `${counts.needsInput}`, 'collapsed-sidebar-indicator-input');
tooltipParts.push(localize('sessionsNeedInput', "{0} session(s) need input", counts.needsInput));
}
// Failed (matches agentSessionsViewer: error)
if (counts.failed > 0) {
this.appendStatusSegment(Codicon.error, `${counts.failed}`, 'collapsed-sidebar-indicator-error');
tooltipParts.push(localize('sessionsFailed', "{0} session(s) with errors", counts.failed));
}
// Unread (matches agentSessionsViewer: circleFilled with textLink-foreground)
if (counts.unread > 0) {
this.appendStatusSegment(Codicon.circleFilled, `${counts.unread}`, 'collapsed-sidebar-indicator-unread');
tooltipParts.push(localize('sessionsUnread', "{0} unread session(s)", counts.unread));
}
// If no sessions at all
if (sessions.length === 0) {
this.appendStatusSegment(Codicon.commentDiscussion, '0', 'collapsed-sidebar-indicator-empty');
tooltipParts.push(localize('noSessions', "No sessions"));
}
if (tooltipParts.length > 0) {
this.indicatorDisposables.add(this.hoverService.setupManagedHover(
this.hoverDelegate, this.indicatorContainer, tooltipParts.join('\n')
));
this.indicatorDisposables.add(addDisposableListener(this.indicatorContainer, EventType.CLICK, () => {
this.layoutService.setPartHidden(false, Parts.SIDEBAR_PART);
}));
}
}
private appendStatusSegment(icon: ThemeIcon, count: string, className: string): void {
const segment = append(this.indicatorContainer, $(`span.collapsed-sidebar-segment.${className}`));
append(segment, $(ThemeIcon.asCSSSelector(icon)));
const label = append(segment, $('span.collapsed-sidebar-count'));
label.textContent = count;
}
private countSessionsByStatus(sessions: IAgentSession[]): { inProgress: number; needsInput: number; failed: number; unread: number } {
let inProgress = 0;
let needsInput = 0;
let failed = 0;
let unread = 0;
for (const session of sessions) {
if (session.isArchived()) {
continue;
}
switch (session.status) {
case AgentSessionStatus.InProgress:
inProgress++;
break;
case AgentSessionStatus.NeedsInput:
needsInput++;
break;
case AgentSessionStatus.Failed:
failed++;
break;
case AgentSessionStatus.Completed:
if (!session.isRead()) {
unread++;
}
break;
}
}
return { inProgress, needsInput, failed, unread };
}
show(): void {
this.element.classList.remove('collapsed-panel-hidden');
}
hide(): void {
this.element.classList.add('collapsed-panel-hidden');
}
}
/**
* Widget shown in the titlebar right area showing file change counts
* (files, insertions, deletions) from the active session.
+1 -7
View File
@@ -14,7 +14,7 @@ import { Menus } from './menus.js';
import { ServicesAccessor } from '../../platform/instantiation/common/instantiation.js';
import { KeybindingWeight } from '../../platform/keybinding/common/keybindingsRegistry.js';
import { registerIcon } from '../../platform/theme/common/iconRegistry.js';
import { AuxiliaryBarVisibleContext, IsAuxiliaryWindowContext, IsWindowAlwaysOnTopContext, SideBarVisibleContext } from '../../workbench/common/contextkeys.js';
import { AuxiliaryBarVisibleContext, IsAuxiliaryWindowContext, IsWindowAlwaysOnTopContext } from '../../workbench/common/contextkeys.js';
import { IWorkbenchLayoutService, Parts } from '../../workbench/services/layout/browser/layoutService.js';
import { SessionsWelcomeVisibleContext } from '../common/contextkeys.js';
@@ -41,12 +41,6 @@ class ToggleSidebarVisibilityAction extends Action2 {
primary: KeyMod.CtrlCmd | KeyCode.KeyB
},
menu: [
{
id: Menus.SidebarTitle,
group: 'navigation',
order: 100,
when: ContextKeyExpr.and(IsAuxiliaryWindowContext.toNegated(), SideBarVisibleContext, SessionsWelcomeVisibleContext.toNegated())
},
{
id: Menus.TitleBarContext,
group: 'navigation',
@@ -21,13 +21,19 @@
display: none;
}
/* ---- Sidebar widget (in titlebar-left) ---- */
/* ---- Sidebar widget (in titlebar-left, always visible, far left) ---- */
.agent-sessions-workbench .collapsed-sidebar-widget {
order: 10;
order: 0;
padding-left: 8px;
}
/* When inside the sidebar title area (panel open), no left padding */
.agent-sessions-workbench .part.sidebar .collapsed-sidebar-widget {
padding-left: 0;
}
/* ---- Auxiliary Bar widget (in titlebar-right) ---- */
.agent-sessions-workbench .collapsed-auxbar-widget {
@@ -74,23 +80,13 @@
color: inherit;
}
/* ---- Consolidated session status button ---- */
/* ---- Session status button (tasklist icon + unread badge) ---- */
.agent-sessions-workbench .collapsed-panel-button.collapsed-sidebar-status {
gap: 6px;
gap: 4px;
}
.agent-sessions-workbench .collapsed-sidebar-segment {
display: inline-flex;
align-items: center;
gap: 2px;
}
.agent-sessions-workbench .collapsed-panel-button .collapsed-sidebar-segment .codicon {
font-size: 14px;
}
/* ---- Sidebar indicators ---- */
/* ---- Shared count styling ---- */
.agent-sessions-workbench .collapsed-sidebar-count,
.agent-sessions-workbench .collapsed-auxbar-count {
@@ -100,51 +96,6 @@
color: inherit;
}
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-active .codicon {
color: var(--vscode-textLink-foreground);
}
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-error {
color: var(--vscode-errorForeground);
}
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-input {
color: var(--vscode-list-warningForeground);
}
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-input .codicon {
animation: collapsed-sidebar-needs-input-pulse 2s ease-in-out infinite;
}
@keyframes collapsed-sidebar-needs-input-pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
@media (prefers-reduced-motion: reduce) {
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-input .codicon {
animation: none;
}
}
.agent-sessions-workbench .collapsed-sidebar-segment.collapsed-sidebar-indicator-unread {
color: var(--vscode-textLink-foreground);
}
/* ---- Panel toggle button ---- */
.agent-sessions-workbench .collapsed-sidebar-panel-toggle {
opacity: 0.7;
}
.agent-sessions-workbench .collapsed-sidebar-panel-toggle:hover {
opacity: 1;
}
/* ---- Auxiliary bar indicators ---- */
.agent-sessions-workbench .collapsed-auxbar-insertions {
+1 -2
View File
@@ -15,7 +15,6 @@
*
* Margin values (must match the constants in the Part classes):
* Sidebar: no card (flush, spans full height)
* Chat bar: top=16, bottom=2, left=16, right=16
* Auxiliary bar: top=16, bottom=18, right=16
* Panel: bottom=18, left=16, right=16
*/
@@ -88,7 +87,7 @@
margin: 0 auto !important;
display: inherit !important;
/* Align with panel (terminal) card margin */
padding: 4px 16px !important;
padding: 4px 16px 16px 16px !important;
box-sizing: border-box;
}
@@ -24,7 +24,8 @@
}
/* Interactive elements in the title area must not be draggable */
.agent-sessions-workbench .part.sidebar > .composite.title .action-item {
.agent-sessions-workbench .part.sidebar > .composite.title .action-item,
.agent-sessions-workbench .part.sidebar > .composite.title > .session-status-toggle {
-webkit-app-region: no-drag;
}
@@ -56,3 +57,38 @@
max-width: 100%;
cursor: default;
}
/* Session status toggle — standalone button in sidebar title area */
.agent-sessions-workbench .part.sidebar .session-status-toggle {
display: flex;
align-items: center;
align-self: center;
gap: 3px;
height: 22px;
padding: 0 4px;
margin-right: 4px;
border: none;
border-radius: 4px;
cursor: pointer;
color: inherit;
font: inherit;
background: var(--vscode-toolbar-activeBackground);
outline: none;
position: relative;
z-index: 1;
}
.agent-sessions-workbench .part.sidebar .session-status-toggle:hover {
background-color: var(--vscode-toolbar-hoverBackground);
}
.agent-sessions-workbench .part.sidebar .session-status-toggle .codicon {
font-size: 16px;
}
.agent-sessions-workbench .part.sidebar .session-status-toggle-badge {
font-size: 12px;
font-variant-numeric: tabular-nums;
line-height: 16px;
color: inherit;
}
+55 -1
View File
@@ -33,13 +33,19 @@ import { Separator } from '../../../base/common/actions.js';
import { IHoverService } from '../../../platform/hover/browser/hover.js';
import { Extensions } from '../../../workbench/browser/panecomposite.js';
import { Menus } from '../menus.js';
import { $, append, getWindowId, prepend } from '../../../base/browser/dom.js';
import { $, addDisposableListener, append, EventType, getWindowId, prepend } from '../../../base/browser/dom.js';
import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js';
import { isMacintosh, isNative } from '../../../base/common/platform.js';
import { isFullscreen, onDidChangeFullscreen } from '../../../base/browser/browser.js';
import { mainWindow } from '../../../base/browser/window.js';
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
import { hasNativeTitlebar, getTitleBarStyle } from '../../../platform/window/common/window.js';
import { ThemeIcon } from '../../../base/common/themables.js';
import { Codicon } from '../../../base/common/codicons.js';
import { DisposableStore } from '../../../base/common/lifecycle.js';
import { IAgentSessionsService } from '../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js';
import { countUnreadSessions } from '../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
import { localize } from '../../../nls.js';
/**
* Sidebar part specifically for agent sessions workbench.
@@ -151,6 +157,11 @@ export class SidebarPart extends AbstractPaneCompositePart {
prepend(titleArea, $('div.titlebar-drag-region'));
}
// Session toggle widget (right side of title area)
if (titleArea) {
this.createSessionsToggle(titleArea);
}
// macOS native: the sidebar spans full height and the traffic lights
// overlay the top-left corner. Add a fixed-width spacer inside the
// title area to push content horizontally past the traffic lights.
@@ -177,6 +188,49 @@ export class SidebarPart extends AbstractPaneCompositePart {
return titleArea;
}
/**
* Creates a standalone session toggle widget appended to the sidebar title area.
* Displays a tasklist icon with an optional unread badge. Clicking hides the sidebar.
*/
private createSessionsToggle(titleArea: HTMLElement): void {
const widgetDisposables = this._register(new DisposableStore());
const widget = append(titleArea, $('button.session-status-toggle')) as HTMLButtonElement;
widget.type = 'button';
widget.tabIndex = 0;
widget.setAttribute('aria-label', localize('hideSidebar', "Hide Side Bar"));
append(widget, $(ThemeIcon.asCSSSelector(Codicon.tasklist)));
const badge = append(widget, $('span.session-status-toggle-badge'));
// Toggle sidebar on click
widgetDisposables.add(addDisposableListener(widget, EventType.CLICK, (e) => {
e.preventDefault();
e.stopPropagation();
this.layoutService.setPartHidden(true, Parts.SIDEBAR_PART);
}));
// Update badge on session changes (deferred to avoid service unavailability)
const updateBadge = (svc: IAgentSessionsService) => {
const unread = countUnreadSessions(svc.model.sessions);
badge.textContent = unread > 0 ? `${unread}` : '';
badge.style.display = unread > 0 ? '' : 'none';
widget.setAttribute('aria-label', unread > 0
? localize('hideSidebarUnread', "Hide Side Bar, {0} unread session(s)", unread)
: localize('hideSidebar', "Hide Side Bar"));
};
setTimeout(() => {
try {
const svc = this.instantiationService.invokeFunction(accessor => accessor.get(IAgentSessionsService));
updateBadge(svc);
widgetDisposables.add(svc.model.onDidChangeSessions(() => updateBadge(svc)));
} catch {
// Service not yet available
badge.style.display = 'none';
}
}, 0);
}
private createFooter(parent: HTMLElement): void {
const footer = append(parent, $('.sidebar-footer.sidebar-action-list'));
this.footerContainer = footer;
+2 -16
View File
@@ -5,7 +5,7 @@
import '../../workbench/browser/style.js';
import './media/style.css';
import { CollapsedSidebarWidget, CollapsedAuxiliaryBarWidget } from './collapsedPartWidgets.js';
import { CollapsedAuxiliaryBarWidget } from './collapsedPartWidgets.js';
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../base/common/lifecycle.js';
import { Emitter, Event, setGlobalLeakWarningThreshold } from '../../base/common/event.js';
import { getActiveDocument, getActiveElement, getClientArea, getWindowId, getWindows, IDimension, isAncestorUsingFlowTo, size, Dimension, runWhenWindowIdle } from '../../base/browser/dom.js';
@@ -244,7 +244,6 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
private chatBarPartView!: ISerializableView;
private collapsedSidebarWidget: CollapsedSidebarWidget | undefined;
private collapsedAuxiliaryBarWidget: CollapsedAuxiliaryBarWidget | undefined;
private readonly partVisibility: IPartVisibilityState = {
@@ -385,14 +384,8 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
// Layout
this.layout();
// Collapsed Sidebar Widget (shown when sidebar is hidden)
const titlebarPart = this.getPart(Parts.TITLEBAR_PART) as TitlebarPart;
this.collapsedSidebarWidget = this._register(instantiationService.createInstance(CollapsedSidebarWidget, titlebarPart.leftContainer));
if (!this.partVisibility.sidebar) {
this.collapsedSidebarWidget.show();
}
// Auxiliary bar changes widget (always visible, acts as a toggle)
const titlebarPart = this.getPart(Parts.TITLEBAR_PART) as TitlebarPart;
this.collapsedAuxiliaryBarWidget = this._register(instantiationService.createInstance(CollapsedAuxiliaryBarWidget, titlebarPart.rightContainer, titlebarPart.rightWindowControlsContainer));
this.collapsedAuxiliaryBarWidget.updateActiveState(this.partVisibility.auxiliaryBar);
@@ -1105,13 +1098,6 @@ export class Workbench extends Disposable implements IWorkbenchLayoutService {
!hidden,
);
// Toggle collapsed sidebar widget
if (hidden) {
this.collapsedSidebarWidget?.show();
} else {
this.collapsedSidebarWidget?.hide();
}
// If sidebar becomes hidden, also hide the current active pane composite
if (hidden && this.paneCompositeService.getActivePaneComposite(ViewContainerLocation.Sidebar)) {
this.paneCompositeService.hideActivePaneComposite(ViewContainerLocation.Sidebar);
@@ -77,4 +77,33 @@
flex-shrink: 0;
}
/* Session count widget (tasklist icon + unread count, left of pill) */
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-count {
display: flex;
align-items: center;
gap: 3px;
flex-shrink: 0;
cursor: pointer;
padding: 0 4px;
height: 22px;
border: none;
border-radius: 4px;
background: transparent;
color: inherit;
font: inherit;
outline: none;
}
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-count:hover {
background-color: var(--vscode-toolbar-hoverBackground);
}
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-count .codicon {
font-size: 16px;
}
.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-count-label {
font-size: 12px;
font-variant-numeric: tabular-nums;
line-height: 16px;
}
@@ -19,8 +19,9 @@ import { IMenuService, MenuId, MenuRegistry, SubmenuItemAction } from '../../../
import { IContextKeyService, ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actions/chatContextKeys.js';
import { IMarshalledAgentSessionContext } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
import { IMarshalledAgentSessionContext, countUnreadSessions } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js';
import { IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js';
import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/layout/browser/layoutService.js';
import { Menus } from '../../../browser/menus.js';
import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js';
import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js';
@@ -76,6 +77,7 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
@IMenuService private readonly menuService: IMenuService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
) {
super(undefined, action, options);
@@ -128,8 +130,9 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
const label = this._getActiveSessionLabel();
const icon = this._getActiveSessionIcon();
const repoLabel = this._getRepositoryLabel();
const unreadCount = this._countUnreadSessions();
// Build a render-state key from all displayed data
const renderState = `${icon?.id ?? ''}|${label}|${repoLabel ?? ''}`;
const renderState = `${icon?.id ?? ''}|${label}|${repoLabel ?? ''}|${unreadCount}`;
// Skip re-render if state hasn't changed
if (this._lastRenderState === renderState) {
@@ -194,6 +197,37 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
this._container.appendChild(sessionPill);
// Session count widget (to the left of the pill) — toggles sidebar
const countWidget = $('button.agent-sessions-titlebar-count') as HTMLButtonElement;
countWidget.type = 'button';
countWidget.tabIndex = 0;
const countIcon = $(ThemeIcon.asCSSSelector(Codicon.tasklist));
countWidget.appendChild(countIcon);
if (unreadCount > 0) {
const countLabel = $('span.agent-sessions-titlebar-count-label');
countLabel.textContent = `${unreadCount}`;
countWidget.appendChild(countLabel);
countWidget.setAttribute('aria-label', localize('showSidebarUnread', "Show Side Bar, {0} unread session(s)", unreadCount));
} else {
countWidget.setAttribute('aria-label', localize('showSidebar', "Show Side Bar"));
}
// Hide when sidebar is visible (only shown when sidebar is hidden)
const updateVisibility = () => {
countWidget.style.display = this.layoutService.isVisible(Parts.SIDEBAR_PART) ? 'none' : '';
};
updateVisibility();
this._dynamicDisposables.add(this.layoutService.onDidChangePartVisibility(e => {
if (e.partId === Parts.SIDEBAR_PART) {
updateVisibility();
}
}));
this._dynamicDisposables.add(addDisposableListener(countWidget, EventType.CLICK, (e) => {
e.preventDefault();
e.stopPropagation();
this.layoutService.setPartHidden(false, Parts.SIDEBAR_PART);
}));
this._container.insertBefore(countWidget, sessionPill);
// Hover
this._dynamicDisposables.add(this.hoverService.setupManagedHover(
getDefaultHoverDelegate('mouse'),
@@ -305,6 +339,10 @@ export class SessionsTitleBarWidget extends BaseActionViewItem {
return basename(uri);
}
private _countUnreadSessions(): number {
return countUnreadSessions(this.agentSessionsService.model.sessions);
}
private _showContextMenu(e: MouseEvent): void {
const activeSession = this.activeSessionService.getActiveSession();
if (!activeSession) {
@@ -158,6 +158,16 @@ export function isAgentSessionsModel(obj: unknown): obj is IAgentSessionsModel {
return Array.isArray(sessionsModel?.sessions) && typeof sessionsModel?.getSession === 'function';
}
export function countUnreadSessions(sessions: IAgentSession[]): number {
let unread = 0;
for (const session of sessions) {
if (!session.isArchived() && session.status === AgentSessionStatus.Completed && !session.isRead()) {
unread++;
}
}
return unread;
}
interface IAgentSessionState {
readonly archived?: boolean;
readonly pinned?: boolean;