Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ In multi-root and empty workspaces, a folder picker option appears in the chat s
### Key Files

- **`common/claudeFolderInfo.ts`**: `ClaudeFolderInfo` interface
- **`../../chatSessions/common/claudeWorkspaceFolderService.ts`**: `IClaudeWorkspaceFolderService` interface — computes git diff changes for session items
- **`../../chatSessions/vscode-node/claudeWorkspaceFolderServiceImpl.ts`**: Implementation — diffs the session's branch against its base branch, caches results, and maps changes to `ChatSessionChangedFile[]` for display in the Sessions view
- **`../../chatSessions/vscode-node/claudeChatSessionContentProvider.ts`**: Folder resolution, picker options, and handler integration
- **`../../chatSessions/vscode-node/folderRepositoryManagerImpl.ts`**: `FolderRepositoryManager` (abstract base) with `ClaudeFolderRepositoryManager` subclass — the Claude subclass does not depend on `ICopilotCLISessionService` (CopilotCLI has its own subclass `CopilotCLIFolderRepositoryManager`)
- **`node/claudeCodeAgent.ts`**: Consumes `ClaudeFolderInfo` in `ClaudeCodeSession._startSession()`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ Each session in the list displays:
| **Blue dot** | Indicates an unread or recently active session |
| **Status icon** | Shows whether the session is completed, in progress, needs input, or failed |
| **Folder badge** | In multi-root or empty workspaces, shows which folder the session ran in |
| **Change stats** | Shows lines added and removed (e.g., `+584 -17`) — a quick summary of the session's code impact, computed by diffing the session's branch against its base branch |

Sessions are sorted by recency — the most recent session appears at the top. In the dedicated sidebar, they're also grouped by time period.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type * as vscode from 'vscode';
import { createServiceIdentifier } from '../../../util/common/services';

export const IClaudeWorkspaceFolderService = createServiceIdentifier<IClaudeWorkspaceFolderService>('IClaudeWorkspaceFolderService');

/**
* Service for computing and caching workspace file changes for Claude chat sessions.
*/
export interface IClaudeWorkspaceFolderService {
readonly _serviceBrand: undefined;
/**
* Computes file changes for a workspace directory by diffing the current branch against a base branch.
* Results are cached per unique (cwd, gitBranch, gitBaseBranch) combination.
*
* @param cwd The working directory of the session.
* @param gitBranch The current git branch name, or `undefined` if unknown.
* @param gitBaseBranch The base branch to diff against, or `undefined` to diff against HEAD.
* @param forceRefresh When `true`, bypasses the cache and recomputes changes.
*/
getWorkspaceChanges(cwd: string, gitBranch: string | undefined, gitBaseBranch: string | undefined, forceRefresh?: boolean): Promise<vscode.ChatSessionChangedFile[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { ClaudeSlashCommandService, IClaudeSlashCommandService } from '../claude
import { IAgentSessionsWorkspace } from '../common/agentSessionsWorkspace';
import { IChatSessionMetadataStore } from '../common/chatSessionMetadataStore';
import { IChatSessionWorkspaceFolderService } from '../common/chatSessionWorkspaceFolderService';
import { IClaudeWorkspaceFolderService } from '../common/claudeWorkspaceFolderService';
import { IChatSessionWorktreeCheckpointService } from '../common/chatSessionWorktreeCheckpointService';
import { IChatSessionWorktreeService } from '../common/chatSessionWorktreeService';
import { IChatFolderMruService, IFolderRepositoryManager } from '../common/folderRepositoryManager';
Expand All @@ -61,6 +62,7 @@ import { UserQuestionHandler } from './askUserQuestionHandler';
import { ChatSessionMetadataStore } from './chatSessionMetadataStoreImpl';
import { ChatSessionRepositoryTracker } from './chatSessionRepositoryTracker';
import { ChatSessionWorkspaceFolderService } from './chatSessionWorkspaceFolderServiceImpl';
import { ClaudeWorkspaceFolderService } from './claudeWorkspaceFolderServiceImpl';
import { ChatSessionWorktreeCheckpointService } from './chatSessionWorktreeCheckpointServiceImpl';
import { ChatSessionWorktreeService } from './chatSessionWorktreeServiceImpl';
import { ClaudeChatSessionContentProvider } from './claudeChatSessionContentProvider';
Expand Down Expand Up @@ -142,6 +144,7 @@ export class ChatSessionsContrib extends Disposable implements IExtensionContrib
[IChatSessionWorktreeService, new SyncDescriptor(ChatSessionWorktreeService)],
[IChatSessionWorktreeCheckpointService, new SyncDescriptor(ChatSessionWorktreeCheckpointService)],
[IChatSessionWorkspaceFolderService, new SyncDescriptor(ChatSessionWorkspaceFolderService)],
[IClaudeWorkspaceFolderService, new SyncDescriptor(ClaudeWorkspaceFolderService)],
[IFolderRepositoryManager, new SyncDescriptor(ClaudeFolderRepositoryManager)],
[IChatFolderMruService, new SyncDescriptor(ClaudeCodeFolderMruService)],
[IClaudeRuntimeDataService, new SyncDescriptor(ClaudeRuntimeDataService)],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { autorun, derived, IObservable, ISettableObservable, observableFromEvent
import { basename } from '../../../util/vs/base/common/resources';
import { URI } from '../../../util/vs/base/common/uri';
import { generateUuid } from '../../../util/vs/base/common/uuid';
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
import { ClaudeFolderInfo } from '../claude/common/claudeFolderInfo';
import { ClaudeSessionUri } from '../claude/common/claudeSessionUri';
import { ClaudeAgentManager } from '../claude/node/claudeCodeAgent';
Expand All @@ -29,6 +30,7 @@ import { IClaudeCodeSessionService } from '../claude/node/sessionParser/claudeCo
import { IClaudeCodeSessionInfo } from '../claude/node/sessionParser/claudeSessionSchema';
import { IClaudeSlashCommandService } from '../claude/vscode-node/claudeSlashCommandService';
import { IChatFolderMruService } from '../common/folderRepositoryManager';
import { IClaudeWorkspaceFolderService } from '../common/claudeWorkspaceFolderService';
import { buildChatHistory } from './chatHistoryBuilder';
import { ClaudeSessionOptionBuilder, buildPermissionModeItems, FOLDER_OPTION_ID, isPermissionMode, PERMISSION_MODE_OPTION_ID } from './claudeSessionOptionBuilder';
import { toWorkspaceFolderOptionItem } from './sessionOptionGroupBuilder';
Expand Down Expand Up @@ -60,21 +62,11 @@ export class ClaudeChatSessionContentProvider extends Disposable implements vsco
@IClaudeCodeSessionService private readonly sessionService: IClaudeCodeSessionService,
@IClaudeSessionStateService private readonly sessionStateService: IClaudeSessionStateService,
@IClaudeSlashCommandService private readonly slashCommandService: IClaudeSlashCommandService,
@IConfigurationService configurationService: IConfigurationService,
@IClaudeCodeModels private readonly claudeModels: IClaudeCodeModels,
@IChatFolderMruService folderMruService: IChatFolderMruService,
@IWorkspaceService workspaceService: IWorkspaceService,
@INativeEnvService envService: INativeEnvService,
@IGitService gitService: IGitService,
@IClaudeCodeSdkService sdkService: IClaudeCodeSdkService,
@ILogService logService: ILogService,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
this._controller = this._register(new ClaudeChatSessionItemController(
sessionService, sessionStateService, configurationService,
folderMruService, workspaceService, envService,
gitService, sdkService, logService,
));
this._controller = this._register(instantiationService.createInstance(ClaudeChatSessionItemController));
}

// #region Chat Participant Handler
Expand Down Expand Up @@ -134,9 +126,9 @@ export class ClaudeChatSessionContentProvider extends Disposable implements vsco
});

const prompt = request.prompt;
this._controller.updateItemStatus(effectiveSessionId, vscode.ChatSessionStatus.InProgress, prompt);
await this._controller.updateItemStatus(effectiveSessionId, vscode.ChatSessionStatus.InProgress, prompt);
const result = await this.claudeAgentManager.handleRequest(effectiveSessionId, request, context, stream, token, isNewSession, yieldRequested);
this._controller.updateItemStatus(effectiveSessionId, vscode.ChatSessionStatus.Completed, prompt);
await this._controller.updateItemStatus(effectiveSessionId, vscode.ChatSessionStatus.Completed, prompt);

// Clear usage handler after request completes
this.sessionStateService.setUsageHandlerForSession(effectiveSessionId, undefined);
Expand Down Expand Up @@ -217,6 +209,7 @@ export class ClaudeChatSessionItemController extends Disposable {
@IGitService private readonly _gitService: IGitService,
@IClaudeCodeSdkService private readonly _sdkService: IClaudeCodeSdkService,
@ILogService private readonly _logService: ILogService,
@IClaudeWorkspaceFolderService private readonly _claudeWorkspaceFolderService: IClaudeWorkspaceFolderService,
) {
super();
this._optionBuilder = new ClaudeSessionOptionBuilder(_configurationService, folderMruService, _workspaceService);
Expand Down Expand Up @@ -642,7 +635,7 @@ export class ClaudeChatSessionItemController extends Disposable {
if (!item) {
const session = await this._claudeCodeSessionService.getSession(resource, CancellationToken.None);
if (session) {
item = this._createClaudeChatSessionItem(session);
item = await this._createClaudeChatSessionItem(session);
} else {
Comment thread
TylerLeonhardt marked this conversation as resolved.
const newlyCreatedSessionInfo: IClaudeCodeSessionInfo = {
id: sessionId,
Expand All @@ -651,7 +644,7 @@ export class ClaudeChatSessionItemController extends Disposable {
lastRequestEnded: Date.now(),
folderName: undefined
};
item = this._createClaudeChatSessionItem(newlyCreatedSessionInfo);
item = await this._createClaudeChatSessionItem(newlyCreatedSessionInfo);
}

this._controller.items.add(item);
Expand All @@ -676,18 +669,37 @@ export class ClaudeChatSessionItemController extends Disposable {
} else {
item.timing = { ...item.timing, lastRequestEnded: Date.now() };
}
const session = await this._claudeCodeSessionService.getSession(resource, CancellationToken.None);
if (session?.cwd) {
item.changes = await this._claudeWorkspaceFolderService.getWorkspaceChanges(
session.cwd,
session.gitBranch,
undefined,
true,
);
}
}
}
}

private async _refreshItems(token: vscode.CancellationToken): Promise<void> {
const sessions = await this._claudeCodeSessionService.getAllSessions(token);
const items = sessions.map(session => this._createClaudeChatSessionItem(session));
const results = await Promise.allSettled(sessions.map(session => this._createClaudeChatSessionItem(session)));
const items: vscode.ChatSessionItem[] = [];
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result.status === 'fulfilled') {
items.push(result.value);
} else {
const session = sessions[i];
this._logService.warn(`Failed to create Claude chat session item for ${session.id} (${session.label}) ${result.reason}`);
}
}
items.push(...this._inProgressItems.values());
this._controller.items.replace(items);
}

private _createClaudeChatSessionItem(session: IClaudeCodeSessionInfo): vscode.ChatSessionItem {
private async _createClaudeChatSessionItem(session: IClaudeCodeSessionInfo): Promise<vscode.ChatSessionItem> {
let badge: vscode.MarkdownString | undefined;
if (session.folderName && this._showBadge) {
badge = new vscode.MarkdownString(`$(folder) ${session.folderName}`);
Expand All @@ -704,8 +716,12 @@ export class ClaudeChatSessionItemController extends Disposable {
};
item.iconPath = new vscode.ThemeIcon('claude');
if (session.cwd) {
// Agents app needs this to decide the working directory for the session
item.metadata = { workingDirectoryPath: session.cwd };
item.changes = await this._claudeWorkspaceFolderService.getWorkspaceChanges(
session.cwd,
session.gitBranch,
undefined,
);
}
return item;
}
Expand Down
Loading
Loading