mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-31 00:10:04 +08:00
sessions: change session description to IMarkdownString and render markdown (#306468)
Some checks failed
Monaco Editor checks / Monaco Editor checks (push) Has been cancelled
Code OSS (node_modules) / Compile (push) Has been cancelled
Code OSS (node_modules) / Linux (push) Has been cancelled
Code OSS (node_modules) / macOS (push) Has been cancelled
Code OSS (node_modules) / Windows (push) Has been cancelled
Checking Component Screenshots / Checking Component Screenshots (push) Has been cancelled
Some checks failed
Monaco Editor checks / Monaco Editor checks (push) Has been cancelled
Code OSS (node_modules) / Compile (push) Has been cancelled
Code OSS (node_modules) / Linux (push) Has been cancelled
Code OSS (node_modules) / macOS (push) Has been cancelled
Code OSS (node_modules) / Windows (push) Has been cancelled
Checking Component Screenshots / Checking Component Screenshots (push) Has been cancelled
* sessions: change description type to IMarkdownString and render it
- Change ISessionData, IChat, and ISession description field types from
string | undefined to IMarkdownString | undefined
- Update all provider implementations (CopilotCLISession, RemoteNewSession,
AgentSessionAdapter, RemoteSessionAdapter) to use IMarkdownString
- In AgentSessionAdapter._extractDescription, wrap plain strings in
MarkdownString; IMarkdownString values are passed through as-is
- In sessionsList.ts, render markdown descriptions using markdownRendererService
with sanitizerConfig: { replaceWithPlaintext: true } — matching the existing
agentSessionsViewer.ts pattern; fallback status labels use plain textContent
- Add MutableDisposable to track the markdown render lifecycle so it is
properly cleared when status changes
- Update CSS to handle paragraph elements inside rendered markdown
- Update SESSIONS_PROVIDER.md documentation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* sessions: clear descriptionDisposable on plain-text fallback to avoid leaks
When the status is active (InProgress/NeedsInput/Error) but description
becomes undefined, the previous markdown renderer was left alive even though
its container element had been removed from the DOM by the autorun rebuild.
Fix: call descriptionDisposable.clear() in every else-branch (plain-text
fallback) so any prior IMarkdownRendererService render is disposed before
the new plain-text content is set.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* sessions: fix import of IsNewChatSessionContext after move to common/contextkeys.ts
Upstream commit moved IsNewChatSessionContext from sessionsManagementService.ts
to the centralised common/contextkeys.ts. Update the import in
sessionsTitleBarWidget.ts accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
4ebc211cda
commit
0930f054ee
@@ -65,7 +65,7 @@ The common session interface exposed by all providers. It is a self-contained fa
|
||||
| `loading` | `IObservable<boolean>` | Whether the session is initializing |
|
||||
| `isArchived` | `IObservable<boolean>` | Archive state |
|
||||
| `isRead` | `IObservable<boolean>` | Read/unread state |
|
||||
| `description` | `IObservable<string \| undefined>` | Status description (e.g., current agent action) |
|
||||
| `description` | `IObservable<IMarkdownString \| undefined>` | Status description (e.g., current agent action), supports markdown |
|
||||
| `lastTurnEnd` | `IObservable<Date \| undefined>` | When the last agent turn ended |
|
||||
| `pullRequest` | `IObservable<ISessionPullRequest \\| undefined>` | Associated pull request |
|
||||
|
||||
|
||||
@@ -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<string> = this._title;
|
||||
|
||||
private readonly _description: ReturnType<typeof observableValue<string | undefined>>;
|
||||
readonly description: IObservable<string | undefined>;
|
||||
private readonly _description: ReturnType<typeof observableValue<IMarkdownString | undefined>>;
|
||||
readonly description: IObservable<IMarkdownString | undefined>;
|
||||
|
||||
private readonly _updatedAt = observableValue(this, new Date());
|
||||
readonly updatedAt: IObservable<Date> = this._updatedAt;
|
||||
@@ -381,7 +382,7 @@ export class RemoteNewSession extends Disposable implements ISessionData {
|
||||
|
||||
readonly isArchived: IObservable<boolean> = observableValue(this, false);
|
||||
readonly isRead: IObservable<boolean> = observableValue(this, true);
|
||||
readonly description: IObservable<string | undefined> = observableValue(this, undefined);
|
||||
readonly description: IObservable<IMarkdownString | undefined> = observableValue(this, undefined);
|
||||
readonly lastTurnEnd: IObservable<Date | undefined> = observableValue(this, undefined);
|
||||
readonly gitHubInfo: IObservable<IGitHubInfo | undefined> = observableValue(this, undefined);
|
||||
|
||||
@@ -623,8 +624,8 @@ class AgentSessionAdapter implements ISessionData {
|
||||
private readonly _isRead: ReturnType<typeof observableValue<boolean>>;
|
||||
readonly isRead: IObservable<boolean>;
|
||||
|
||||
private readonly _description: ReturnType<typeof observableValue<string | undefined>>;
|
||||
readonly description: IObservable<string | undefined>;
|
||||
private readonly _description: ReturnType<typeof observableValue<IMarkdownString | undefined>>;
|
||||
readonly description: IObservable<IMarkdownString | undefined>;
|
||||
|
||||
private readonly _lastTurnEnd: ReturnType<typeof observableValue<Date | undefined>>;
|
||||
readonly lastTurnEnd: IObservable<Date | undefined>;
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<string | undefined>;
|
||||
readonly description: ISettableObservable<IMarkdownString | undefined>;
|
||||
readonly lastTurnEnd: ISettableObservable<Date | undefined>;
|
||||
readonly gitHubInfo = observableValue<IGitHubInfo | undefined>('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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -251,6 +251,7 @@ class SessionItemRenderer implements ITreeRenderer<SessionListItem, FuzzyScore,
|
||||
|
||||
// Details row — reactive: badge · diff stats · time
|
||||
const timeDisposable = template.elementDisposables.add(new MutableDisposable());
|
||||
const descriptionDisposable = template.elementDisposables.add(new MutableDisposable());
|
||||
template.elementDisposables.add(autorun(reader => {
|
||||
const sessionStatus = element.status.read(reader);
|
||||
const changes = element.changes.read(reader);
|
||||
@@ -317,22 +318,39 @@ class SessionItemRenderer implements ITreeRenderer<SessionListItem, FuzzyScore,
|
||||
DOM.append(template.detailsRow, $('span.session-separator.has-separator'));
|
||||
}
|
||||
const statusEl = DOM.append(template.detailsRow, $('span.session-description'));
|
||||
statusEl.textContent = description ?? localize('working', "Working...");
|
||||
if (description) {
|
||||
descriptionDisposable.value = this.markdownRendererService.render(description, { sanitizerConfig: { replaceWithPlaintext: true } }, statusEl);
|
||||
} else {
|
||||
descriptionDisposable.clear();
|
||||
statusEl.textContent = localize('working', "Working...");
|
||||
}
|
||||
parts.push(statusEl);
|
||||
} else if (sessionStatus === SessionStatus.NeedsInput) {
|
||||
if (parts.length > 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
|
||||
|
||||
@@ -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<boolean>;
|
||||
/** Whether the session has been read. */
|
||||
readonly isRead: IObservable<boolean>;
|
||||
/** Status description shown while the session is active (e.g., current agent action). */
|
||||
readonly description: IObservable<string | undefined>;
|
||||
/** Status description shown while the session is active (e.g., current agent action). Supports markdown. */
|
||||
readonly description: IObservable<IMarkdownString | undefined>;
|
||||
/** Timestamp of when the last agent turn ended, if any. */
|
||||
readonly lastTurnEnd: IObservable<Date | undefined>;
|
||||
/** GitHub information associated with this session, if any. */
|
||||
@@ -164,8 +165,8 @@ export interface IChat {
|
||||
readonly isArchived: IObservable<boolean>;
|
||||
/** Whether the chat has been read. */
|
||||
readonly isRead: IObservable<boolean>;
|
||||
/** Status description shown while the chat is active (e.g., current agent action). */
|
||||
readonly description: IObservable<string | undefined>;
|
||||
/** Status description shown while the chat is active (e.g., current agent action). Supports markdown. */
|
||||
readonly description: IObservable<IMarkdownString | undefined>;
|
||||
/** Timestamp of when the last agent turn ended, if any. */
|
||||
readonly lastTurnEnd: IObservable<Date | undefined>;
|
||||
/** GitHub information associated with this session, if any. */
|
||||
@@ -212,8 +213,8 @@ export interface ISession {
|
||||
readonly isArchived: IObservable<boolean>;
|
||||
/** Whether the session has been read. */
|
||||
readonly isRead: IObservable<boolean>;
|
||||
/** Status description shown while the session is active (e.g., current agent action). */
|
||||
readonly description: IObservable<string | undefined>;
|
||||
/** Status description shown while the session is active (e.g., current agent action). Supports markdown. */
|
||||
readonly description: IObservable<IMarkdownString | undefined>;
|
||||
/** Timestamp of when the last agent turn ended, if any. */
|
||||
readonly lastTurnEnd: IObservable<Date | undefined>;
|
||||
/** GitHub information associated with this session, if any. */
|
||||
|
||||
Reference in New Issue
Block a user