From 43e59622e302f1c14e8ebb017dbf8be625446c5e Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 15 Apr 2026 16:48:36 -0700 Subject: [PATCH 1/5] sessions: set session title to first message text immediately When a new agent host session starts, the title shows "New Session" for a long time until the AI-generated title arrives. Fix this by: 1. Dispatching SessionTitleChanged with the user's message text on the first turn in agentSideEffects 2. Overlaying the live title from the state manager in listSessions so refreshSessions picks up the updated title instead of the stale SDK value 3. Handling notify/sessionSummaryChanged in both local and remote session providers to propagate title changes via the notification channel Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> (Written by Copilot) --- src/vs/platform/agentHost/node/agentService.ts | 9 +++++++-- src/vs/platform/agentHost/node/agentSideEffects.ts | 13 +++++++++++++ .../browser/localAgentHostSessionsProvider.ts | 4 ++++ .../browser/remoteAgentHostSessionsProvider.ts | 4 ++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index ca507282d8e9f..eaf50c300e2ee 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -176,11 +176,16 @@ export class AgentService extends Disposable implements IAgentService { return s; })); - // Overlay live session status from the state manager + // Overlay live session state from the state manager const withStatus = result.map(s => { const liveState = this._stateManager.getSessionState(s.session.toString()); if (liveState) { - return { ...s, status: liveState.summary.status, model: liveState.summary.model ?? s.model }; + return { + ...s, + summary: liveState.summary.title ?? s.summary, + status: liveState.summary.status, + model: liveState.summary.model ?? s.model, + }; } return s; }); diff --git a/src/vs/platform/agentHost/node/agentSideEffects.ts b/src/vs/platform/agentHost/node/agentSideEffects.ts index 1443c922307d2..42298e2745b79 100644 --- a/src/vs/platform/agentHost/node/agentSideEffects.ts +++ b/src/vs/platform/agentHost/node/agentSideEffects.ts @@ -555,6 +555,19 @@ export class AgentSideEffects extends Disposable { for (const mapper of this._eventMappers.values()) { mapper.reset(action.session); } + + // On the very first turn, immediately set the session title to the + // user's message so the UI shows a meaningful title right away + // instead of "New Session" while waiting for the AI-generated title. + const state = this._stateManager.getSessionState(action.session); + if (state && state.turns.length === 0) { + this._stateManager.dispatchServerAction({ + type: ActionType.SessionTitleChanged, + session: action.session, + title: action.userMessage.text, + }); + } + const agent = this._options.getAgent(action.session); if (!agent) { this._stateManager.dispatchServerAction({ diff --git a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts index f2f475248fd42..98af775076f1f 100644 --- a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts @@ -280,6 +280,10 @@ export class LocalAgentHostSessionsProvider extends Disposable implements IAgent this._handleSessionAdded(n.summary); } else if (n.type === 'notify/sessionRemoved') { this._handleSessionRemoved(n.session); + } else if (n.type === 'notify/sessionSummaryChanged') { + if (n.changes.title !== undefined) { + this._handleTitleChanged(n.session, n.changes.title); + } } })); diff --git a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts index e34942367070d..3bc2751524d6b 100644 --- a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts @@ -396,6 +396,10 @@ export class RemoteAgentHostSessionsProvider extends Disposable implements IAgen this._handleSessionAdded(n.summary); } else if (n.type === 'notify/sessionRemoved') { this._handleSessionRemoved(n.session); + } else if (n.type === 'notify/sessionSummaryChanged') { + if (n.changes.title !== undefined) { + this._handleTitleChanged(n.session, n.changes.title); + } } })); From c3ffe454f4aa850b332de60441ffce54240dc8d7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 15 Apr 2026 17:27:42 -0700 Subject: [PATCH 2/5] Simplify: use empty initial title, add unit + integration tests - Use empty string as initial session title instead of 'New Session' sentinel. The UI already falls back to the localized 'New Session' label when the title is falsy, so no display change. - Simplify listSessions overlay: liveTitle || s.summary (natural falsy) - Simplify side effects check: just turns.length === 0, no string compare - Remove redundant sessionSummaryChanged notification handlers - Add 3 unit tests for immediate title dispatch (first turn, whitespace, second turn) - Add 1 unit test for listSessions title overlay - Add 1 integration test for end-to-end immediate title via WebSocket - Update existing agent-generated title integration test to expect immediate title first (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../platform/agentHost/node/agentService.ts | 9 ++- .../agentHost/node/agentSideEffects.ts | 7 +- .../agentHost/test/node/agentService.test.ts | 17 ++++ .../test/node/agentSideEffects.test.ts | 81 ++++++++++++++++++- .../sessionFeatures.integrationTest.ts | 35 +++++++- .../test/node/protocolServerHandler.test.ts | 2 +- .../browser/localAgentHostSessionsProvider.ts | 4 - .../remoteAgentHostSessionsProvider.ts | 4 - 8 files changed, 142 insertions(+), 17 deletions(-) diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index eaf50c300e2ee..e694fb0293f2f 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -176,13 +176,16 @@ export class AgentService extends Disposable implements IAgentService { return s; })); - // Overlay live session state from the state manager + // Overlay live session state from the state manager. + // For the title, prefer the state manager's value when it is + // non-empty, so SDK-sourced titles are not overwritten by the + // initial empty placeholder. const withStatus = result.map(s => { const liveState = this._stateManager.getSessionState(s.session.toString()); if (liveState) { return { ...s, - summary: liveState.summary.title ?? s.summary, + summary: liveState.summary.title || s.summary, status: liveState.summary.status, model: liveState.summary.model ?? s.model, }; @@ -265,7 +268,7 @@ export class AgentService extends Disposable implements IAgentService { const summary: ISessionSummary = { resource: session.toString(), provider: provider.id, - title: 'New Session', + title: '', status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now(), diff --git a/src/vs/platform/agentHost/node/agentSideEffects.ts b/src/vs/platform/agentHost/node/agentSideEffects.ts index 42298e2745b79..3215bc6cba116 100644 --- a/src/vs/platform/agentHost/node/agentSideEffects.ts +++ b/src/vs/platform/agentHost/node/agentSideEffects.ts @@ -558,13 +558,14 @@ export class AgentSideEffects extends Disposable { // On the very first turn, immediately set the session title to the // user's message so the UI shows a meaningful title right away - // instead of "New Session" while waiting for the AI-generated title. + // while waiting for the AI-generated title. const state = this._stateManager.getSessionState(action.session); - if (state && state.turns.length === 0) { + const fallbackTitle = action.userMessage.text.trim(); + if (state && state.turns.length === 0 && fallbackTitle.length > 0) { this._stateManager.dispatchServerAction({ type: ActionType.SessionTitleChanged, session: action.session, - title: action.userMessage.text, + title: fallbackTitle, }); } diff --git a/src/vs/platform/agentHost/test/node/agentService.test.ts b/src/vs/platform/agentHost/test/node/agentService.test.ts index 9045a91f874ed..6142de88cefb2 100644 --- a/src/vs/platform/agentHost/test/node/agentService.test.ts +++ b/src/vs/platform/agentHost/test/node/agentService.test.ts @@ -221,6 +221,23 @@ suite('AgentService (node dispatcher)', () => { assert.strictEqual(sessions[0].summary, 'Auto-generated Title'); }); + test('listSessions overlays live state manager title over SDK title', async () => { + service.registerProvider(copilotAgent); + + const session = await service.createSession({ provider: 'copilot' }); + + // Simulate immediate title change via state manager + service.stateManager.dispatchServerAction({ + type: ActionType.SessionTitleChanged, + session: session.toString(), + title: 'User first message', + }); + + const sessions = await service.listSessions(); + assert.strictEqual(sessions.length, 1); + assert.strictEqual(sessions[0].summary, 'User first message'); + }); + test('createSession stores live session config', async () => { service.registerProvider(copilotAgent); diff --git a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts index c3fa5369d4d57..8bbd9e141e9e4 100644 --- a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts +++ b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts @@ -129,7 +129,86 @@ suite('AgentSideEffects', () => { }); }); - // ---- handleAction: session/turnCancelled ---------------------------- + // ---- immediate title on first turn ----------------------------------- + + suite('immediate title on first turn', () => { + + function setupDefaultSession(): void { + stateManager.createSession({ + resource: sessionUri.toString(), + provider: 'mock', + title: '', + status: SessionStatus.Idle, + createdAt: Date.now(), + modifiedAt: Date.now(), + project: { uri: 'file:///test-project', displayName: 'Test Project' }, + }); + stateManager.dispatchServerAction({ type: ActionType.SessionReady, session: sessionUri.toString() }); + } + + test('dispatches titleChanged with user message on first turn', () => { + setupDefaultSession(); + + const envelopes: IActionEnvelope[] = []; + disposables.add(stateManager.onDidEmitEnvelope(e => envelopes.push(e))); + + sideEffects.handleAction({ + type: ActionType.SessionTurnStarted, + session: sessionUri.toString(), + turnId: 'turn-1', + userMessage: { text: 'Fix the login bug' }, + }); + + const titleAction = envelopes.find(e => e.action.type === ActionType.SessionTitleChanged); + assert.ok(titleAction, 'should dispatch session/titleChanged'); + if (titleAction?.action.type === ActionType.SessionTitleChanged) { + assert.strictEqual(titleAction.action.title, 'Fix the login bug'); + } + }); + + test('does not dispatch titleChanged when message is whitespace', () => { + setupDefaultSession(); + + const envelopes: IActionEnvelope[] = []; + disposables.add(stateManager.onDidEmitEnvelope(e => envelopes.push(e))); + + sideEffects.handleAction({ + type: ActionType.SessionTurnStarted, + session: sessionUri.toString(), + turnId: 'turn-1', + userMessage: { text: ' ' }, + }); + + const titleAction = envelopes.find(e => e.action.type === ActionType.SessionTitleChanged); + assert.strictEqual(titleAction, undefined, 'should not dispatch titleChanged for empty message'); + }); + + test('does not dispatch titleChanged on second turn', () => { + setupDefaultSession(); + startTurn('turn-1'); + + // After the first turn starts, the session has an active turn. + // Now change the title back to default to isolate the turns.length check. + stateManager.dispatchServerAction({ + type: ActionType.SessionTurnComplete, + session: sessionUri.toString(), + turnId: 'turn-1', + }); + + const envelopes: IActionEnvelope[] = []; + disposables.add(stateManager.onDidEmitEnvelope(e => envelopes.push(e))); + + sideEffects.handleAction({ + type: ActionType.SessionTurnStarted, + session: sessionUri.toString(), + turnId: 'turn-2', + userMessage: { text: 'second message' }, + }); + + const titleAction = envelopes.find(e => e.action.type === ActionType.SessionTitleChanged); + assert.strictEqual(titleAction, undefined, 'should not dispatch titleChanged on second turn'); + }); + }); suite('handleAction — session/turnCancelled', () => { diff --git a/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts b/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts index c8eee7fbae66b..d8d890566f320 100644 --- a/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts +++ b/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts @@ -77,7 +77,15 @@ suite('Protocol WebSocket — Session Features', function () { const sessionUri = await createAndSubscribeSession(client, 'test-agent-title'); dispatchTurnStarted(client, sessionUri, 'turn-title', 'with-title', 1); - const titleNotif = await client.waitForNotification(n => isActionNotification(n, 'session/titleChanged')); + // The first titleChanged is the immediate fallback (user message text). + // Wait for the agent-generated title which arrives second. + const titleNotif = await client.waitForNotification(n => { + if (!isActionNotification(n, 'session/titleChanged')) { + return false; + } + const action = getActionEnvelope(n).action as ITitleChangedAction; + return action.title === MOCK_AUTO_TITLE; + }); const titleAction = getActionEnvelope(titleNotif).action as ITitleChangedAction; assert.strictEqual(titleAction.title, MOCK_AUTO_TITLE); @@ -88,6 +96,31 @@ suite('Protocol WebSocket — Session Features', function () { assert.strictEqual(state.summary.title, MOCK_AUTO_TITLE); }); + test('first turn immediately sets title to user message', async function () { + this.timeout(10_000); + + const sessionUri = await createAndSubscribeSession(client, 'test-immediate-title'); + + // Verify the session starts with an empty title + const before = await client.call('subscribe', { resource: sessionUri }); + assert.strictEqual((before.snapshot.state as ISessionState).summary.title, ''); + + // Send first turn — side effects should dispatch an immediate titleChanged + // with the user's message text before the agent produces its own title. + dispatchTurnStarted(client, sessionUri, 'turn-immediate', 'Fix the login bug', 1); + + // The first titleChanged should carry the user message text + const titleNotif = await client.waitForNotification(n => isActionNotification(n, 'session/titleChanged')); + const titleAction = getActionEnvelope(titleNotif).action as ITitleChangedAction; + assert.strictEqual(titleAction.title, 'Fix the login bug'); + + // listSessions should also reflect the updated title + const result = await client.call('listSessions'); + const session = result.items.find(s => s.resource === sessionUri); + assert.ok(session, 'session should appear in listSessions'); + assert.strictEqual(session.title, 'Fix the login bug'); + }); + test('renamed session title persists across listSessions', async function () { this.timeout(10_000); diff --git a/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts b/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts index 394dd0c336c7e..3b6cc8f766a7c 100644 --- a/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts +++ b/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts @@ -95,7 +95,7 @@ class MockAgentService implements IAgentService { this._stateManager.createSession({ resource: session.toString(), provider: config?.provider ?? 'copilot', - title: 'New Session', + title: '', status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now(), diff --git a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts index 98af775076f1f..f2f475248fd42 100644 --- a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts @@ -280,10 +280,6 @@ export class LocalAgentHostSessionsProvider extends Disposable implements IAgent this._handleSessionAdded(n.summary); } else if (n.type === 'notify/sessionRemoved') { this._handleSessionRemoved(n.session); - } else if (n.type === 'notify/sessionSummaryChanged') { - if (n.changes.title !== undefined) { - this._handleTitleChanged(n.session, n.changes.title); - } } })); diff --git a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts index 3bc2751524d6b..e34942367070d 100644 --- a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts @@ -396,10 +396,6 @@ export class RemoteAgentHostSessionsProvider extends Disposable implements IAgen this._handleSessionAdded(n.summary); } else if (n.type === 'notify/sessionRemoved') { this._handleSessionRemoved(n.session); - } else if (n.type === 'notify/sessionSummaryChanged') { - if (n.changes.title !== undefined) { - this._handleTitleChanged(n.session, n.changes.title); - } } })); From 7e728f5b12a0f833afafb01afdbbabdd2d183fe7 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 15 Apr 2026 17:41:03 -0700 Subject: [PATCH 3/5] Address Copilot review: guard against clobbering existing title - Only dispatch immediate title when state.summary.title is empty, preventing overwrite of user-renamed or provider-set titles - Add test for the non-empty title guard - Fix misleading test comment (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agentHost/node/agentSideEffects.ts | 6 ++-- .../test/node/agentSideEffects.test.ts | 30 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/vs/platform/agentHost/node/agentSideEffects.ts b/src/vs/platform/agentHost/node/agentSideEffects.ts index 3215bc6cba116..aa715927a5763 100644 --- a/src/vs/platform/agentHost/node/agentSideEffects.ts +++ b/src/vs/platform/agentHost/node/agentSideEffects.ts @@ -558,10 +558,12 @@ export class AgentSideEffects extends Disposable { // On the very first turn, immediately set the session title to the // user's message so the UI shows a meaningful title right away - // while waiting for the AI-generated title. + // while waiting for the AI-generated title. Only apply when the + // title is still empty (the default) to avoid clobbering a title + // set by the user or provider before the first turn. const state = this._stateManager.getSessionState(action.session); const fallbackTitle = action.userMessage.text.trim(); - if (state && state.turns.length === 0 && fallbackTitle.length > 0) { + if (state && state.turns.length === 0 && !state.summary.title && fallbackTitle.length > 0) { this._stateManager.dispatchServerAction({ type: ActionType.SessionTitleChanged, session: action.session, diff --git a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts index 8bbd9e141e9e4..edb72edfa0ebb 100644 --- a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts +++ b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts @@ -187,8 +187,7 @@ suite('AgentSideEffects', () => { setupDefaultSession(); startTurn('turn-1'); - // After the first turn starts, the session has an active turn. - // Now change the title back to default to isolate the turns.length check. + // Complete the first turn so turns.length becomes 1. stateManager.dispatchServerAction({ type: ActionType.SessionTurnComplete, session: sessionUri.toString(), @@ -208,6 +207,33 @@ suite('AgentSideEffects', () => { const titleAction = envelopes.find(e => e.action.type === ActionType.SessionTitleChanged); assert.strictEqual(titleAction, undefined, 'should not dispatch titleChanged on second turn'); }); + + test('does not dispatch titleChanged when title is already set', () => { + // Session has a non-empty title (e.g. user renamed before first message) + stateManager.createSession({ + resource: sessionUri.toString(), + provider: 'mock', + title: 'User Renamed', + status: SessionStatus.Idle, + createdAt: Date.now(), + modifiedAt: Date.now(), + project: { uri: 'file:///test-project', displayName: 'Test Project' }, + }); + stateManager.dispatchServerAction({ type: ActionType.SessionReady, session: sessionUri.toString() }); + + const envelopes: IActionEnvelope[] = []; + disposables.add(stateManager.onDidEmitEnvelope(e => envelopes.push(e))); + + sideEffects.handleAction({ + type: ActionType.SessionTurnStarted, + session: sessionUri.toString(), + turnId: 'turn-1', + userMessage: { text: 'hello' }, + }); + + const titleAction = envelopes.find(e => e.action.type === ActionType.SessionTitleChanged); + assert.strictEqual(titleAction, undefined, 'should not clobber existing title'); + }); }); suite('handleAction — session/turnCancelled', () => { From d44251fdc5aabc04b453ac0ec205d118a8cb53c4 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 15 Apr 2026 17:58:02 -0700 Subject: [PATCH 4/5] Use DEFAULT_SESSION_TITLE constant instead of empty string Reverts from empty string to 'New Session' placeholder with a named constant (DEFAULT_SESSION_TITLE) to avoid blank titles in consumers using nullish coalescing (`??`) instead of logical OR (`||`). The side effects check now compares against the constant, and listSessions overlay skips the default title to avoid overwriting SDK-reported titles. Updated PR description to match final implementation (removed reference to provider-side sessionSummaryChanged handlers). (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/vs/platform/agentHost/common/agentService.ts | 3 +++ src/vs/platform/agentHost/node/agentService.ts | 13 +++++++------ src/vs/platform/agentHost/node/agentSideEffects.ts | 8 ++++---- .../agentHost/test/node/agentSideEffects.test.ts | 4 ++-- .../protocol/sessionFeatures.integrationTest.ts | 5 +++-- .../test/node/protocolServerHandler.test.ts | 4 ++-- 6 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/vs/platform/agentHost/common/agentService.ts b/src/vs/platform/agentHost/common/agentService.ts index ff2c26ab4f262..0d444afe1b368 100644 --- a/src/vs/platform/agentHost/common/agentService.ts +++ b/src/vs/platform/agentHost/common/agentService.ts @@ -35,6 +35,9 @@ export const AgentHostEnabledSettingId = 'chat.agentHost.enabled'; /** Configuration key that controls whether per-host IPC traffic output channels are created. */ export const AgentHostIpcLoggingSettingId = 'chat.agentHost.ipcLoggingEnabled'; +/** Default placeholder title assigned to newly created sessions. */ +export const DEFAULT_SESSION_TITLE = 'New Session'; + // ---- IPC data types (serializable across MessagePort) ----------------------- export interface IAgentSessionMetadata { diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index e694fb0293f2f..3ec1036dd17ab 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -11,7 +11,7 @@ import { URI } from '../../../base/common/uri.js'; import { generateUuid } from '../../../base/common/uuid.js'; import { FileSystemProviderErrorCode, IFileService, toFileSystemProviderErrorCode } from '../../files/common/files.js'; import { ILogService } from '../../log/common/log.js'; -import { AgentProvider, AgentSession, IAgent, IAgentCreateSessionConfig, IAgentMessageEvent, IAgentResolveSessionConfigParams, IAgentService, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IAgentSubagentStartedEvent, IAgentToolCompleteEvent, IAgentToolStartEvent, IAuthenticateParams, IAuthenticateResult } from '../common/agentService.js'; +import { AgentProvider, AgentSession, DEFAULT_SESSION_TITLE, IAgent, IAgentCreateSessionConfig, IAgentMessageEvent, IAgentResolveSessionConfigParams, IAgentService, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IAgentSubagentStartedEvent, IAgentToolCompleteEvent, IAgentToolStartEvent, IAuthenticateParams, IAuthenticateResult } from '../common/agentService.js'; import { ISessionDataService } from '../common/sessionDataService.js'; import { ActionType, IActionEnvelope, INotification, ISessionAction, ITerminalAction, isSessionAction } from '../common/state/sessionActions.js'; import type { ICreateTerminalParams, IResolveSessionConfigResult, ISessionConfigCompletionsResult } from '../common/state/protocol/commands.js'; @@ -177,15 +177,16 @@ export class AgentService extends Disposable implements IAgentService { })); // Overlay live session state from the state manager. - // For the title, prefer the state manager's value when it is - // non-empty, so SDK-sourced titles are not overwritten by the - // initial empty placeholder. + // For the title, prefer the state manager's value when it has + // been changed from the initial placeholder, so SDK-sourced + // titles are not overwritten by the default. const withStatus = result.map(s => { const liveState = this._stateManager.getSessionState(s.session.toString()); if (liveState) { + const liveTitle = liveState.summary.title; return { ...s, - summary: liveState.summary.title || s.summary, + summary: (liveTitle && liveTitle !== DEFAULT_SESSION_TITLE) ? liveTitle : s.summary, status: liveState.summary.status, model: liveState.summary.model ?? s.model, }; @@ -268,7 +269,7 @@ export class AgentService extends Disposable implements IAgentService { const summary: ISessionSummary = { resource: session.toString(), provider: provider.id, - title: '', + title: DEFAULT_SESSION_TITLE, status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now(), diff --git a/src/vs/platform/agentHost/node/agentSideEffects.ts b/src/vs/platform/agentHost/node/agentSideEffects.ts index aa715927a5763..9a0961770a2b7 100644 --- a/src/vs/platform/agentHost/node/agentSideEffects.ts +++ b/src/vs/platform/agentHost/node/agentSideEffects.ts @@ -12,7 +12,7 @@ import { hasKey } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { generateUuid } from '../../../base/common/uuid.js'; import { ILogService } from '../../log/common/log.js'; -import { IAgent, IAgentAttachment, IAgentProgressEvent } from '../common/agentService.js'; +import { DEFAULT_SESSION_TITLE, IAgent, IAgentAttachment, IAgentProgressEvent } from '../common/agentService.js'; import { IDiffComputeService } from '../common/diffComputeService.js'; import { ISessionDataService } from '../common/sessionDataService.js'; import { ActionType, ISessionAction } from '../common/state/sessionActions.js'; @@ -559,11 +559,11 @@ export class AgentSideEffects extends Disposable { // On the very first turn, immediately set the session title to the // user's message so the UI shows a meaningful title right away // while waiting for the AI-generated title. Only apply when the - // title is still empty (the default) to avoid clobbering a title - // set by the user or provider before the first turn. + // title is still the default placeholder to avoid clobbering a + // title set by the user or provider before the first turn. const state = this._stateManager.getSessionState(action.session); const fallbackTitle = action.userMessage.text.trim(); - if (state && state.turns.length === 0 && !state.summary.title && fallbackTitle.length > 0) { + if (state && state.turns.length === 0 && state.summary.title === DEFAULT_SESSION_TITLE && fallbackTitle.length > 0) { this._stateManager.dispatchServerAction({ type: ActionType.SessionTitleChanged, session: action.session, diff --git a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts index edb72edfa0ebb..8c62565e24514 100644 --- a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts +++ b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts @@ -14,7 +14,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/c import { FileService } from '../../../files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js'; import { NullLogService } from '../../../log/common/log.js'; -import { AgentSession, IAgent } from '../../common/agentService.js'; +import { AgentSession, DEFAULT_SESSION_TITLE, IAgent } from '../../common/agentService.js'; import { ISessionDataService } from '../../common/sessionDataService.js'; import { ActionType, IActionEnvelope, ISessionAction } from '../../common/state/sessionActions.js'; import { PendingMessageKind, ResponsePartKind, SessionStatus, ToolCallStatus, ToolResultContentType } from '../../common/state/sessionState.js'; @@ -137,7 +137,7 @@ suite('AgentSideEffects', () => { stateManager.createSession({ resource: sessionUri.toString(), provider: 'mock', - title: '', + title: DEFAULT_SESSION_TITLE, status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now(), diff --git a/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts b/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts index d8d890566f320..78ff89eefb7e5 100644 --- a/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts +++ b/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts @@ -5,6 +5,7 @@ import assert from 'assert'; import { timeout } from '../../../../../base/common/async.js'; +import { DEFAULT_SESSION_TITLE } from '../../../common/agentService.js'; import { ISubscribeResult } from '../../../common/state/protocol/commands.js'; import type { IModelChangedAction, IResponsePartAction, ISessionAddedNotification, ITitleChangedAction } from '../../../common/state/sessionActions.js'; import { PROTOCOL_VERSION } from '../../../common/state/sessionCapabilities.js'; @@ -101,9 +102,9 @@ suite('Protocol WebSocket — Session Features', function () { const sessionUri = await createAndSubscribeSession(client, 'test-immediate-title'); - // Verify the session starts with an empty title + // Verify the session starts with the default placeholder title const before = await client.call('subscribe', { resource: sessionUri }); - assert.strictEqual((before.snapshot.state as ISessionState).summary.title, ''); + assert.strictEqual((before.snapshot.state as ISessionState).summary.title, DEFAULT_SESSION_TITLE); // Send first turn — side effects should dispatch an immediate titleChanged // with the user's message text before the agent produces its own title. diff --git a/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts b/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts index 3b6cc8f766a7c..a278a5dea37e0 100644 --- a/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts +++ b/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts @@ -9,7 +9,7 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { NullLogService } from '../../../log/common/log.js'; -import type { IAgentCreateSessionConfig, IAgentResolveSessionConfigParams, IAgentService, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IAuthenticateParams, IAuthenticateResult } from '../../common/agentService.js'; +import { DEFAULT_SESSION_TITLE, type IAgentCreateSessionConfig, type IAgentResolveSessionConfigParams, type IAgentService, type IAgentSessionConfigCompletionsParams, type IAgentSessionMetadata, type IAuthenticateParams, type IAuthenticateResult } from '../../common/agentService.js'; import { IListSessionsResult, IResourceReadResult, IResolveSessionConfigResult, ISessionConfigCompletionsResult } from '../../common/state/protocol/commands.js'; import { ActionType, type ISessionAction } from '../../common/state/sessionActions.js'; import { PROTOCOL_VERSION } from '../../common/state/sessionCapabilities.js'; @@ -95,7 +95,7 @@ class MockAgentService implements IAgentService { this._stateManager.createSession({ resource: session.toString(), provider: config?.provider ?? 'copilot', - title: '', + title: DEFAULT_SESSION_TITLE, status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now(), From cf3f48480e42d3f4ce3deaff278f0673ec2aa62f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Wed, 15 Apr 2026 18:23:44 -0700 Subject: [PATCH 5/5] Use empty string initial title, fix ?? consumers to use || Reverts to empty string as the initial session title for simplicity. Fixes downstream consumers that used nullish coalescing (??) to use logical OR (||) so empty strings correctly fall through to the fallback label. Also normalizes whitespace and truncates the fallback title to 200 characters. Affected consumers: - agentHostSessionListController.ts - localAgentHostSessionsProvider.ts - remoteAgentHostSessionsProvider.ts (Written by Copilot) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../platform/agentHost/common/agentService.ts | 3 -- .../platform/agentHost/node/agentService.ts | 13 ++++----- .../agentHost/node/agentSideEffects.ts | 6 ++-- .../test/node/agentSideEffects.test.ts | 28 +++++++++++++++++-- .../sessionFeatures.integrationTest.ts | 3 +- .../test/node/protocolServerHandler.test.ts | 4 +-- .../browser/localAgentHostSessionsProvider.ts | 2 +- .../remoteAgentHostSessionsProvider.ts | 4 +-- .../agentHostSessionListController.ts | 2 +- 9 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/vs/platform/agentHost/common/agentService.ts b/src/vs/platform/agentHost/common/agentService.ts index a86592224ff75..cdfa041a1ea4f 100644 --- a/src/vs/platform/agentHost/common/agentService.ts +++ b/src/vs/platform/agentHost/common/agentService.ts @@ -35,9 +35,6 @@ export const AgentHostEnabledSettingId = 'chat.agentHost.enabled'; /** Configuration key that controls whether per-host IPC traffic output channels are created. */ export const AgentHostIpcLoggingSettingId = 'chat.agentHost.ipcLoggingEnabled'; -/** Default placeholder title assigned to newly created sessions. */ -export const DEFAULT_SESSION_TITLE = 'New Session'; - // ---- IPC data types (serializable across MessagePort) ----------------------- export interface IAgentSessionMetadata { diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index a7126d28efa92..9a8bf2ef1d21b 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -11,7 +11,7 @@ import { URI } from '../../../base/common/uri.js'; import { generateUuid } from '../../../base/common/uuid.js'; import { FileSystemProviderErrorCode, IFileService, toFileSystemProviderErrorCode } from '../../files/common/files.js'; import { ILogService } from '../../log/common/log.js'; -import { AgentProvider, AgentSession, DEFAULT_SESSION_TITLE, IAgent, IAgentCreateSessionConfig, IAgentMessageEvent, IAgentResolveSessionConfigParams, IAgentService, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IAgentSubagentStartedEvent, IAgentToolCompleteEvent, IAgentToolStartEvent, IAuthenticateParams, IAuthenticateResult } from '../common/agentService.js'; +import { AgentProvider, AgentSession, IAgent, IAgentCreateSessionConfig, IAgentMessageEvent, IAgentResolveSessionConfigParams, IAgentService, IAgentSessionConfigCompletionsParams, IAgentSessionMetadata, IAgentSubagentStartedEvent, IAgentToolCompleteEvent, IAgentToolStartEvent, IAuthenticateParams, IAuthenticateResult } from '../common/agentService.js'; import { ISessionDataService } from '../common/sessionDataService.js'; import { ActionType, IActionEnvelope, INotification, ISessionAction, ITerminalAction, isSessionAction } from '../common/state/sessionActions.js'; import type { ICreateTerminalParams, IResolveSessionConfigResult, ISessionConfigCompletionsResult } from '../common/state/protocol/commands.js'; @@ -179,16 +179,15 @@ export class AgentService extends Disposable implements IAgentService { })); // Overlay live session state from the state manager. - // For the title, prefer the state manager's value when it has - // been changed from the initial placeholder, so SDK-sourced - // titles are not overwritten by the default. + // For the title, prefer the state manager's value when it is + // non-empty, so SDK-sourced titles are not overwritten by the + // initial empty placeholder. const withStatus = result.map(s => { const liveState = this._stateManager.getSessionState(s.session.toString()); if (liveState) { - const liveTitle = liveState.summary.title; return { ...s, - summary: (liveTitle && liveTitle !== DEFAULT_SESSION_TITLE) ? liveTitle : s.summary, + summary: liveState.summary.title || s.summary, status: liveState.summary.status, model: liveState.summary.model ?? s.model, }; @@ -271,7 +270,7 @@ export class AgentService extends Disposable implements IAgentService { const summary: ISessionSummary = { resource: session.toString(), provider: provider.id, - title: DEFAULT_SESSION_TITLE, + title: '', status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now(), diff --git a/src/vs/platform/agentHost/node/agentSideEffects.ts b/src/vs/platform/agentHost/node/agentSideEffects.ts index 39229fd101403..4d5102c187997 100644 --- a/src/vs/platform/agentHost/node/agentSideEffects.ts +++ b/src/vs/platform/agentHost/node/agentSideEffects.ts @@ -12,7 +12,7 @@ import { hasKey } from '../../../base/common/types.js'; import { URI } from '../../../base/common/uri.js'; import { generateUuid } from '../../../base/common/uuid.js'; import { ILogService } from '../../log/common/log.js'; -import { DEFAULT_SESSION_TITLE, IAgent, IAgentAttachment, IAgentProgressEvent, type IAgentToolReadyEvent } from '../common/agentService.js'; +import { IAgent, IAgentAttachment, IAgentProgressEvent, type IAgentToolReadyEvent } from '../common/agentService.js'; import { IDiffComputeService } from '../common/diffComputeService.js'; import { ISessionDataService } from '../common/sessionDataService.js'; import { ActionType, ISessionAction } from '../common/state/sessionActions.js'; @@ -575,8 +575,8 @@ export class AgentSideEffects extends Disposable { // title is still the default placeholder to avoid clobbering a // title set by the user or provider before the first turn. const state = this._stateManager.getSessionState(action.session); - const fallbackTitle = action.userMessage.text.trim(); - if (state && state.turns.length === 0 && state.summary.title === DEFAULT_SESSION_TITLE && fallbackTitle.length > 0) { + const fallbackTitle = action.userMessage.text.trim().replace(/\s+/g, ' ').slice(0, 200); + if (state && state.turns.length === 0 && !state.summary.title && fallbackTitle.length > 0) { this._stateManager.dispatchServerAction({ type: ActionType.SessionTitleChanged, session: action.session, diff --git a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts index 552455b655369..f66b5605dd2e4 100644 --- a/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts +++ b/src/vs/platform/agentHost/test/node/agentSideEffects.test.ts @@ -14,7 +14,7 @@ import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/c import { FileService } from '../../../files/common/fileService.js'; import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js'; import { NullLogService } from '../../../log/common/log.js'; -import { AgentSession, DEFAULT_SESSION_TITLE, IAgent } from '../../common/agentService.js'; +import { AgentSession, IAgent } from '../../common/agentService.js'; import { ISessionDataService } from '../../common/sessionDataService.js'; import { ActionType, IActionEnvelope, ISessionAction } from '../../common/state/sessionActions.js'; import { PendingMessageKind, ResponsePartKind, SessionStatus, ToolCallStatus, ToolResultContentType } from '../../common/state/sessionState.js'; @@ -138,7 +138,7 @@ suite('AgentSideEffects', () => { stateManager.createSession({ resource: sessionUri.toString(), provider: 'mock', - title: DEFAULT_SESSION_TITLE, + title: '', status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now(), @@ -184,6 +184,30 @@ suite('AgentSideEffects', () => { assert.strictEqual(titleAction, undefined, 'should not dispatch titleChanged for empty message'); }); + test('normalizes whitespace and truncates long messages', () => { + setupDefaultSession(); + + const envelopes: IActionEnvelope[] = []; + disposables.add(stateManager.onDidEmitEnvelope(e => envelopes.push(e))); + + const longMessage = 'Fix the bug\nin the login\tpage please ' + 'a'.repeat(250); + sideEffects.handleAction({ + type: ActionType.SessionTurnStarted, + session: sessionUri.toString(), + turnId: 'turn-1', + userMessage: { text: longMessage }, + }); + + const titleAction = envelopes.find(e => e.action.type === ActionType.SessionTitleChanged); + assert.ok(titleAction, 'should dispatch session/titleChanged'); + if (titleAction?.action.type === ActionType.SessionTitleChanged) { + assert.ok(!titleAction.action.title.includes('\n'), 'should not contain newlines'); + assert.ok(!titleAction.action.title.includes('\t'), 'should not contain tabs'); + assert.ok(!titleAction.action.title.includes(' '), 'should not contain double spaces'); + assert.ok(titleAction.action.title.length <= 200, 'should be truncated to 200 chars'); + } + }); + test('does not dispatch titleChanged on second turn', () => { setupDefaultSession(); startTurn('turn-1'); diff --git a/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts b/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts index 78ff89eefb7e5..cd6766fcf074a 100644 --- a/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts +++ b/src/vs/platform/agentHost/test/node/protocol/sessionFeatures.integrationTest.ts @@ -5,7 +5,6 @@ import assert from 'assert'; import { timeout } from '../../../../../base/common/async.js'; -import { DEFAULT_SESSION_TITLE } from '../../../common/agentService.js'; import { ISubscribeResult } from '../../../common/state/protocol/commands.js'; import type { IModelChangedAction, IResponsePartAction, ISessionAddedNotification, ITitleChangedAction } from '../../../common/state/sessionActions.js'; import { PROTOCOL_VERSION } from '../../../common/state/sessionCapabilities.js'; @@ -104,7 +103,7 @@ suite('Protocol WebSocket — Session Features', function () { // Verify the session starts with the default placeholder title const before = await client.call('subscribe', { resource: sessionUri }); - assert.strictEqual((before.snapshot.state as ISessionState).summary.title, DEFAULT_SESSION_TITLE); + assert.strictEqual((before.snapshot.state as ISessionState).summary.title, ''); // Send first turn — side effects should dispatch an immediate titleChanged // with the user's message text before the agent produces its own title. diff --git a/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts b/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts index 1c2fd3b2cbdb4..0ceb1291a2555 100644 --- a/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts +++ b/src/vs/platform/agentHost/test/node/protocolServerHandler.test.ts @@ -9,7 +9,7 @@ import { DisposableStore } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js'; import { NullLogService } from '../../../log/common/log.js'; -import { DEFAULT_SESSION_TITLE, type IAgentCreateSessionConfig, type IAgentResolveSessionConfigParams, type IAgentService, type IAgentSessionConfigCompletionsParams, type IAgentSessionMetadata, type IAuthenticateParams, type IAuthenticateResult } from '../../common/agentService.js'; +import { type IAgentCreateSessionConfig, type IAgentResolveSessionConfigParams, type IAgentService, type IAgentSessionConfigCompletionsParams, type IAgentSessionMetadata, type IAuthenticateParams, type IAuthenticateResult } from '../../common/agentService.js'; import { IListSessionsResult, IResourceReadResult, IResolveSessionConfigResult, ISessionConfigCompletionsResult } from '../../common/state/protocol/commands.js'; import { ActionType, type ISessionAction } from '../../common/state/sessionActions.js'; import { PROTOCOL_VERSION } from '../../common/state/sessionCapabilities.js'; @@ -95,7 +95,7 @@ class MockAgentService implements IAgentService { this._stateManager.createSession({ resource: session.toString(), provider: config?.provider ?? 'copilot', - title: DEFAULT_SESSION_TITLE, + title: '', status: SessionStatus.Idle, createdAt: Date.now(), modifiedAt: Date.now(), diff --git a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts index cc2bb72787648..4c40f4342f08a 100644 --- a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts @@ -112,7 +112,7 @@ class LocalSessionAdapter implements ISession { this.providerId = providerId; this.sessionType = logicalSessionType; this.createdAt = new Date(metadata.startTime); - this.title = observableValue('title', metadata.summary ?? `Session ${rawId.substring(0, 8)}`); + this.title = observableValue('title', metadata.summary || `Session ${rawId.substring(0, 8)}`); this.updatedAt = observableValue('updatedAt', new Date(metadata.modifiedTime)); this.modelId = observableValue('modelId', metadata.model ? `${logicalSessionType}:${metadata.model}` : undefined); this.lastTurnEnd = observableValue('lastTurnEnd', metadata.modifiedTime ? new Date(metadata.modifiedTime) : undefined); diff --git a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts index 73e49827b7485..2983bca3276b9 100644 --- a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts @@ -195,7 +195,7 @@ class RemoteSessionAdapter implements IChatData { this.providerId = providerId; this.sessionType = logicalSessionType; this.createdAt = new Date(metadata.startTime); - this.title = observableValue('title', metadata.summary ?? `Session ${rawId.substring(0, 8)}`); + this.title = observableValue('title', metadata.summary || `Session ${rawId.substring(0, 8)}`); this.updatedAt = observableValue('updatedAt', new Date(metadata.modifiedTime)); this.modelId = observableValue('modelId', metadata.model ? `${resourceScheme}:${metadata.model}` : undefined); this.lastTurnEnd = observableValue('lastTurnEnd', metadata.modifiedTime ? new Date(metadata.modifiedTime) : undefined); @@ -214,7 +214,7 @@ class RemoteSessionAdapter implements IChatData { } update(metadata: IAgentSessionMetadata): void { - this.title.set(metadata.summary ?? this.title.get(), undefined); + this.title.set(metadata.summary || this.title.get(), undefined); this.updatedAt.set(new Date(metadata.modifiedTime), undefined); this.lastTurnEnd.set(metadata.modifiedTime ? new Date(metadata.modifiedTime) : undefined, undefined); if (metadata.isRead !== undefined) { diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts index e0da46526e8e8..f3411eb837080 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionListController.ts @@ -171,7 +171,7 @@ export class AgentHostSessionListController extends Disposable implements IChatS }): IChatSessionItem { return { resource: URI.from({ scheme: this._sessionType, path: `/${rawId}` }), - label: opts.title ?? `Session ${rawId.substring(0, 8)}`, + label: opts.title || `Session ${rawId.substring(0, 8)}`, description: this._description, iconPath: getAgentHostIcon(this._productService), status: mapSessionStatus(opts.status),