diff --git a/src/vs/platform/agentHost/node/agentService.ts b/src/vs/platform/agentHost/node/agentService.ts index 50a79dfa965..b90fa91c0c0 100644 --- a/src/vs/platform/agentHost/node/agentService.ts +++ b/src/vs/platform/agentHost/node/agentService.ts @@ -172,7 +172,6 @@ export class AgentService extends Disposable implements IAgentService { this._sessionToProvider.delete(session.toString()); } this._stateManager.deleteSession(session.toString()); - this._sessionDataService.deleteSessionData(session); } // ---- Protocol methods --------------------------------------------------- diff --git a/src/vs/platform/agentHost/node/agentSideEffects.ts b/src/vs/platform/agentHost/node/agentSideEffects.ts index 64132425be3..fa90488d644 100644 --- a/src/vs/platform/agentHost/node/agentSideEffects.ts +++ b/src/vs/platform/agentHost/node/agentSideEffects.ts @@ -341,7 +341,6 @@ export class AgentSideEffects extends Disposable implements IProtocolSideEffectH const agent = this._options.getAgent(session); agent?.disposeSession(URI.parse(session)).catch(() => { }); this._stateManager.deleteSession(session); - this._options.sessionDataService.deleteSessionData(URI.parse(session)); } async handleListSessions(): Promise { diff --git a/src/vs/platform/agentHost/node/copilot/fileEditTracker.ts b/src/vs/platform/agentHost/node/copilot/fileEditTracker.ts index d6aa590242f..7eee79b096e 100644 --- a/src/vs/platform/agentHost/node/copilot/fileEditTracker.ts +++ b/src/vs/platform/agentHost/node/copilot/fileEditTracker.ts @@ -45,12 +45,16 @@ export function parseSessionDbUri(raw: string): ISessionDbUriFields | undefined if (!toolCallId || !filePath || (part !== 'before' && part !== 'after')) { return undefined; } - return { - sessionUri: decodeHex(parsed.authority).toString(), - toolCallId: decodeURIComponent(toolCallId), - filePath: decodeHex(filePath).toString(), - part - }; + try { + return { + sessionUri: decodeHex(parsed.authority).toString(), + toolCallId: decodeURIComponent(toolCallId), + filePath: decodeHex(filePath).toString(), + part + }; + } catch { + return undefined; + } } /** diff --git a/src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts b/src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts index 56c9a09e79a..6f697f02cae 100644 --- a/src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts +++ b/src/vs/platform/agentHost/node/copilot/mapSessionEvents.ts @@ -115,7 +115,7 @@ export async function mapSessionEvents( list.push(r); } } - } catch { + } catch (_e) { // Database may not exist yet for new sessions — that's fine } } diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts index dceeac89e09..5a3e30493f8 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/stateToProgressAdapter.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IMarkdownString, MarkdownString } from '../../../../../../base/common/htmlContent.js'; -import { generateUuid } from '../../../../../../base/common/uuid.js'; import { URI } from '../../../../../../base/common/uri.js'; import { ToolCallStatus, TurnState, ResponsePartKind, getToolFileEdits, getToolOutputText, type ICompletedToolCall, type IToolCallState, type ITurn } from '../../../../../../platform/agentHost/common/state/sessionState.js'; import { getToolKind, getToolLanguage } from '../../../../../../platform/agentHost/common/state/sessionReducers.js'; @@ -46,12 +45,20 @@ export function turnsToHistory(turns: readonly ITurn[], participantId: string): switch (rp.kind) { case ResponsePartKind.Markdown: if (rp.content) { - parts.push({ kind: 'markdownContent', content: new MarkdownString(rp.content) }); + parts.push({ kind: 'markdownContent', content: new MarkdownString(rp.content, { supportHtml: true }) }); } break; - case ResponsePartKind.ToolCall: - parts.push(completedToolCallToSerialized(rp.toolCall as ICompletedToolCall)); + case ResponsePartKind.ToolCall: { + const tc = rp.toolCall as ICompletedToolCall; + const fileEditParts = completedToolCallToEditParts(tc); + const serialized = completedToolCallToSerialized(tc); + if (fileEditParts.length > 0) { + serialized.presentation = ToolInvocationPresentation.Hidden; + } + parts.push(serialized); + parts.push(...fileEditParts); break; + } case ResponsePartKind.Reasoning: if (rp.content) { parts.push({ kind: 'thinking', value: rp.content }); @@ -122,6 +129,35 @@ function completedToolCallToSerialized(tc: ICompletedToolCall): IChatToolInvocat }; } +/** + * Builds edit-structure progress parts for a completed tool call that + * produced file edits. Returns an empty array if the tool call has no edits. + * These parts replay the undo stops and code-block UI when restoring history. + */ +function completedToolCallToEditParts(tc: ICompletedToolCall): IChatProgress[] { + if (tc.status !== ToolCallStatus.Completed) { + return []; + } + const fileEdits = getToolFileEdits(tc); + if (fileEdits.length === 0) { + return []; + } + const filePath = getFilePathFromToolInput(tc); + if (!filePath) { + return []; + } + const fileUri = URI.file(filePath); + const parts: IChatProgress[] = []; + for (const _edit of fileEdits) { + parts.push({ kind: 'markdownContent', content: new MarkdownString('\n````\n') }); + parts.push({ kind: 'codeblockUri', uri: fileUri, isEdit: true, undoStopId: tc.toolCallId }); + parts.push({ kind: 'textEdit', uri: fileUri, edits: [], done: false, isExternalEdit: true }); + parts.push({ kind: 'textEdit', uri: fileUri, edits: [], done: true, isExternalEdit: true }); + parts.push({ kind: 'markdownContent', content: new MarkdownString('\n````\n') }); + } + return parts; +} + /** * Creates a live {@link ChatToolInvocation} from the protocol's tool-call * state. Used during active turns to represent running tool calls in the UI. @@ -278,7 +314,7 @@ function fileEditsToExternalEdits(tc: IToolCallState): IToolCallFileEdit[] { resource: URI.file(filePath), beforeContentUri: URI.parse(edit.beforeURI), afterContentUri: URI.parse(edit.afterURI), - undoStopId: generateUuid(), + undoStopId: tc.toolCallId, }); } }