diff --git a/src/vs/sessions/contrib/changesView/browser/changesViewActions.ts b/src/vs/sessions/contrib/changesView/browser/changesViewActions.ts index 4b348230ee0..9ca4eeb503d 100644 --- a/src/vs/sessions/contrib/changesView/browser/changesViewActions.ts +++ b/src/vs/sessions/contrib/changesView/browser/changesViewActions.ts @@ -3,28 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import './media/changesViewActions.css'; -import { $, reset } from '../../../../base/browser/dom.js'; -import { BaseActionViewItem, IBaseActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; import { Codicon } from '../../../../base/common/codicons.js'; -import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js'; -import { autorun, observableFromEvent } from '../../../../base/common/observable.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { localize, localize2 } from '../../../../nls.js'; -import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js'; +import { Disposable } from '../../../../base/common/lifecycle.js'; +import { observableFromEvent } from '../../../../base/common/observable.js'; +import { localize2 } from '../../../../nls.js'; import { Action2, IAction2Options, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; -import { getAgentChangesSummary, hasValidDiff } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js'; +import { hasValidDiff } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js'; import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js'; import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js'; -import { Menus } from '../../../browser/menus.js'; import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; import { CHANGES_VIEW_ID } from './changesView.js'; -import { IAction } from '../../../../base/common/actions.js'; -import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js'; -import { IHoverService } from '../../../../platform/hover/browser/hover.js'; -import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; +import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js'; import { activeSessionHasChangesContextKey } from '../common/changes.js'; @@ -34,12 +25,6 @@ const openChangesViewActionOptions: IAction2Options = { title: localize2('openChangesView', "Changes"), icon: Codicon.diffMultiple, f1: false, - menu: { - id: Menus.TitleBarSessionMenu, - group: 'navigation', - order: 1, - when: ContextKeyExpr.equals(activeSessionHasChangesContextKey.key, true), - }, }; class OpenChangesViewAction extends Action2 { @@ -58,111 +43,17 @@ class OpenChangesViewAction extends Action2 { registerAction2(OpenChangesViewAction); -/** - * Custom action view item that renders the changes summary as: - * [diff-icon] +insertions -deletions - */ -class ChangesActionViewItem extends BaseActionViewItem { - - private _container: HTMLElement | undefined; - private readonly _renderDisposables = this._register(new DisposableStore()); - - constructor( - action: IAction, - options: IBaseActionViewItemOptions | undefined, - @ISessionsManagementService private readonly sessionManagementService: ISessionsManagementService, - @IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService, - @IHoverService private readonly hoverService: IHoverService, - ) { - super(undefined, action, options); - - this._register(autorun(reader => { - this.sessionManagementService.activeSession.read(reader); - this._updateLabel(); - })); - - this._register(this.agentSessionsService.model.onDidChangeSessions(() => { - this._updateLabel(); - })); - } - - override render(container: HTMLElement): void { - super.render(container); - this._container = container; - container.classList.add('changes-action-view-item'); - this._updateLabel(); - } - - private _updateLabel(): void { - if (!this._container) { - return; - } - - this._renderDisposables.clear(); - reset(this._container); - - const activeSession = this.sessionManagementService.getActiveSession(); - if (!activeSession) { - this._container.style.display = 'none'; - return; - } - - const agentSession = this.agentSessionsService.getSession(activeSession.resource); - const changes = agentSession?.changes; - - if (!changes || !hasValidDiff(changes)) { - this._container.style.display = 'none'; - return; - } - - const summary = getAgentChangesSummary(changes); - if (!summary) { - this._container.style.display = 'none'; - return; - } - - this._container.style.display = ''; - - // Diff icon - const iconEl = $('span.changes-action-icon' + ThemeIcon.asCSSSelector(Codicon.diffMultiple)); - this._container.appendChild(iconEl); - - // Insertions - const addedEl = $('span.changes-action-added'); - addedEl.textContent = `+${summary.insertions}`; - this._container.appendChild(addedEl); - - // Deletions - const removedEl = $('span.changes-action-removed'); - removedEl.textContent = `-${summary.deletions}`; - this._container.appendChild(removedEl); - - // Hover - this._renderDisposables.add(this.hoverService.setupManagedHover( - getDefaultHoverDelegate('mouse'), - this._container, - localize('agentSessions.viewChanges', "View All Changes") - )); - } -} - class ChangesViewActionsContribution extends Disposable implements IWorkbenchContribution { static readonly ID = 'workbench.contrib.changesViewActions'; constructor( - @IActionViewItemService actionViewItemService: IActionViewItemService, - @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @ISessionsManagementService sessionManagementService: ISessionsManagementService, @IAgentSessionsService agentSessionsService: IAgentSessionsService, ) { super(); - this._register(actionViewItemService.register(Menus.TitleBarSessionMenu, OpenChangesViewAction.ID, (action, options) => { - return instantiationService.createInstance(ChangesActionViewItem, action, options); - })); - // Bind context key: true when the active session has changes const sessionsChanged = observableFromEvent(this, agentSessionsService.model.onDidChangeSessions, () => { }); this._register(bindContextKey(activeSessionHasChangesContextKey, contextKeyService, reader => { diff --git a/src/vs/sessions/contrib/chat/browser/branchPicker.ts b/src/vs/sessions/contrib/chat/browser/branchPicker.ts index e12427b28ca..b5ada806bc2 100644 --- a/src/vs/sessions/contrib/chat/browser/branchPicker.ts +++ b/src/vs/sessions/contrib/chat/browser/branchPicker.ts @@ -31,6 +31,7 @@ interface IBranchItem { export class BranchPicker extends Disposable { private _selectedBranch: string | undefined; + private _preferredBranch: string | undefined; private _newSession: INewSession | undefined; private _branches: string[] = []; @@ -48,6 +49,13 @@ export class BranchPicker extends Disposable { return this._selectedBranch; } + /** + * Sets a preferred branch to select when branches are loaded. + */ + setPreferredBranch(branch: string | undefined): void { + this._preferredBranch = branch; + } + constructor( @IActionWidgetService private readonly actionWidgetService: IActionWidgetService, ) { @@ -85,8 +93,11 @@ export class BranchPicker extends Disposable { .filter((name): name is string => !!name) .filter(name => !name.includes(COPILOT_WORKTREE_PATTERN)); - // Select active branch, main, master, or the first branch by default - const defaultBranch = this._branches.find(b => b === repository.state.get().HEAD?.name) + // Select preferred branch (from draft), active branch, main, master, or the first branch + const preferred = this._preferredBranch; + this._preferredBranch = undefined; + const defaultBranch = (preferred ? this._branches.find(b => b === preferred) : undefined) + ?? this._branches.find(b => b === repository.state.get().HEAD?.name) ?? this._branches.find(b => b === 'main') ?? this._branches.find(b => b === 'master') ?? this._branches[0]; diff --git a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts index 247501dfc64..9f285ef4c48 100644 --- a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts +++ b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts @@ -11,7 +11,7 @@ import { toAction } from '../../../../base/common/actions.js'; import { Emitter } from '../../../../base/common/event.js'; import { KeyCode } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; -import { observableValue } from '../../../../base/common/observable.js'; +import { autorun, observableValue } from '../../../../base/common/observable.js'; import { URI } from '../../../../base/common/uri.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Button } from '../../../../base/browser/ui/button/button.js'; @@ -56,7 +56,7 @@ import { NewChatContextAttachments } from './newChatContextAttachments.js'; import { GITHUB_REMOTE_FILE_SCHEME } from '../../fileTreeView/browser/githubFileSystemProvider.js'; import { FolderPicker } from './folderPicker.js'; import { IGitService } from '../../../../workbench/contrib/git/common/gitService.js'; -import { IsolationModePicker, SessionTargetPicker } from './sessionTargetPicker.js'; +import { IsolationMode, IsolationModePicker, SessionTargetPicker } from './sessionTargetPicker.js'; import { BranchPicker } from './branchPicker.js'; import { SyncIndicator } from './syncIndicator.js'; import { INewSession, ISessionOptionGroup, RemoteNewSession } from './newSession.js'; @@ -75,6 +75,14 @@ const STORAGE_KEY_DRAFT_STATE = 'sessions.draftState'; const MIN_EDITOR_HEIGHT = 50; const MAX_EDITOR_HEIGHT = 200; +interface IDraftState extends IChatModelInputState { + target?: AgentSessionProviders; + isolationMode?: IsolationMode; + branch?: string; + folderUri?: string; + repo?: string; +} + // #region --- Chat Welcome Widget --- /** @@ -152,6 +160,16 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { // Slash commands private _slashCommandHandler: SlashCommandHandler | undefined; + // Input state + private _draftState: IDraftState | undefined = { + inputText: '', + attachments: [], + mode: { id: ChatModeKind.Agent, kind: ChatModeKind.Agent }, + selectedModel: undefined, + selections: [], + contrib: {} + }; + // Input history private readonly _history: ChatHistoryNavigator; private _historyNavigationBackwardsEnablement!: IHistoryNavigationContext['historyNavigationBackwardsEnablement']; @@ -190,6 +208,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._isolationModePicker.setVisible(isLocal); this._branchPicker.setVisible(isLocal); this._syncIndicator.setVisible(isLocal); + this._updateDraftState(); this._focusEditor(); })); @@ -200,21 +219,37 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._register(this._branchPicker.onDidChange((branch) => { this._syncIndicator.setBranch(branch); + this._updateDraftState(); this._focusEditor(); })); this._register(this._folderPicker.onDidSelectFolder(() => { + this._updateDraftState(); this._focusEditor(); })); - this._register(this._isolationModePicker.onDidChange(() => { + this._register(this._isolationModePicker.onDidChange((mode) => { + this._branchPicker.setVisible(mode === 'worktree'); + this._syncIndicator.setVisible(mode === 'worktree'); + this._updateDraftState(); this._focusEditor(); })); + this._register(this._repoPicker.onDidSelectRepo(() => { + this._updateDraftState(); + })); + // When language models change (e.g., extension activates), reinitialize if no model selected this._register(this.languageModelsService.onDidChangeLanguageModels(() => { this._initDefaultModel(); })); + + // Update input state when attachments or model change + this._register(this._contextAttachments.onDidChangeContext(() => this._updateDraftState())); + this._register(autorun(reader => { + this._currentLanguageModel.read(reader); + this._updateDraftState(); + })); } // --- Rendering --- @@ -261,11 +296,12 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._branchPicker.render(branchContainer); this._syncIndicator.render(branchContainer); - // Set initial visibility based on default target + // Set initial visibility based on default target and isolation mode const isLocal = this._targetPicker.selectedTarget === AgentSessionProviders.Background; + const isWorktree = this._isolationModePicker.isolationMode === 'worktree'; this._isolationModePicker.setVisible(isLocal); - this._branchPicker.setVisible(isLocal); - this._syncIndicator.setVisible(isLocal); + this._branchPicker.setVisible(isLocal && isWorktree); + this._syncIndicator.setVisible(isLocal && isWorktree); // Render target buttons & extension pickers this._renderOptionGroupPickers(); @@ -517,6 +553,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._slashCommandHandler = this._register(this.instantiationService.createInstance(SlashCommandHandler, this._editor)); this._register(this._editor.onDidChangeModelContent(() => { + this._updateDraftState(); this._updateSendButtonState(); })); } @@ -848,9 +885,8 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { if (this._history.isAtStart()) { return; } - const state = this._getInputState(); - if (state.inputText || state.attachments.length) { - this._history.overlay(state); + if (this._draftState?.inputText || this._draftState?.attachments.length) { + this._history.overlay(this._draftState); } this._navigateHistory(true); } @@ -859,21 +895,26 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { if (this._history.isAtEnd()) { return; } - const state = this._getInputState(); - if (state.inputText || state.attachments.length) { - this._history.overlay(state); + if (this._draftState?.inputText || this._draftState?.attachments.length) { + this._history.overlay(this._draftState); } this._navigateHistory(false); } - private _getInputState(): IChatModelInputState { - return { + private _updateDraftState(): void { + const attachments = [...this._contextAttachments.attachments]; + this._draftState = { inputText: this._editor?.getModel()?.getValue() ?? '', - attachments: [...this._contextAttachments.attachments], + attachments, mode: { id: ChatModeKind.Agent, kind: ChatModeKind.Agent }, selectedModel: this._currentLanguageModel.get(), selections: this._editor?.getSelections() ?? [], contrib: {}, + target: this._targetPicker.selectedTarget, + isolationMode: this._isolationModePicker.isolationMode, + branch: this._branchPicker.selectedBranch, + folderUri: this._folderPicker.selectedFolderUri?.toString(), + repo: this._repoPicker.selectedRepo, }; } @@ -932,7 +973,9 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._contextAttachments.attachments.length > 0 ? [...this._contextAttachments.attachments] : undefined ); - this._history.append(this._getInputState()); + if (this._draftState) { + this._history.append(this._draftState); + } this._clearDraftState(); this._sending = true; @@ -940,22 +983,23 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._updateSendButtonState(); this._updateInputLoadingState(); - this.sessionsManagementService.sendRequestForNewSession( - session.resource, - options?.openNewAfterSend ? { openNewSessionView: true } : undefined - ).then(() => { - // Release ref without disposing - the service owns disposal - this._newSession.clearAndLeak(); + + try { + await this.sessionsManagementService.sendRequestForNewSession( + session.resource, + options?.openNewAfterSend ? { openNewSessionView: true } : undefined + ); this._newSessionListener.clear(); this._contextAttachments.clear(); - }, e => { + } catch (e) { this.logService.error('Failed to send request:', e); - }).finally(() => { - this._sending = false; - this._editor.updateOptions({ readOnly: false }); - this._updateSendButtonState(); - this._updateInputLoadingState(); - }); + } + + + this._sending = false; + this._editor.updateOptions({ readOnly: false }); + this._updateSendButtonState(); + this._updateInputLoadingState(); } /** @@ -1001,10 +1045,23 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._currentLanguageModel.set(model, undefined); } } + if (draft.isolationMode) { + this._isolationModePicker.setPreferredIsolationMode(draft.isolationMode); + this._isolationModePicker.setIsolationMode(draft.isolationMode); + } + if (draft.branch) { + this._branchPicker.setPreferredBranch(draft.branch); + } + if (draft.folderUri) { + try { this._folderPicker.setSelectedFolder(URI.parse(draft.folderUri)); } catch { /* ignore */ } + } + if (draft.repo) { + this._repoPicker.setSelectedRepo(draft.repo); + } } } - private _getDraftState(): (IChatModelInputState & { target?: AgentSessionProviders }) | undefined { + private _getDraftState(): IDraftState | undefined { const raw = this.storageService.get(STORAGE_KEY_DRAFT_STATE, StorageScope.WORKSPACE); if (!raw) { return undefined; @@ -1017,21 +1074,20 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { } private _clearDraftState(): void { + this._draftState = undefined; this.storageService.remove(STORAGE_KEY_DRAFT_STATE, StorageScope.WORKSPACE); } saveState(): void { - const inputState = this._getInputState(); - const state = { - ...inputState, - attachments: inputState.attachments.map(IChatRequestVariableEntry.toExport), - target: this._targetPicker.selectedTarget, - }; - this.storageService.store(STORAGE_KEY_DRAFT_STATE, JSON.stringify(state), StorageScope.WORKSPACE, StorageTarget.MACHINE); + if (this._draftState) { + const state = { + ...this._draftState, + attachments: this._draftState.attachments.map(IChatRequestVariableEntry.toExport), + }; + this.storageService.store(STORAGE_KEY_DRAFT_STATE, JSON.stringify(state), StorageScope.WORKSPACE, StorageTarget.MACHINE); + } } - // --- Layout --- - layout(_height: number, _width: number): void { this._editor?.layout(); } @@ -1118,6 +1174,11 @@ export class NewChatViewPane extends ViewPane { override saveState(): void { this._widget?.saveState(); } + + override dispose(): void { + this._widget?.saveState(); + super.dispose(); + } } // #endregion diff --git a/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts b/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts index 9b3de3cff05..88aaa11a7a0 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts @@ -137,6 +137,7 @@ export type IsolationMode = 'worktree' | 'workspace'; export class IsolationModePicker extends Disposable { private _isolationMode: IsolationMode = 'worktree'; + private _preferredIsolationMode: IsolationMode | undefined; private _newSession: INewSession | undefined; private _repository: IGitRepository | undefined; @@ -171,7 +172,9 @@ export class IsolationModePicker extends Disposable { setRepository(repository: IGitRepository | undefined): void { this._repository = repository; if (repository) { - this._setMode('worktree'); + const preferred = this._preferredIsolationMode; + this._preferredIsolationMode = undefined; + this._setMode(preferred ?? 'worktree'); } else if (this._isolationMode === 'worktree') { this._setMode('workspace'); } @@ -207,6 +210,20 @@ export class IsolationModePicker extends Disposable { })); } + /** + * Sets a preferred isolation mode to apply when a repository is set. + */ + setPreferredIsolationMode(mode: IsolationMode): void { + this._preferredIsolationMode = mode; + } + + /** + * Programmatically set the isolation mode. + */ + setIsolationMode(mode: IsolationMode): void { + this._setMode(mode); + } + /** * Shows or hides the picker. */ diff --git a/src/vs/sessions/contrib/sessions/browser/media/sessionsTitleBarWidget.css b/src/vs/sessions/contrib/sessions/browser/media/sessionsTitleBarWidget.css index 26b50d594d9..b9f14273091 100644 --- a/src/vs/sessions/contrib/sessions/browser/media/sessionsTitleBarWidget.css +++ b/src/vs/sessions/contrib/sessions/browser/media/sessionsTitleBarWidget.css @@ -76,3 +76,19 @@ opacity: 0.5; flex-shrink: 0; } + +/* Changes summary */ +.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-changes { + display: flex; + align-items: center; + flex-shrink: 0; + gap: 3px; +} + +.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-changes-added { + color: var(--vscode-gitDecoration-addedResourceForeground); +} + +.command-center .agent-sessions-titlebar-container .agent-sessions-titlebar-changes-removed { + color: var(--vscode-gitDecoration-deletedResourceForeground); +} diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsTitleBarWidget.ts b/src/vs/sessions/contrib/sessions/browser/sessionsTitleBarWidget.ts index f2a3198b33b..f15bed0dc94 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsTitleBarWidget.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsTitleBarWidget.ts @@ -19,9 +19,8 @@ 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, getAgentChangesSummary, hasValidDiff } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsModel.js'; import { IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; - import { Menus } from '../../../browser/menus.js'; import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js'; import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js'; @@ -128,9 +127,10 @@ export class SessionsTitleBarWidget extends BaseActionViewItem { const label = this._getActiveSessionLabel(); const icon = this._getActiveSessionIcon(); const repoLabel = this._getRepositoryLabel(); + const changesSummary = this._getChangesSummary(); // Build a render-state key from all displayed data - const renderState = `${icon?.id ?? ''}|${label}|${repoLabel ?? ''}`; + const renderState = `${icon?.id ?? ''}|${label}|${repoLabel ?? ''}|${changesSummary?.insertions ?? ''}|${changesSummary?.deletions ?? ''}`; // Skip re-render if state hasn't changed if (this._lastRenderState === renderState) { @@ -175,6 +175,25 @@ export class SessionsTitleBarWidget extends BaseActionViewItem { centerGroup.appendChild(repoEl); } + // Changes summary shown next to the repo + if (changesSummary) { + const separator2 = $('span.agent-sessions-titlebar-separator'); + separator2.textContent = '\u00B7'; + centerGroup.appendChild(separator2); + + const changesEl = $('span.agent-sessions-titlebar-changes'); + + const addedEl = $('span.agent-sessions-titlebar-changes-added'); + addedEl.textContent = `+${changesSummary.insertions}`; + changesEl.appendChild(addedEl); + + const removedEl = $('span.agent-sessions-titlebar-changes-removed'); + removedEl.textContent = `-${changesSummary.deletions}`; + changesEl.appendChild(removedEl); + + centerGroup.appendChild(changesEl); + } + sessionPill.appendChild(centerGroup); // Click handler on pill - show sessions picker @@ -336,6 +355,24 @@ export class SessionsTitleBarWidget extends BaseActionViewItem { menu.dispose(); } + /** + * Get the changes summary for the active session. + */ + private _getChangesSummary(): { insertions: number; deletions: number } | undefined { + const activeSession = this.activeSessionService.getActiveSession(); + if (!activeSession) { + return undefined; + } + + const agentSession = this.agentSessionsService.getSession(activeSession.resource); + const changes = agentSession?.changes; + if (!changes || !hasValidDiff(changes)) { + return undefined; + } + + return getAgentChangesSummary(changes); + } + private _showSessionsPicker(): void { const picker = this.instantiationService.createInstance(AgentSessionsPicker, undefined, { overrideSessionOpen: (session, openOptions) => this.activeSessionService.openSession(session.resource, openOptions)