diff --git a/src/vs/sessions/SESSIONS_PROVIDER.md b/src/vs/sessions/SESSIONS_PROVIDER.md index 3292867ed5d..1db3330f1ae 100644 --- a/src/vs/sessions/SESSIONS_PROVIDER.md +++ b/src/vs/sessions/SESSIONS_PROVIDER.md @@ -65,7 +65,7 @@ The common session interface exposed by all providers. It is a self-contained fa | `loading` | `IObservable` | Whether the session is initializing | | `isArchived` | `IObservable` | Archive state | | `isRead` | `IObservable` | Read/unread state | -| `description` | `IObservable` | Status description (e.g., current agent action) | +| `description` | `IObservable` | Status description (e.g., current agent action), supports markdown | | `lastTurnEnd` | `IObservable` | When the last agent turn ended | | `pullRequest` | `IObservable` | Associated pull request | diff --git a/src/vs/sessions/contrib/copilotChatSessions/browser/copilotChatSessionsProvider.ts b/src/vs/sessions/contrib/copilotChatSessions/browser/copilotChatSessionsProvider.ts index 8fb2e4bb304..9990b803d50 100644 --- a/src/vs/sessions/contrib/copilotChatSessions/browser/copilotChatSessionsProvider.ts +++ b/src/vs/sessions/contrib/copilotChatSessions/browser/copilotChatSessionsProvider.ts @@ -5,6 +5,7 @@ import { Emitter, Event } from '../../../../base/common/event.js'; import { Codicon } from '../../../../base/common/codicons.js'; +import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { IObservable, observableValue, transaction } from '../../../../base/common/observable.js'; import { themeColorFromId, ThemeIcon } from '../../../../base/common/themables.js'; @@ -77,8 +78,8 @@ export class CopilotCLISession extends Disposable implements ISessionData { private readonly _title = observableValue(this, ''); readonly title: IObservable = this._title; - private readonly _description: ReturnType>; - readonly description: IObservable; + private readonly _description: ReturnType>; + readonly description: IObservable; private readonly _updatedAt = observableValue(this, new Date()); readonly updatedAt: IObservable = this._updatedAt; @@ -381,7 +382,7 @@ export class RemoteNewSession extends Disposable implements ISessionData { readonly isArchived: IObservable = observableValue(this, false); readonly isRead: IObservable = observableValue(this, true); - readonly description: IObservable = observableValue(this, undefined); + readonly description: IObservable = observableValue(this, undefined); readonly lastTurnEnd: IObservable = observableValue(this, undefined); readonly gitHubInfo: IObservable = observableValue(this, undefined); @@ -623,8 +624,8 @@ class AgentSessionAdapter implements ISessionData { private readonly _isRead: ReturnType>; readonly isRead: IObservable; - private readonly _description: ReturnType>; - readonly description: IObservable; + private readonly _description: ReturnType>; + readonly description: IObservable; private readonly _lastTurnEnd: ReturnType>; readonly lastTurnEnd: IObservable; @@ -703,11 +704,11 @@ class AgentSessionAdapter implements ISessionData { } } - private _extractDescription(session: IAgentSession): string | undefined { + private _extractDescription(session: IAgentSession): IMarkdownString | undefined { if (!session.description) { return undefined; } - return typeof session.description === 'string' ? session.description : session.description.value; + return typeof session.description === 'string' ? new MarkdownString(session.description) : session.description; } private _extractGitHubInfo(session: IAgentSession): IGitHubInfo | undefined { diff --git a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts index a8937d722ee..9fbeab4c4f6 100644 --- a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts @@ -6,6 +6,7 @@ import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { Emitter, Event } from '../../../../base/common/event.js'; +import { IMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js'; import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js'; import { basename } from '../../../../base/common/resources.js'; import { ISettableObservable, observableValue } from '../../../../base/common/observable.js'; @@ -54,7 +55,7 @@ class RemoteSessionAdapter implements ISessionData { readonly loading = observableValue('loading', false); readonly isArchived = observableValue('isArchived', false); readonly isRead = observableValue('isRead', true); - readonly description: ISettableObservable; + readonly description: ISettableObservable; readonly lastTurnEnd: ISettableObservable; readonly gitHubInfo = observableValue('gitHubInfo', undefined); @@ -79,7 +80,7 @@ class RemoteSessionAdapter implements ISessionData { this.title = observableValue('title', metadata.summary ?? `Session ${rawId.substring(0, 8)}`); this.updatedAt = observableValue('updatedAt', new Date(metadata.modifiedTime)); this.lastTurnEnd = observableValue('lastTurnEnd', metadata.modifiedTime ? new Date(metadata.modifiedTime) : undefined); - this.description = observableValue('description', providerLabel); + this.description = observableValue('description', new MarkdownString().appendText(providerLabel)); this.workspace = observableValue('workspace', metadata.workingDirectory ? RemoteAgentHostSessionsProvider.buildWorkspace(metadata.workingDirectory, providerLabel, connectionAuthority) : undefined); diff --git a/src/vs/sessions/contrib/sessions/browser/media/sessionsList.css b/src/vs/sessions/contrib/sessions/browser/media/sessionsList.css index a5472d80953..11026fb4827 100644 --- a/src/vs/sessions/contrib/sessions/browser/media/sessionsList.css +++ b/src/vs/sessions/contrib/sessions/browser/media/sessionsList.css @@ -204,6 +204,13 @@ text-overflow: ellipsis; } + .session-description p { + margin: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + .session-description:empty { display: none; } diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsTitleBarWidget.ts b/src/vs/sessions/contrib/sessions/browser/sessionsTitleBarWidget.ts index 0ef7cd4ea7a..dfdb4aa6d4b 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsTitleBarWidget.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsTitleBarWidget.ts @@ -21,11 +21,11 @@ import { IWorkbenchLayoutService, Parts } from '../../../../workbench/services/l import { Menus } from '../../../browser/menus.js'; import { IWorkbenchContribution } from '../../../../workbench/common/contributions.js'; import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js'; -import { ISessionsManagementService, IsNewChatSessionContext } from './sessionsManagementService.js'; +import { ISessionsManagementService } from './sessionsManagementService.js'; import { autorun, observableSignalFromEvent } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js'; -import { ChatSessionProviderIdContext, SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js'; +import { ChatSessionProviderIdContext, IsNewChatSessionContext, SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js'; import { ISessionsProvidersService } from './sessionsProvidersService.js'; import { SessionStatus } from '../common/sessionData.js'; import { SHOW_SESSIONS_PICKER_COMMAND_ID } from './sessionsActions.js'; diff --git a/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts b/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts index e94fa0e5d00..64c1bfb61db 100644 --- a/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts +++ b/src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts @@ -251,6 +251,7 @@ class SessionItemRenderer implements ITreeRenderer { const sessionStatus = element.status.read(reader); const changes = element.changes.read(reader); @@ -317,22 +318,39 @@ class SessionItemRenderer implements ITreeRenderer 0) { DOM.append(template.detailsRow, $('span.session-separator.has-separator')); } const statusEl = DOM.append(template.detailsRow, $('span.session-description')); - statusEl.textContent = description ?? localize('needsInput', "Input needed"); + if (description) { + descriptionDisposable.value = this.markdownRendererService.render(description, { sanitizerConfig: { replaceWithPlaintext: true } }, statusEl); + } else { + descriptionDisposable.clear(); + statusEl.textContent = localize('needsInput', "Input needed"); + } parts.push(statusEl); } else if (sessionStatus === SessionStatus.Error) { if (parts.length > 0) { DOM.append(template.detailsRow, $('span.session-separator.has-separator')); } const statusEl = DOM.append(template.detailsRow, $('span.session-description')); - statusEl.textContent = localize('failed', "Failed"); + if (description) { + descriptionDisposable.value = this.markdownRendererService.render(description, { sanitizerConfig: { replaceWithPlaintext: true } }, statusEl); + } else { + descriptionDisposable.clear(); + statusEl.textContent = localize('failed', "Failed"); + } parts.push(statusEl); + } else { + descriptionDisposable.clear(); } // Timestamp — visible when not hiding details diff --git a/src/vs/sessions/contrib/sessions/common/sessionData.ts b/src/vs/sessions/contrib/sessions/common/sessionData.ts index c5f22be07d3..9707692a2fe 100644 --- a/src/vs/sessions/contrib/sessions/common/sessionData.ts +++ b/src/vs/sessions/contrib/sessions/common/sessionData.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IMarkdownString } from '../../../../base/common/htmlContent.js'; import { IObservable } from '../../../../base/common/observable.js'; import { ThemeIcon } from '../../../../base/common/themables.js'; import { URI } from '../../../../base/common/uri.js'; @@ -116,8 +117,8 @@ export interface ISessionData { readonly isArchived: IObservable; /** Whether the session has been read. */ readonly isRead: IObservable; - /** Status description shown while the session is active (e.g., current agent action). */ - readonly description: IObservable; + /** Status description shown while the session is active (e.g., current agent action). Supports markdown. */ + readonly description: IObservable; /** Timestamp of when the last agent turn ended, if any. */ readonly lastTurnEnd: IObservable; /** GitHub information associated with this session, if any. */ @@ -164,8 +165,8 @@ export interface IChat { readonly isArchived: IObservable; /** Whether the chat has been read. */ readonly isRead: IObservable; - /** Status description shown while the chat is active (e.g., current agent action). */ - readonly description: IObservable; + /** Status description shown while the chat is active (e.g., current agent action). Supports markdown. */ + readonly description: IObservable; /** Timestamp of when the last agent turn ended, if any. */ readonly lastTurnEnd: IObservable; /** GitHub information associated with this session, if any. */ @@ -212,8 +213,8 @@ export interface ISession { readonly isArchived: IObservable; /** Whether the session has been read. */ readonly isRead: IObservable; - /** Status description shown while the session is active (e.g., current agent action). */ - readonly description: IObservable; + /** Status description shown while the session is active (e.g., current agent action). Supports markdown. */ + readonly description: IObservable; /** Timestamp of when the last agent turn ended, if any. */ readonly lastTurnEnd: IObservable; /** GitHub information associated with this session, if any. */