From f29e9dc0861d795cbf05d512b3c01e8092c629f4 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Fri, 21 Aug 2026 12:29:15 +0200 Subject: [PATCH 01/12] sessions: Remove session metadata setting (#331928) sessions: remove session metadata setting Make the new session tabs and input metadata presentation unconditional, and remove the retired header/tab fallback paths and fixtures.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../browser/parts/chatCompositeBar.ts | 36 --- .../browser/parts/media/chatCompositeBar.css | 34 +-- .../sessions/browser/parts/sessionHeader.ts | 78 +---- src/vs/sessions/browser/parts/sessionView.ts | 11 +- src/vs/sessions/common/sessionConfig.ts | 2 - .../contrib/changes/browser/changesActions.ts | 20 +- .../contrib/chat/browser/chat.contribution.ts | 8 - .../chat/browser/sessionChatInputToolbar.ts | 38 +-- .../chat/browser/sessionMetadataPills.ts | 5 - .../browser/sessionsChatAccessibilityHelp.ts | 6 +- .../browser/sessionChatInputToolbar.test.ts | 16 +- .../files/browser/workspaceFolderActions.ts | 29 +- .../contrib/github/browser/issueActions.ts | 20 +- .../github/browser/pullRequestActions.ts | 14 +- .../test/browser/chatCompositeBar.test.ts | 22 +- .../test/browser/sessionHeader.test.ts | 54 +--- .../sessions/openIssue.fixture.ts | 5 +- .../sessions/openPullRequest.fixture.ts | 5 +- .../sessionChatInputToolbar.fixture.ts | 9 +- .../sessions/sessionHeader.fixture.ts | 280 ++---------------- .../sessions/viewAllChanges.fixture.ts | 5 +- 21 files changed, 83 insertions(+), 614 deletions(-) diff --git a/src/vs/sessions/browser/parts/chatCompositeBar.ts b/src/vs/sessions/browser/parts/chatCompositeBar.ts index 5784b43920bacc..8b0b738a0dbdfc 100644 --- a/src/vs/sessions/browser/parts/chatCompositeBar.ts +++ b/src/vs/sessions/browser/parts/chatCompositeBar.ts @@ -39,14 +39,7 @@ import { ISessionsProvidersService } from '../../services/sessions/browser/sessi import { isAgentHostProvider } from '../../common/agentHostSessionsProvider.js'; import { ICommandService } from '../../../platform/commands/common/commands.js'; import { CLOSE_CHAT_COMMAND_ID } from '../../common/sessionCommands.js'; -import { MenuItemAction } from '../../../platform/actions/common/actions.js'; -import { ChatPillActionViewItem } from '../../../workbench/browser/chatPills.js'; -import { SessionActivatingActionRunner } from '../sessionActionRunner.js'; -import { ISessionsService } from '../../services/sessions/browser/sessionsService.js'; import { getSessionConversationStatusAriaLabel } from '../sessionConversationGroups.js'; -import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; -import { observableConfigValue } from '../../../platform/observable/common/platformObservableUtils.js'; -import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js'; interface IChatTab { readonly chat: IChat; @@ -115,9 +108,6 @@ export class ChatCompositeBar extends Disposable { private readonly _newChatContainer: HTMLElement; private readonly _sessionActionsContainer: HTMLElement; private readonly _sessionToolbar: MenuWorkbenchToolBar; - private readonly _metaRow: HTMLElement; - private readonly _metaToolbar: MenuWorkbenchToolBar; - private readonly _showMetadataInChatInput: IObservable; private readonly _tabs: IChatTab[] = []; private readonly _tabDisposables = this._register(new DisposableStore()); @@ -126,7 +116,6 @@ export class ChatCompositeBar extends Disposable { private _editingTab: IChatTab | undefined; private _delegate: IChatCompositeBarDelegate | undefined; private _showSessionActions = false; - private _metadataInInput = false; private readonly _onDidChangeVisibility = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; @@ -157,12 +146,9 @@ export class ChatCompositeBar extends Disposable { @IInstantiationService private readonly _instantiationService: IInstantiationService, @ISessionsProvidersService private readonly _sessionsProvidersService: ISessionsProvidersService, @ICommandService private readonly _commandService: ICommandService, - @ISessionsService sessionsService: ISessionsService, - @IConfigurationService configurationService: IConfigurationService, ) { super(); - this._showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, configurationService); this._container = $('.chat-composite-bar.session-chat-tabs-bar'); // Tabs row — only shown when the group has multiple chats or is split out. @@ -202,21 +188,6 @@ export class ChatCompositeBar extends Disposable { highlightToggledItems: true, })); - this._metaRow = $('.chat-composite-bar-meta-row'); - this._container.appendChild(this._metaRow); - const metaToolbarContainer = $('.chat-composite-bar-meta-toolbar'); - this._metaRow.appendChild(metaToolbarContainer); - const metaActionRunner = this._register(new SessionActivatingActionRunner(() => this._delegate?.session, sessionsService)); - this._metaToolbar = this._register(this._instantiationService.createInstance(MenuWorkbenchToolBar, metaToolbarContainer, Menus.SessionHeaderMeta, { - hiddenItemStrategy: HiddenItemStrategy.Ignore, - menuOptions: { shouldForwardArgs: true }, - actionRunner: metaActionRunner, - actionViewItemProvider: (action, options) => action instanceof MenuItemAction - ? this._instantiationService.createInstance(ChatPillActionViewItem, undefined, action, options) - : undefined, - })); - this._register(this._metaToolbar.onDidChangeMenuItems(() => this._updateMetaRowVisibility())); - const preventMiddleButtonDefault = (e: MouseEvent) => { if (e.button === 1 && !this._isInTabInput(e)) { e.preventDefault(); @@ -269,7 +240,6 @@ export class ChatCompositeBar extends Disposable { this._delegate = delegate; this._sessionToolbar.context = delegate?.session; - this._metaToolbar.context = delegate?.session; const store = new DisposableStore(); this._groupDisposables.value = store; @@ -292,18 +262,12 @@ export class ChatCompositeBar extends Disposable { this._newChatContainer.classList.toggle('hidden', !supportsMultipleChats || isQuickChat); this._newChatAction.enabled = supportsMultipleChats && !isQuickChat && !delegate.session.isArchived.read(reader); this._showSessionActions = delegate.showSessionActions.read(reader); - this._metadataInInput = this._showMetadataInChatInput.read(reader); this._sessionActionsContainer.classList.toggle('hidden', !this._showSessionActions); - this._updateMetaRowVisibility(); this._setVisible(delegate.visible.read(reader)); })); } - private _updateMetaRowVisibility(): void { - this._metaRow.style.display = this._showSessionActions && !this._metadataInInput && !this._metaToolbar.isEmpty() ? '' : 'none'; - } - setAriaLabel(label: string): void { this._tabsContainer.setAttribute('aria-label', label); } diff --git a/src/vs/sessions/browser/parts/media/chatCompositeBar.css b/src/vs/sessions/browser/parts/media/chatCompositeBar.css index f82c736eb8f98e..b3ba42b1693bf8 100644 --- a/src/vs/sessions/browser/parts/media/chatCompositeBar.css +++ b/src/vs/sessions/browser/parts/media/chatCompositeBar.css @@ -12,7 +12,7 @@ overflow: hidden; } -/* Header host: title row + meta row. */ +/* Header host. */ .chat-composite-bar.session-header-bar { padding: 0 var(--vscode-spacing-size100); box-sizing: border-box; @@ -29,8 +29,7 @@ --chat-tab-max-width: min(200px, 40cqi); } -/* Header: a status icon column next to a main column (title row + meta row). - Mirrors the sessions list so the meta row aligns under the title. */ +/* Header: a status icon column next to the title row. */ .chat-composite-bar-header { display: flex; flex-direction: row; @@ -39,7 +38,7 @@ border-bottom: var(--vscode-strokeThickness) solid color-mix(in srgb, var(--session-view-foreground, var(--chat-tab-active-foreground)) 12%, transparent); } -/* Main column stacks the title row and the meta row */ +/* Main column hosts the title row. */ .chat-composite-bar-header-main { display: flex; flex-direction: column; @@ -146,25 +145,10 @@ margin-left: auto; } -/* Meta row: workspace + contributed changes / pull request buttons. - No `overflow: hidden` here — it would clip the meta buttons' focus ring at the - row's 22px height. The workspace label self-truncates via its own containers. */ -.chat-composite-bar-meta-row { - display: flex; - align-items: center; - gap: 6px; - height: 22px; - font-size: var(--vscode-agents-fontSize-label1, 12px); - font-weight: var(--vscode-agents-fontWeight-regular, 400); - line-height: 18px; - color: var(--chat-tab-inactive-foreground, var(--session-view-foreground)); - white-space: nowrap; -} - -/* Session header meta toolbar */ -.chat-composite-bar-meta-toolbar, -.chat-composite-bar-meta-toolbar .monaco-action-bar, -.chat-composite-bar-meta-toolbar .actions-container { +/* Metadata action fixture host. */ +.session-metadata-pill-toolbar, +.session-metadata-pill-toolbar .monaco-action-bar, +.session-metadata-pill-toolbar .actions-container { display: inline-flex; align-items: center; height: 100%; @@ -172,8 +156,8 @@ max-width: 100%; } -/* Spacing between the contributed meta buttons (e.g. changes · pull request). */ -.chat-composite-bar-meta-toolbar .actions-container { +/* Spacing between contributed metadata buttons. */ +.session-metadata-pill-toolbar .actions-container { gap: 6px; } diff --git a/src/vs/sessions/browser/parts/sessionHeader.ts b/src/vs/sessions/browser/parts/sessionHeader.ts index fcde5fa5b2272e..e47f5fd7a91cec 100644 --- a/src/vs/sessions/browser/parts/sessionHeader.ts +++ b/src/vs/sessions/browser/parts/sessionHeader.ts @@ -10,15 +10,13 @@ import { $, addDisposableGenericMouseDownListener, addDisposableListener, addSta import { StandardMouseEvent } from '../../../base/browser/mouseEvent.js'; import { IKeyboardEvent } from '../../../base/browser/keyboardEvent.js'; import { KeyCode } from '../../../base/common/keyCodes.js'; -import { autorun, IObservable, IReader, observableSignalFromEvent } from '../../../base/common/observable.js'; +import { autorun, IReader } from '../../../base/common/observable.js'; import { IThemeService } from '../../../platform/theme/common/themeService.js'; import { localize } from '../../../nls.js'; import { IActiveSession, ISessionsManagementService } from '../../services/sessions/common/sessionsManagement.js'; -import { ISessionsService } from '../../services/sessions/browser/sessionsService.js'; import { getUntitledSessionTitle } from '../../services/sessions/common/session.js'; import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../platform/actions/browser/toolbar.js'; -import { MenuItemAction } from '../../../platform/actions/common/actions.js'; import { IContextMenuService } from '../../../platform/contextview/browser/contextView.js'; import { Menus } from '../menus.js'; import { LocalSelectionTransfer } from '../../../platform/dnd/browser/dnd.js'; @@ -28,16 +26,10 @@ import { applySessionBarThemeColors } from './sessionBarStyles.js'; import { IContextKeyService } from '../../../platform/contextkey/common/contextkey.js'; import { onUnexpectedError } from '../../../base/common/errors.js'; import { SessionStatusIcon } from '../sessionStatusIcon.js'; -import { ChatPillActionViewItem } from '../../../workbench/browser/chatPills.js'; -import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; -import { observableConfigValue } from '../../../platform/observable/common/platformObservableUtils.js'; -import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js'; -import { SessionActivatingActionRunner } from '../sessionActionRunner.js'; /** * The session header shown at the top of a session view. It surfaces the session - * identity, optional workspace metadata, contributed metadata pills, and the - * session toolbars. + * identity and session toolbar. * * It is intentionally decoupled from the {@link ChatCompositeBar} (the chat tab * strip) so the two surfaces evolve independently. The hosting view tells the @@ -49,9 +41,7 @@ export class SessionHeader extends Disposable { private readonly _iconEl: HTMLElement; private readonly _titleEl: HTMLElement; private readonly _titleTextEl: HTMLElement; - private readonly _metaRow: HTMLElement; private readonly _toolbar: MenuWorkbenchToolBar; - private readonly _metaToolbar: MenuWorkbenchToolBar; private readonly _titleActionsEl: HTMLElement; private readonly _sessionDisposables = this._register(new MutableDisposable()); @@ -75,9 +65,6 @@ export class SessionHeader extends Disposable { private readonly _sessionTransfer = LocalSelectionTransfer.getInstance(); - private readonly _metaActionsSignal: IObservable; - private readonly _showMetadataInChatInput: IObservable; - private readonly _statusIcon: SessionStatusIcon; get element(): HTMLElement { @@ -98,18 +85,12 @@ export class SessionHeader extends Disposable { @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @ISessionsManagementService private readonly _sessionsManagementService: ISessionsManagementService, - @ISessionsService private readonly _sessionsService: ISessionsService, - @IConfigurationService configurationService: IConfigurationService, ) { super(); - this._showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, configurationService); this._container = $('.chat-composite-bar.session-header-bar'); - // Header: a status icon column alongside a main column that stacks the title - // row (title + actions) and the meta row (workspace · diff). This mirrors the - // sessions list so the meta row aligns under the title rather than under the - // status icon. + // Header: a status icon column alongside the title and actions. const header = $('.chat-composite-bar-header'); this._container.appendChild(header); @@ -150,41 +131,7 @@ export class SessionHeader extends Disposable { highlightToggledItems: true, })); - this._metaRow = $('.chat-composite-bar-meta-row'); - main.appendChild(this._metaRow); - - // Session header meta toolbar. Actions are contributed into the generic - // Menus.SessionHeaderMeta menu: the files view contributes the workspace - // folder pill (opens the Files view), the changes view contributes the - // diff-stats action (opens the multi-file diff editor) and the GitHub - // contribution contributes the pull request pill (opens the PR on GitHub), - // each rendered as a compact secondary button pill via - // ChatPillActionViewItem. - const metaToolbarContainer = $('.chat-composite-bar-meta-toolbar'); - this._metaRow.appendChild(metaToolbarContainer); - // Commands contributed into the header meta toolbar (e.g. View All Changes) - // operate on this view's session. Promote it to the active session before - // running any of them via a custom action runner, so the command always - // targets the clicked session even when another session is active. - const metaActionRunner = this._register(new SessionActivatingActionRunner(() => this._session, this._sessionsService)); - this._metaToolbar = this._register(instantiationService.createInstance(MenuWorkbenchToolBar, metaToolbarContainer, Menus.SessionHeaderMeta, { - hiddenItemStrategy: HiddenItemStrategy.Ignore, - menuOptions: { shouldForwardArgs: true }, - actionRunner: metaActionRunner, - // Render every meta action as a consistent `icon title` pill unless it - // registers its own action view item via IActionViewItemService. - actionViewItemProvider: (action, options) => { - if (action instanceof MenuItemAction) { - return instantiationService.createInstance(ChatPillActionViewItem, undefined, action, options); - } - return undefined; - }, - })); - // The meta row separator/visibility tracks whether the meta toolbar has any - // contributed actions, so recompute the header whenever they change. - this._metaActionsSignal = observableSignalFromEvent(this, this._metaToolbar.onDidChangeMenuItems); - - // Report height changes (e.g. meta row content wrapping) so the host can re-layout + // Report height changes so the host can re-layout. const heightObserver = this._register(new DisposableResizeObserver('SessionHeader.height', () => { this._onDidChangeHeight.fire(); })); @@ -235,9 +182,9 @@ export class SessionHeader extends Disposable { return; } - // Don't swallow a click on the toolbar or meta row pills into a session drag. + // Don't swallow a click on the toolbar into a session drag. const target = this._lastPointerDownTarget; - if (target && (this._titleActionsEl.contains(target) || this._metaRow.contains(target))) { + if (target && this._titleActionsEl.contains(target)) { e.preventDefault(); return; } @@ -276,7 +223,6 @@ export class SessionHeader extends Disposable { this._cancelTitleEditing(); this._session = session; this._toolbar.context = session; - this._metaToolbar.context = session; this._statusIcon.reset(); const store = new DisposableStore(); @@ -313,8 +259,8 @@ export class SessionHeader extends Disposable { private _updateHeader(session: IActiveSession, reader: IReader): void { // Session icon — the SessionStatusIcon widget owns the rendering (spinner vs. // codicon, cross-fade, reduced-motion); here we just feed it the latest state. - // The pull request is surfaced in the meta row, so in terminal/default states the - // title shows the read/unread dot indicator (no session type or PR icon). + // Metadata is surfaced above the chat input, so the title keeps the + // read/unread status indicator. const status = session.status.read(reader); const isRead = session.isRead.read(reader); const isArchived = session.isArchived.read(reader); @@ -324,14 +270,6 @@ export class SessionHeader extends Disposable { const isQuickChat = session.isQuickChat?.read(reader) ?? false; this._titleTextEl.textContent = session.title.read(reader) || getUntitledSessionTitle(isQuickChat); this._titleEl.classList.toggle('editable', this._isTitleEditable()); - const showMetadataInChatInput = this._showMetadataInChatInput.read(reader); - - // Meta row: contributed action pills (workspace folder · diff stats · pull request). - // Reading the signal re-runs this on menu changes. - this._metaActionsSignal.read(reader); - const hasMetaActions = !this._metaToolbar.isEmpty(); - - this._metaRow.style.display = !showMetadataInChatInput && hasMetaActions ? '' : 'none'; this._onDidChangeHeight.fire(); } diff --git a/src/vs/sessions/browser/parts/sessionView.ts b/src/vs/sessions/browser/parts/sessionView.ts index 39a4f94ca56980..bb178ce3ab21ad 100644 --- a/src/vs/sessions/browser/parts/sessionView.ts +++ b/src/vs/sessions/browser/parts/sessionView.ts @@ -18,15 +18,12 @@ import { AbstractChatView, IChatViewOptions } from './chatView.js'; import { ChatGroupsView } from './chatGroupsView.js'; import { SessionHeader, SessionViewFloatingToolbar } from './sessionHeader.js'; import { ISessionContext, SessionContext } from '../../services/sessions/browser/sessionContext.js'; -import { autorun, IObservable, observableValue } from '../../../base/common/observable.js'; +import { autorun, observableValue } from '../../../base/common/observable.js'; import { SessionIsMaximizedContext } from '../../common/contextkeys.js'; import { AGENTS_CENTERED_CONTENT_MAX_WIDTH } from '../../common/layoutConstants.js'; import { setActiveSessionContextKeys } from '../../services/sessions/common/sessionContextKeys.js'; import { applySessionViewThemeColors } from './sessionBarStyles.js'; import { IChatViewFactory } from '../../services/chatView/browser/chatViewFactory.js'; -import { IConfigurationService } from '../../../platform/configuration/common/configuration.js'; -import { observableConfigValue } from '../../../platform/observable/common/platformObservableUtils.js'; -import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../common/sessionConfig.js'; /** * Options passed to {@link SessionView.openSession}. Extends the chat view @@ -86,18 +83,15 @@ export class SessionView extends Disposable implements ISerializableView { private _isLeafVisible = true; private readonly _sessionObs = observableValue(this, undefined); - private readonly _showMetadataInChatInput: IObservable; constructor( @IChatViewFactory private readonly _chatViewFactory: IChatViewFactory, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService private readonly themeService: IThemeService, - @IConfigurationService configurationService: IConfigurationService, ) { super(); - this._showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, configurationService); // Scoped context key service so toolbars hosted within can react to // session-specific context keys (e.g. sessionIsCreated, sessionIsSticky). const scopedContextKeyService = this._scopedContextKeyService = this._register(contextKeyService.createScoped(this.element)); @@ -157,8 +151,7 @@ export class SessionView extends Disposable implements ISerializableView { this._register(autorun(reader => { const session = this._sessionObs.read(reader); - const tabsReplaceHeader = this._showMetadataInChatInput.read(reader) - && this._groupsView.groupCount.read(reader) === 1 + const tabsReplaceHeader = this._groupsView.groupCount.read(reader) === 1 && (session?.isCreated.read(reader) ?? false) && (session?.shouldShowChatTabs.read(reader) ?? false); this._header.setVisible(!tabsReplaceHeader); diff --git a/src/vs/sessions/common/sessionConfig.ts b/src/vs/sessions/common/sessionConfig.ts index 09d79fe367365c..deda8edc5edf24 100644 --- a/src/vs/sessions/common/sessionConfig.ts +++ b/src/vs/sessions/common/sessionConfig.ts @@ -13,8 +13,6 @@ import type { ResolveSessionConfigResult } from '../../platform/agentHost/common */ export const DOCK_DETAIL_PANEL_SETTING = 'sessions.layout.singlePaneDetailPanel'; -export const SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING = 'chat.agentSessions.showSessionMetadataInInput'; - export function isSessionConfigComplete(config: ResolveSessionConfigResult): boolean { return (config.schema.required ?? []).every(property => config.values[property] !== undefined); } diff --git a/src/vs/sessions/contrib/changes/browser/changesActions.ts b/src/vs/sessions/contrib/changes/browser/changesActions.ts index c42c3fc1b3bd58..4a6415872ea934 100644 --- a/src/vs/sessions/contrib/changes/browser/changesActions.ts +++ b/src/vs/sessions/contrib/changes/browser/changesActions.ts @@ -49,9 +49,7 @@ class ViewAllChangesAction extends Action2 { title: localize2('agentSessions.changes', 'Changes'), icon: Codicon.diffMultiple, f1: false, - // Diff stats shown in the session header meta row - // (vs/sessions/browser/parts/sessionHeader.ts). Rendered with a - // custom action view item that shows the live +/- counts. + // Metadata pill rendered with live +/- counts. menu: { id: Menus.SessionHeaderMeta, group: 'navigation', @@ -233,10 +231,8 @@ interface IDiffStats { } /** - * Renders the {@link ViewAllChangesAction} menu item contributed into {@link Menus.SessionHeaderMeta} - * (the session header meta row) as a ` files +insertions -deletions` pill. It extends the - * generic {@link ChatPillActionViewItem} (so the icon and label render consistently with other - * meta actions) and appends the session's live aggregate diff stats. Activating the item runs the + * Renders the {@link ViewAllChangesAction} as a ` files +insertions -deletions` + * metadata pill. It appends the session's live aggregate diff stats. Activating the item runs the * action, which opens the multi-file diff editor. * * The stats are read from the {@link ISessionContext} so the correct per-session changes @@ -332,9 +328,7 @@ export class ViewAllChangesActionViewItem extends ChatPillActionViewItem { } /** - * Registers the {@link ViewAllChangesActionViewItem} for the diff-stats action in the - * session header meta toolbar. Registering it here (rather than in the core session header) - * keeps the rendering of the changes-owned action co-located with the action itself. + * Registers the {@link ViewAllChangesActionViewItem} for the diff-stats metadata pill. */ class ViewAllChangesActionViewItemContribution extends Disposable implements IWorkbenchContribution { @@ -345,11 +339,7 @@ class ViewAllChangesActionViewItemContribution extends Disposable implements IWo ) { super(); - // The action view item service only notifies toolbars of a factory via - // the event passed to register(), not on registration itself. A session - // header restored with existing changes may create its meta toolbar - // before this contribution runs, so announce the factory once right - // after registering to make those toolbars re-render and pick it up. + // Announce the factory after registration so existing metadata pills re-render. const onDidRegister = this._register(new Emitter()); this._register(actionViewItemService.register(Menus.SessionHeaderMeta, ViewAllChangesAction.ID, (action, options, instantiationService) => { if (!(action instanceof MenuItemAction)) { diff --git a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts index 548b4b9b36ac50..a5745eedae41a7 100644 --- a/src/vs/sessions/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/sessions/contrib/chat/browser/chat.contribution.ts @@ -9,7 +9,6 @@ import { localize, localize2 } from '../../../../nls.js'; import { Action2, registerAction2 } from '../../../../platform/actions/common/actions.js'; import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; import { ConfigurationScope, Extensions as ConfigurationExtensions, IConfigurationRegistry } from '../../../../platform/configuration/common/configurationRegistry.js'; -import product from '../../../../platform/product/common/product.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; import { ISessionsService } from '../../../services/sessions/browser/sessionsService.js'; import { ISessionsManagementService, inheritableSessionTarget } from '../../../services/sessions/common/sessionsManagement.js'; @@ -49,7 +48,6 @@ import { ISessionsChatViewStateService, SessionsChatViewStateService } from './c import { SessionsChatResponseFileChangesService } from './sessionTurnChanges.js'; import { IChatResponseFileChangesService } from '../../../../workbench/contrib/chat/browser/chatResponseFileChangesService.js'; import { SessionsChatPetAchievementContribution } from './chatPetAchievements.js'; -import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../../common/sessionConfig.js'; class NewChatInSessionsWindowAction extends Action2 { @@ -153,11 +151,5 @@ Registry.as(ConfigurationExtensions.Configuration).regis scope: ConfigurationScope.APPLICATION, description: localize('chat.agentSessions.scopedInputHistory', "Controls whether chat input history in the Agents Window is scoped to the current session. Disable this to use shared input history across sessions."), }, - [SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING]: { - type: 'boolean', - default: product.quality !== 'stable', - scope: ConfigurationScope.APPLICATION, - description: localize('chat.agentSessions.showSessionMetadataInInput', "Controls whether session metadata such as changes, pull requests, and issues appears above the chat input instead of in the session header."), - }, }, }); diff --git a/src/vs/sessions/contrib/chat/browser/sessionChatInputToolbar.ts b/src/vs/sessions/contrib/chat/browser/sessionChatInputToolbar.ts index e15b8d272fe245..23f98e8fb7c3bd 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionChatInputToolbar.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionChatInputToolbar.ts @@ -24,18 +24,15 @@ import { localize } from '../../../../nls.js'; import { getChatPillEntries, ChatPillsWidget, IChatPill, IChatPillsModel, type IChatPillSection } from '../../../../workbench/browser/chatPills.js'; import { createChatSectionPill, type IChatDropdownPillOptions } from '../../../../workbench/browser/chatDropdownPill.js'; import { DEFAULT_LABELS_CONTAINER, ResourceLabels } from '../../../../workbench/browser/labels.js'; -import { isAgentHostProviderId } from '../../../common/agentHostSessionsProvider.js'; import { VIEW_SESSION_CHANGES_COMMAND_ID } from '../../changes/common/changes.js'; import { OPEN_ISSUE_ACTION_ID, OPEN_PULL_REQUEST_ACTION_ID } from '../../github/common/types.js'; import { getSessionChatPillMenu, SessionChatPillKind, SessionChatPillVisibility, type ISessionChatPillMenuEntry } from '../common/sessionChatPills.js'; -import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../../common/sessionConfig.js'; import { ISessionsService } from '../../../services/sessions/browser/sessionsService.js'; -import { IChat, isActiveSessionStatus } from '../../../services/sessions/common/session.js'; +import { IChat } from '../../../services/sessions/common/session.js'; import { IActiveSession } from '../../../services/sessions/common/sessionsManagement.js'; import { SessionBackgroundActivitiesControl, sessionSubagentsPillOptions } from './sessionBackgroundActivitiesControl.js'; import { SessionBrowsersControl, sessionBrowsersPillOptions } from './sessionBrowsersControl.js'; import type { ISessionChatPillsDebugData } from './sessionChatInputToolbarDebug.js'; -import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js'; import { SessionMetadataPills } from './sessionMetadataPills.js'; import { SessionActivatingActionRunner } from '../../../browser/sessionActionRunner.js'; import './media/sessionChatInputToolbar.css'; @@ -53,11 +50,6 @@ function computeTurnStats(chat: IChat, reader: IReader): IDiffStats { } return { files, insertions, deletions }; } -/** Whether last-turn pills should remain available for the current session state. */ -export function shouldShowSessionTurnPills(hasDebugData: boolean, turnActive: boolean, showSessionMetadataInInput: boolean, turnStatusPillsEnabled: boolean): boolean { - return hasDebugData || turnStatusPillsEnabled && (turnActive || showSessionMetadataInInput); -} - /** Fake artifacts for the pill debug overlay. */ function buildDebugArtifactSections(debugData: ISessionChatPillsDebugData): readonly IChatPillSection[] { const entries = debugData.markdownFiles.map(name => { @@ -139,16 +131,6 @@ export class SessionChatInputToolbar extends Disposable { /** Customization sections shown in the customizations pill. */ private readonly _customizationSections: IObservable; - /** Whether pills may show at all: an agent host session with an active turn. */ - private readonly _active = derived(reader => { - const session = this._session.read(reader); - const chat = this._chat.read(reader); - if (!session || !chat || !isAgentHostProviderId(session.providerId)) { - return false; - } - return isActiveSessionStatus(chat.status.read(reader)); - }); - constructor( @IConfigurationService private readonly _configurationService: IConfigurationService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @@ -186,33 +168,23 @@ export class SessionChatInputToolbar extends Disposable { this._customizationSections = sessionCustomizations.sections; const turnStatusPillsEnabled = observeTurnStatusPillsEnabled(this._configurationService); - const showMetadataInChatInput = observableConfigValue(SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING, false, this._configurationService); - const showTurnPills = derived(reader => shouldShowSessionTurnPills( - this._debugData.read(reader) !== undefined, - this._active.read(reader), - showMetadataInChatInput.read(reader), - turnStatusPillsEnabled.read(reader), - )); + const pillsEnabled = derived(reader => this._debugData.read(reader) !== undefined || turnStatusPillsEnabled.read(reader)); const model: IChatTurnPillsModel = { stats: this._diffStats, artifacts: this._artifactSections, - changesEnabled: showTurnPills, - // Artifacts outlive the turn that produced them, so they only need the pills enabled. - artifactsEnabled: derived(reader => this._debugData.read(reader) !== undefined || turnStatusPillsEnabled.read(reader)), + changesEnabled: pillsEnabled, + artifactsEnabled: pillsEnabled, openChanges: () => this._debugData.get() ? undefined : this._openChanges(), }; const turnPills = this._register(instantiationService.createInstance(ChatTurnPillsProvider, model)); - const metadataPills = this._register(instantiationService.createInstance(SessionMetadataPills, this.element, this._session, showMetadataInChatInput)); + const metadataPills = this._register(instantiationService.createInstance(SessionMetadataPills, this.element, this._session)); const visibility = this._register(instantiationService.createInstance(SessionChatPillVisibility)); // Every pill the session currently has data for, before the user's // per-kind visibility choices are applied. const candidatePills = derived(reader => { const turn = turnPills.pills.read(reader); - if (!showMetadataInChatInput.read(reader)) { - return turn; - } return [ ...metadataPills.pills.read(reader), ...turn.filter(pill => pill.action.id !== CHAT_TURN_CHANGES_PILL_ID), diff --git a/src/vs/sessions/contrib/chat/browser/sessionMetadataPills.ts b/src/vs/sessions/contrib/chat/browser/sessionMetadataPills.ts index ed3b9428d53fad..4c160b05c8430a 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionMetadataPills.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionMetadataPills.ts @@ -29,7 +29,6 @@ export class SessionMetadataPills extends Disposable { constructor( container: HTMLElement, session: IObservable, - enabled: IObservable, @IActionViewItemService private readonly _actionViewItemService: IActionViewItemService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService instantiationService: IInstantiationService, @@ -54,10 +53,6 @@ export class SessionMetadataPills extends Disposable { )); this.pills = derived(this, reader => { menuSignal.read(reader); - if (!enabled.read(reader)) { - return []; - } - return menu.getActions({ shouldForwardArgs: true }).flatMap(([group, actions]) => { if (group !== 'navigation') { return []; diff --git a/src/vs/sessions/contrib/chat/browser/sessionsChatAccessibilityHelp.ts b/src/vs/sessions/contrib/chat/browser/sessionsChatAccessibilityHelp.ts index 2094641cd9e7fd..5f45f7fd63f9fc 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionsChatAccessibilityHelp.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionsChatAccessibilityHelp.ts @@ -42,7 +42,7 @@ export class SessionsChatAccessibilityHelp implements IAccessibleViewImplementat content.push(localize('sessionsChat.inputBackground', "Press Alt+Enter to start the session in the background without navigating into it. The started session appears in the Chat Sessions view.")); content.push(localize('sessionsChat.workspace', "Shift+Tab to navigate to the workspace picker and choose a workspace for your session.")); content.push(localize('sessionsChat.pullRequestSession', "In a repository section where New Session is a split button, focus New Session and press Right Arrow to reach its dropdown, then activate New Session from Pull Request to open a searchable pull request picker. Pull requests are grouped by review and assignment status. Use the arrow keys to navigate, Enter to create the session, and Escape to close the picker.")); - content.push(localize('sessionsChat.githubReferences', "Pull request and issue pills in the session header open their GitHub item in the GitHub Pull Requests extension when it is available. Pills that represent several items open a keyboard-accessible picker.")); + content.push(localize('sessionsChat.githubReferences', "Pull request and issue pills above the chat input open their GitHub item in the GitHub Pull Requests extension when it is available. Pills that represent several items open a keyboard-accessible picker.")); content.push(localize('sessionsChat.failingChecksPullRequest', "When the active session has failing checks, use Reveal in the banner above the input to open its pull request, or use Fix Checks to ask the agent to address the failures.")); content.push(localize('sessionsChat.pickFolderQuickPick', "To choose a folder from a searchable list instead, use the New Session in Folder command{0}.", '')); content.push(localize('sessionsChat.quickChat', "To start a workspace-less quick chat, use the New Quick Chat command{0} or the plus button on the Chats section in the sessions list. A quick chat has no workspace, so the workspace picker does not apply and the Toggle Side Panel command is disabled.", '')); @@ -56,8 +56,8 @@ export class SessionsChatAccessibilityHelp implements IAccessibleViewImplementat content.push(localize('sessionsChat.micContextMenu', "To choose a microphone or turn off dictation or Voice Mode, focus the microphone button in the input toolbar and open its context menu (for example Shift+F10).")); content.push(localize('sessionsChat.contextReferences', "Type # in the chat input to attach context. Use #file to reference a file or folder, or #session to reference another agent session. Referencing a session together with the /troubleshoot command analyzes that session's logs instead of the current one. Accept a suggestion with Tab or Enter; the reference appears as a pill above the input that you can remove.")); content.push(localize('sessionsChat.pastedText', "Long pasted text is stored as an attached text item and replaced in the input with a numbered inline reference.")); - content.push(localize('sessionsChat.backgroundActivities', "Press Shift+Tab from the chat input to reach status pills above it, then press Enter or Space to activate a pill. Live browsers appear in their own pill, and background activities such as running subagents in another. A pill with more than one entry opens a picker; use the up and down arrows to navigate, Enter to open an entry, and Escape to dismiss the picker and return focus to the pill.")); - content.push(localize('sessionsChat.conversations', "When a session supports multiple chats, a New Chat button is always shown: as a labeled button in the session header while the session has a single visible chat tab, and as a compact button at the end of the chat tab strip once the session has more than one visible chat tab. Activate it to start a new chat. A Chats dropdown is also shown in the session header meta row, at the end of the pills, once the session has more than one committed chat or the active chat has subagents. Side chats appear as first-level chats. A Subagents group lists work delegated by the active chat, and every item announces its state. When there is one first-level chat, only its Subagents are listed. The active chat or subagent is selected when the dropdown opens. Select an item to open or focus it.")); + content.push(localize('sessionsChat.backgroundActivities', "Press Shift+Tab from the chat input to reach metadata and status pills above it, then press Enter or Space to activate a pill. Live browsers appear in their own pill, and background activities such as running subagents in another. A pill with more than one entry opens a picker; use the up and down arrows to navigate, Enter to open an entry, and Escape to dismiss the picker and return focus to the pill.")); + content.push(localize('sessionsChat.conversations', "When multiple chats appear as tabs in a single group, the tab row replaces the session header and includes the session actions. Side-by-side chat groups retain the session header and keep their tab rows compact. Activate New Chat at the end of a tab row to start another chat in that group.")); content.push(localize('sessionsChat.subagentPills', "Subagent pills in the chat transcript can be dragged to a chat group's edge to open the subagent beside the current chat. With the keyboard, focus a subagent pill and press Alt+Enter to open it beside the current chat.")); content.push(localize('sessionsChat.chatGroups', "Chats can be arranged in groups. Focus the previous group{0} or next group{1}. Split the active chat into a group to the right{2} or below{3}, or move it to the previous group{4} or next group{5}.", ``, ``, ``, ``, ``, ``)); content.push(localize('sessionsChat.closeChat', "Activate a chat tab's close button to close (hide) that chat from the tab strip without deleting it; reopen it later from the Chats menu. The session's main chat cannot be closed.")); diff --git a/src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts b/src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts index ccf9e8130b85d0..0beae6ba6931a0 100644 --- a/src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts +++ b/src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts @@ -9,7 +9,7 @@ import { CHAT_TURN_ARTIFACT_PILL_ID, CHAT_TURN_CHANGES_PILL_ID } from '../../../ import { VIEW_SESSION_CHANGES_COMMAND_ID } from '../../../changes/common/changes.js'; import { OPEN_ISSUE_ACTION_ID, OPEN_PULL_REQUEST_ACTION_ID } from '../../../github/common/types.js'; import { SessionChatPillKind } from '../../common/sessionChatPills.js'; -import { getSessionChatPillKindForAction, SESSION_BROWSERS_PILL_ID, SESSION_SUBAGENTS_PILL_ID, shouldShowSessionTurnPills } from '../../browser/sessionChatInputToolbar.js'; +import { getSessionChatPillKindForAction, SESSION_BROWSERS_PILL_ID, SESSION_SUBAGENTS_PILL_ID } from '../../browser/sessionChatInputToolbar.js'; import { SESSION_CUSTOMIZATIONS_PILL_ID } from '../../browser/sessionCustomizations.js'; suite('SessionChatInputToolbar', () => { @@ -38,18 +38,4 @@ suite('SessionChatInputToolbar', () => { undefined, ]); }); - - test('keeps last-turn pills visible after completion only in metadata-input placement', () => { - assert.deepStrictEqual([ - shouldShowSessionTurnPills(false, false, false, true), - shouldShowSessionTurnPills(false, false, true, true), - shouldShowSessionTurnPills(false, true, false, true), - shouldShowSessionTurnPills(false, false, true, false), - ], [ - false, - true, - true, - false, - ]); - }); }); diff --git a/src/vs/sessions/contrib/files/browser/workspaceFolderActions.ts b/src/vs/sessions/contrib/files/browser/workspaceFolderActions.ts index 5d7e4c43006667..52bc9847437bf1 100644 --- a/src/vs/sessions/contrib/files/browser/workspaceFolderActions.ts +++ b/src/vs/sessions/contrib/files/browser/workspaceFolderActions.ts @@ -27,7 +27,6 @@ import { getSessionWorkspaceDisplayInfo, ISessionWorkspaceDisplayInfo } from '.. import { ChatPillActionViewItem } from '../../../../workbench/browser/chatPills.js'; import { SessionHasWorkspaceContext, IsQuickChatSessionContext } from '../../../common/contextkeys.js'; import { NEW_FILE_TAB_COMMAND_ID } from '../../../common/sessionCommands.js'; -import { SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING } from '../../../common/sessionConfig.js'; import { ISessionContext } from '../../../services/sessions/browser/sessionContext.js'; import { ISessionsService } from '../../../services/sessions/browser/sessionsService.js'; import { IActiveSession } from '../../../services/sessions/common/sessionsManagement.js'; @@ -44,9 +43,7 @@ export class OpenFilesViewAction extends Action2 { title: localize2('agentSessions.files', 'Files'), icon: Codicon.folder, f1: false, - // Workspace folder pill shown in the session header meta row - // (vs/sessions/browser/parts/sessionHeader.ts), rendered with a custom - // action view item. Ordered before the changes pill (order 0). + // Workspace metadata pill, ordered before changes. menu: { id: Menus.SessionHeaderMeta, group: 'navigation', @@ -54,7 +51,6 @@ export class OpenFilesViewAction extends Action2 { when: ContextKeyExpr.and( SessionHasWorkspaceContext, IsQuickChatSessionContext.negate(), - ContextKeyExpr.notEquals(`config.${SHOW_SESSION_METADATA_IN_CHAT_INPUT_SETTING}`, true), ) }, }); @@ -66,9 +62,8 @@ export class OpenFilesViewAction extends Action2 { const commandService = accessor.get(ICommandService); const layoutService = accessor.get(IAgentWorkbenchLayoutService); - // The clicked session is forwarded as the argument by the session header, - // which has already promoted it to be the active session. Fall back to the - // active session when invoked without an explicit argument. + // The clicked pill forwards its session. Fall back to the active session + // when invoked without an explicit argument. const targetSession = session ?? sessionsService.activeSession.get(); if (!targetSession) { return; @@ -83,13 +78,11 @@ export class OpenFilesViewAction extends Action2 { } registerAction2(OpenFilesViewAction); -// --- Open Files view action view item (session header workspace folder pill) +// --- Open Files view action view item /** - * Renders the session's workspace folder as a `