diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index ad86d3eb212..4aaba46c691 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -146,7 +146,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA })); this._register(this._chatService.onDidDisposeSession(e => { - for (const resource of e.sessionResource) { + for (const resource of e.sessionResources) { this._proxy.$releaseSession(resource); } })); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/localAgentSessionsController.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/localAgentSessionsController.ts index a077f22635f..a1e11d322d3 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/localAgentSessionsController.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/localAgentSessionsController.ts @@ -7,16 +7,17 @@ import { coalesce } from '../../../../../base/common/arrays.js'; import { CancellationToken } from '../../../../../base/common/cancellation.js'; import { Codicon } from '../../../../../base/common/codicons.js'; import { Emitter } from '../../../../../base/common/event.js'; -import { Disposable } from '../../../../../base/common/lifecycle.js'; -import { ResourceSet } from '../../../../../base/common/map.js'; -import { Schemas } from '../../../../../base/common/network.js'; +import { Disposable, DisposableResourceMap } from '../../../../../base/common/lifecycle.js'; +import { ResourceMap, ResourceSet } from '../../../../../base/common/map.js'; +import { equals } from '../../../../../base/common/objects.js'; +import { autorun, observableSignalFromEvent } from '../../../../../base/common/observable.js'; import { isEqual } from '../../../../../base/common/resources.js'; import { URI } from '../../../../../base/common/uri.js'; -import { ILogService } from '../../../../../platform/log/common/log.js'; import { IWorkbenchContribution } from '../../../../common/contributions.js'; -import { convertLegacyChatSessionTiming, IChatDetail, IChatService, ResponseModelState } from '../../common/chatService/chatService.js'; +import { convertLegacyChatSessionTiming, IChatDetail, IChatService, IChatSessionTiming, ResponseModelState } from '../../common/chatService/chatService.js'; +import { chatModelToChatDetail } from '../../common/chatService/chatServiceImpl.js'; import { ChatSessionStatus, IChatSessionItem, IChatSessionItemController, IChatSessionItemsDelta, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js'; -import { IChatModel } from '../../common/model/chatModel.js'; +import { ChatModel, IChatModel } from '../../common/model/chatModel.js'; import { getChatSessionType } from '../../common/model/chatUri.js'; import { getInProgressSessionDescription } from '../chatSessions/chatSessionDescription.js'; @@ -26,16 +27,14 @@ export class LocalAgentsSessionsController extends Disposable implements IChatSe readonly chatSessionType = localChatSessionType; - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange = this._onDidChange.event; - readonly _onDidChangeChatSessionItems = this._register(new Emitter()); readonly onDidChangeChatSessionItems = this._onDidChangeChatSessionItems.event; + private readonly _modelListeners = this._register(new DisposableResourceMap()); + constructor( @IChatService private readonly chatService: IChatService, @IChatSessionsService private readonly chatSessionsService: IChatSessionsService, - @ILogService private readonly logService: ILogService, ) { super(); @@ -44,44 +43,81 @@ export class LocalAgentsSessionsController extends Disposable implements IChatSe this.registerListeners(); } - private _items: IChatSessionItem[] = []; + private _items = new ResourceMap(); get items(): readonly IChatSessionItem[] { - return this._items; + return Array.from(this._items.values()); } async refresh(token: CancellationToken): Promise { - this._items = await this.provideChatSessionItems(token); + const newItems = await this.provideChatSessionItems(token); + + this._items.clear(); + for (const item of newItems) { + this._items.set(item.resource, item); + } } private registerListeners(): void { - this._register(this.chatService.registerChatModelChangeListeners(Schemas.vscodeLocalChatSession, async sessionResource => { - if (getChatSessionType(sessionResource) !== this.chatSessionType) { + const tryAddModelListeners = async (model: IChatModel) => { + if (getChatSessionType(model.sessionResource) !== this.chatSessionType) { return; } - // TODO: This gets fired too often - await this.refresh(CancellationToken.None); - const item = this.getItem(sessionResource); + const onChange = () => { + this.tryUpdateLiveSessionItem(model); + }; - if (item) { - this._onDidChangeChatSessionItems.fire({ addedOrUpdated: [item] }); - } - })); + await this.refresh(CancellationToken.None); + onChange(); + + const requestChangeListener = model.lastRequestObs.map(last => last?.response && observableSignalFromEvent('chatSessions.modelRequestChangeListener', last.response.onDidChange)); + const modelChangeListener = observableSignalFromEvent('chatSessions.modelChangeListener', model.onDidChange); + this._modelListeners.set(model.sessionResource, autorun(reader => { + requestChangeListener.read(reader)?.read(reader); + modelChangeListener.read(reader); + + onChange(); + })); + }; + + this._register(this.chatService.onDidCreateModel(model => tryAddModelListeners(model))); + for (const model of this.chatService.chatModels.get()) { + tryAddModelListeners(model); + } this._register(this.chatService.onDidDisposeSession(e => { - const removedSessionResources = e.sessionResource.filter(resource => getChatSessionType(resource) === this.chatSessionType); + for (const sessionResource of e.sessionResources) { + this._modelListeners.deleteAndDispose(sessionResource); + } + + const removedSessionResources = e.sessionResources.filter(resource => getChatSessionType(resource) === this.chatSessionType); if (removedSessionResources.length) { this._onDidChangeChatSessionItems.fire({ removed: removedSessionResources }); } })); } - private getItem(sessionResource: URI): IChatSessionItem | undefined { - return this._items.find(item => isEqual(item.resource, sessionResource)); + private async tryUpdateLiveSessionItem(model: IChatModel): Promise { + if (!(model instanceof ChatModel)) { + return; + } + + const existing = this._items.get(model.sessionResource); + if (!existing) { + return; + } + + const updated = new LocalChatSessionItem(await chatModelToChatDetail(model), model); + if (existing.isEqual(updated)) { + return; + } + + this._items.set(existing.resource, updated); + this._onDidChangeChatSessionItems.fire({ addedOrUpdated: [updated] }); } - private async provideChatSessionItems(token: CancellationToken): Promise { - const sessions: IChatSessionItem[] = []; + private async provideChatSessionItems(token: CancellationToken): Promise { + const sessions: LocalChatSessionItem[] = []; const sessionsByResource = new ResourceSet(); for (const sessionDetail of await this.chatService.getLiveSessionItems()) { @@ -102,7 +138,7 @@ export class LocalAgentsSessionsController extends Disposable implements IChatSe return sessions; } - private async getHistoryItems(): Promise { + private async getHistoryItems(): Promise { try { const historyItems = await this.chatService.getHistorySessionItems(); @@ -112,72 +148,90 @@ export class LocalAgentsSessionsController extends Disposable implements IChatSe } } - private toChatSessionItem(chat: IChatDetail): IChatSessionItem | undefined { + private toChatSessionItem(chat: IChatDetail): LocalChatSessionItem | undefined { const model = this.chatService.getSession(chat.sessionResource); - let description: string | undefined; if (model) { if (!model.hasRequests) { return undefined; // ignore sessions without requests } - - description = getInProgressSessionDescription(model); } else if (chat.isActive) { // Sessions that are active but don't have a chat model are ultimately untitled with no requests return undefined; } - return { - resource: chat.sessionResource, - label: chat.title, - description, - status: model ? this.modelToStatus(model) : this.chatResponseStateToStatus(chat.lastResponseState), - iconPath: Codicon.chatSparkle, - timing: convertLegacyChatSessionTiming(chat.timing), - changes: chat.stats ? { - insertions: chat.stats.added, - deletions: chat.stats.removed, - files: chat.stats.fileCount, - } : undefined - }; - } - - private modelToStatus(model: IChatModel): ChatSessionStatus | undefined { - if (model.requestInProgress.get()) { - this.logService.trace(`[agent sessions] Session ${model.sessionResource.toString()} request is in progress.`); - return ChatSessionStatus.InProgress; - } - - const lastRequest = model.getRequests().at(-1); - this.logService.trace(`[agent sessions] Session ${model.sessionResource.toString()} last request response: state ${lastRequest?.response?.state}, isComplete ${lastRequest?.response?.isComplete}, isCanceled ${lastRequest?.response?.isCanceled}, error: ${lastRequest?.response?.result?.errorDetails?.message}.`); - if (lastRequest?.response) { - if (lastRequest.response.state === ResponseModelState.NeedsInput) { - return ChatSessionStatus.NeedsInput; - } else if (lastRequest.response.isCanceled || lastRequest.response.result?.errorDetails?.code === 'canceled') { - return ChatSessionStatus.Completed; - } else if (lastRequest.response.result?.errorDetails) { - return ChatSessionStatus.Failed; - } else if (lastRequest.response.isComplete) { - return ChatSessionStatus.Completed; - } else { - return ChatSessionStatus.InProgress; - } - } - - return undefined; - } - - private chatResponseStateToStatus(state: ResponseModelState): ChatSessionStatus { - switch (state) { - case ResponseModelState.Cancelled: - case ResponseModelState.Complete: - return ChatSessionStatus.Completed; - case ResponseModelState.Failed: - return ChatSessionStatus.Failed; - case ResponseModelState.Pending: - return ChatSessionStatus.InProgress; - case ResponseModelState.NeedsInput: - return ChatSessionStatus.NeedsInput; - } + return new LocalChatSessionItem(chat, model); + } +} + +class LocalChatSessionItem implements IChatSessionItem { + readonly resource: URI; + readonly iconPath = Codicon.chatSparkle; + + readonly label: string; + readonly description: string | undefined; + readonly status: ChatSessionStatus | undefined; + readonly timing: IChatSessionTiming; + readonly changes: IChatSessionItem['changes']; + + constructor(chatDetail: IChatDetail, model: IChatModel | undefined) { + this.resource = chatDetail.sessionResource; + this.label = chatDetail.title; + this.description = model ? getInProgressSessionDescription(model) : undefined; + this.status = (model && getSessionStatusForModel(model)) ?? chatResponseStateToSessionStatus(chatDetail.lastResponseState); + this.timing = convertLegacyChatSessionTiming(chatDetail.timing); + this.changes = chatDetail.stats ? { + insertions: chatDetail.stats.added, + deletions: chatDetail.stats.removed, + files: chatDetail.stats.fileCount, + } : undefined; + } + + isEqual(other: LocalChatSessionItem): boolean { + return isEqual(this.resource, other.resource) + && this.label === other.label + && this.description === other.description + && this.status === other.status + && this.timing.created === other.timing.created + && this.timing.lastRequestStarted === other.timing.lastRequestStarted + && this.timing.lastRequestEnded === other.timing.lastRequestEnded + && equals(this.changes, other.changes); + } +} + +function getSessionStatusForModel(model: IChatModel): ChatSessionStatus | undefined { + if (model.requestInProgress.get()) { + return ChatSessionStatus.InProgress; + } + + const lastRequest = model.getRequests().at(-1); + if (lastRequest?.response) { + if (lastRequest.response.state === ResponseModelState.NeedsInput) { + return ChatSessionStatus.NeedsInput; + } else if (lastRequest.response.isCanceled || lastRequest.response.result?.errorDetails?.code === 'canceled') { + return ChatSessionStatus.Completed; + } else if (lastRequest.response.result?.errorDetails) { + return ChatSessionStatus.Failed; + } else if (lastRequest.response.isComplete) { + return ChatSessionStatus.Completed; + } else { + return ChatSessionStatus.InProgress; + } + } + + return undefined; +} + +function chatResponseStateToSessionStatus(state: ResponseModelState): ChatSessionStatus { + switch (state) { + case ResponseModelState.Cancelled: + case ResponseModelState.Complete: + return ChatSessionStatus.Completed; + case ResponseModelState.Failed: + return ChatSessionStatus.Failed; + case ResponseModelState.Pending: + return ChatSessionStatus.InProgress; + case ResponseModelState.NeedsInput: + return ChatSessionStatus.NeedsInput; } } diff --git a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts index 11a6ce23b97..a8d49698b6e 100644 --- a/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingServiceImpl.ts @@ -87,7 +87,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic this._register(this._chatService.onDidDisposeSession((e) => { if (e.reason === 'cleared') { - for (const resource of e.sessionResource) { + for (const resource of e.sessionResources) { this.getEditingSession(resource)?.stop(); } } diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts index 26426bb3213..c07d363fcee 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatService.ts @@ -1525,7 +1525,7 @@ export interface IChatService { readonly onDidReceiveQuestionCarouselAnswer: Event<{ requestId: string; resolveId: string; answers: IChatQuestionAnswers | undefined }>; notifyQuestionCarouselAnswer(requestId: string, resolveId: string, answers: IChatQuestionAnswers | undefined): void; - readonly onDidDisposeSession: Event<{ readonly sessionResource: readonly URI[]; readonly reason: 'cleared' }>; + readonly onDidDisposeSession: Event<{ readonly sessionResources: readonly URI[]; readonly reason: 'cleared' }>; transferChatSession(transferredSessionResource: URI, toWorkspace: URI): Promise; diff --git a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts index 3581db7b410..e5b4ab2afe3 100644 --- a/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatService/chatServiceImpl.ts @@ -126,7 +126,7 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidReceiveQuestionCarouselAnswer = this._register(new Emitter<{ requestId: string; resolveId: string; answers: IChatQuestionAnswers | undefined }>()); public readonly onDidReceiveQuestionCarouselAnswer = this._onDidReceiveQuestionCarouselAnswer.event; - private readonly _onDidDisposeSession = this._register(new Emitter<{ readonly sessionResource: URI[]; reason: 'cleared' }>()); + private readonly _onDidDisposeSession = this._register(new Emitter<{ readonly sessionResources: URI[]; reason: 'cleared' }>()); public readonly onDidDisposeSession = this._onDidDisposeSession.event; private readonly _sessionFollowupCancelTokens = this._register(new DisposableResourceMap()); @@ -195,7 +195,7 @@ export class ChatService extends Disposable implements IChatService { this._register(this._sessionModels.onDidDisposeModel(model => { clearChatMarks(model.sessionResource); this.chatDebugService.endSession(model.sessionResource); - this._onDidDisposeSession.fire({ sessionResource: [model.sessionResource], reason: 'cleared' }); + this._onDidDisposeSession.fire({ sessionResources: [model.sessionResource], reason: 'cleared' }); })); this._chatServiceTelemetry = this.instantiationService.createInstance(ChatServiceTelemetry); @@ -400,18 +400,7 @@ export class ChatService extends Disposable implements IChatService { async getLiveSessionItems(): Promise { return await Promise.all(Array.from(this._sessionModels.values()) .filter(session => this.shouldBeInHistory(session)) - .map(async (session): Promise => { - const title = session.title || localize('newChat', "New Chat"); - return { - sessionResource: session.sessionResource, - title, - lastMessageDate: session.lastMessageDate, - timing: session.timing, - isActive: true, - stats: await awaitStatsForSession(session), - lastResponseState: session.lastRequest?.response?.state ?? ResponseModelState.Pending, - }; - })); + .map(chatModelToChatDetail)); } /** @@ -452,7 +441,7 @@ export class ChatService extends Disposable implements IChatService { async removeHistoryEntry(sessionResource: URI): Promise { await this._chatSessionStore.deleteSession(this.toLocalSessionId(sessionResource)); - this._onDidDisposeSession.fire({ sessionResource: [sessionResource], reason: 'cleared' }); + this._onDidDisposeSession.fire({ sessionResources: [sessionResource], reason: 'cleared' }); } async clearAllHistoryEntries(): Promise { @@ -1881,3 +1870,16 @@ export class ChatService extends Disposable implements IChatService { return disposableStore; } } + +export async function chatModelToChatDetail(model: ChatModel): Promise { + const title = model.title || localize('newChat', "New Chat"); + return { + sessionResource: model.sessionResource, + title, + lastMessageDate: model.lastMessageDate, + timing: model.timing, + isActive: true, + stats: await awaitStatsForSession(model), + lastResponseState: model.lastRequest?.response?.state ?? ResponseModelState.Pending, + }; +} diff --git a/src/vs/workbench/contrib/chat/common/widget/chatResponseResourceFileSystemProvider.ts b/src/vs/workbench/contrib/chat/common/widget/chatResponseResourceFileSystemProvider.ts index 6cab24df2d6..acc15dcca1e 100644 --- a/src/vs/workbench/contrib/chat/common/widget/chatResponseResourceFileSystemProvider.ts +++ b/src/vs/workbench/contrib/chat/common/widget/chatResponseResourceFileSystemProvider.ts @@ -61,7 +61,7 @@ export class ChatResponseResourceFileSystemProvider extends Disposable implement ) { super(); this._register(this.chatService.onDidDisposeSession(e => { - for (const sessionResource of e.sessionResource) { + for (const sessionResource of e.sessionResources) { const uris = this._sessionAssociations.get(sessionResource); if (uris) { for (const uri of uris) { diff --git a/src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts index c4dacb71de5..a87d57bc6f2 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService/chatService.test.ts @@ -426,7 +426,7 @@ suite('ChatService', () => { let disposed = false; testDisposables.add(testService.onDidDisposeSession(e => { - for (const resource of e.sessionResource) { + for (const resource of e.sessionResources) { if (resource.toString() === model.sessionResource.toString()) { disposed = true; } diff --git a/src/vs/workbench/contrib/chat/test/common/chatService/mockChatService.ts b/src/vs/workbench/contrib/chat/test/common/chatService/mockChatService.ts index 52c097fac48..3d57e64e0a7 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService/mockChatService.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService/mockChatService.ts @@ -27,11 +27,11 @@ export class MockChatService implements IChatService { private liveSessionItems: IChatDetail[] = []; private historySessionItems: IChatDetail[] = []; - private readonly _onDidDisposeSession = new Emitter<{ sessionResource: URI[]; reason: 'cleared' }>(); + private readonly _onDidDisposeSession = new Emitter<{ sessionResources: URI[]; reason: 'cleared' }>(); readonly onDidDisposeSession = this._onDidDisposeSession.event; - fireDidDisposeSession(sessionResource: URI[]): void { - this._onDidDisposeSession.fire({ sessionResource, reason: 'cleared' }); + fireDidDisposeSession(sessionResources: URI[]): void { + this._onDidDisposeSession.fire({ sessionResources, reason: 'cleared' }); } setSaveModelsEnabled(enabled: boolean): void { diff --git a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts index acb2ca13eaa..dba3e6271f2 100644 --- a/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts +++ b/src/vs/workbench/contrib/terminalContrib/chat/browser/terminalChatService.ts @@ -81,7 +81,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ // Clear session auto-approve rules when chat sessions end this._register(this._chatService.onDidDisposeSession(e => { - for (const resource of e.sessionResource) { + for (const resource of e.sessionResources) { this._sessionAutoApproveRules.delete(resource); this._sessionAutoApprovalEnabled.delete(resource); } @@ -105,7 +105,7 @@ export class TerminalChatService extends Disposable implements ITerminalChatServ })); this._register(this._chatService.onDidDisposeSession(e => { - for (const resource of e.sessionResource) { + for (const resource of e.sessionResources) { if (LocalChatSessionUri.parseLocalSessionId(resource) === terminalToolSessionId) { this._terminalInstancesByToolSessionId.delete(terminalToolSessionId); this._toolSessionIdByTerminalInstance.delete(instance); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts index c6024ac397e..3a5188955dc 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/browser/tools/runInTerminalTool.ts @@ -512,7 +512,7 @@ export class RunInTerminalTool extends Disposable implements IToolImpl { // Listen for chat session disposal to clean up associated terminals this._register(this._chatService.onDidDisposeSession(e => { - for (const resource of e.sessionResource) { + for (const resource of e.sessionResources) { this._cleanupSessionTerminals(resource); } })); diff --git a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts index 77639dedd5c..ef174e97fc9 100644 --- a/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/chatAgentTools/test/electron-browser/runInTerminalTool.test.ts @@ -75,7 +75,7 @@ suite('RunInTerminalTool', () => { let storageService: IStorageService; let workspaceContextService: TestContextService; let terminalServiceDisposeEmitter: Emitter; - let chatServiceDisposeEmitter: Emitter<{ sessionResource: URI[]; reason: 'cleared' }>; + let chatServiceDisposeEmitter: Emitter<{ sessionResources: URI[]; reason: 'cleared' }>; let chatSessionArchivedEmitter: Emitter; let sandboxEnabled: boolean; let terminalSandboxService: ITerminalSandboxService; @@ -95,7 +95,7 @@ suite('RunInTerminalTool', () => { setConfig(TerminalChatAgentToolsSettingId.BlockDetectedFileWrites, 'outsideWorkspace'); sandboxEnabled = false; terminalServiceDisposeEmitter = new Emitter(); - chatServiceDisposeEmitter = new Emitter<{ sessionResource: URI[]; reason: 'cleared' }>(); + chatServiceDisposeEmitter = new Emitter<{ sessionResources: URI[]; reason: 'cleared' }>(); chatSessionArchivedEmitter = new Emitter(); instantiationService = workbenchInstantiationService({ @@ -1521,7 +1521,7 @@ suite('RunInTerminalTool', () => { }); runInTerminalTool.sessionTerminalInstances.set(sessionResource, new Set([mockTerminal1, mockTerminal2])); - chatServiceDisposeEmitter.fire({ sessionResource: [sessionResource], reason: 'cleared' }); + chatServiceDisposeEmitter.fire({ sessionResources: [sessionResource], reason: 'cleared' }); strictEqual(terminal1Disposed, true, 'Terminal 1 should have been disposed'); strictEqual(terminal2Disposed, true, 'Terminal 2 should have been disposed'); @@ -1543,7 +1543,7 @@ suite('RunInTerminalTool', () => { ok(runInTerminalTool.sessionTerminalAssociations.has(sessionResource), 'Terminal association should exist before disposal'); - chatServiceDisposeEmitter.fire({ sessionResource: [sessionResource], reason: 'cleared' }); + chatServiceDisposeEmitter.fire({ sessionResources: [sessionResource], reason: 'cleared' }); strictEqual(terminalDisposed, true, 'Terminal should have been disposed'); ok(!runInTerminalTool.sessionTerminalAssociations.has(sessionResource), 'Terminal association should be removed after disposal'); @@ -1574,7 +1574,7 @@ suite('RunInTerminalTool', () => { ok(runInTerminalTool.sessionTerminalAssociations.has(sessionResource1), 'Session 1 terminal association should exist'); ok(runInTerminalTool.sessionTerminalAssociations.has(sessionResource2), 'Session 2 terminal association should exist'); - chatServiceDisposeEmitter.fire({ sessionResource: [sessionResource1], reason: 'cleared' }); + chatServiceDisposeEmitter.fire({ sessionResources: [sessionResource1], reason: 'cleared' }); strictEqual(terminal1Disposed, true, 'Terminal 1 should have been disposed'); strictEqual(terminal2Disposed, false, 'Terminal 2 should NOT have been disposed'); @@ -1584,7 +1584,7 @@ suite('RunInTerminalTool', () => { test('should handle disposal of non-existent session gracefully', () => { strictEqual(runInTerminalTool.sessionTerminalAssociations.size, 0, 'No associations should exist initially'); - chatServiceDisposeEmitter.fire({ sessionResource: [LocalChatSessionUri.forSession('non-existent-session')], reason: 'cleared' }); + chatServiceDisposeEmitter.fire({ sessionResources: [LocalChatSessionUri.forSession('non-existent-session')], reason: 'cleared' }); strictEqual(runInTerminalTool.sessionTerminalAssociations.size, 0, 'No associations should exist after handling non-existent session'); }); }); @@ -1921,7 +1921,7 @@ suite('ChatAgentToolsContribution - tool registration refresh', () => { store.add(fileService.registerProvider(Schemas.file, fileSystemProvider)); const terminalServiceDisposeEmitter = store.add(new Emitter()); - const chatServiceDisposeEmitter = store.add(new Emitter<{ sessionResource: URI[]; reason: 'cleared' }>()); + const chatServiceDisposeEmitter = store.add(new Emitter<{ sessionResources: URI[]; reason: 'cleared' }>()); const chatSessionArchivedEmitter = store.add(new Emitter()); instantiationService = workbenchInstantiationService({