Fix new chat

This commit is contained in:
andrepimenta
2025-07-28 23:33:17 +01:00
parent 4f126641e4
commit 6bd906981b

View File

@@ -56,7 +56,6 @@ interface ConversationData {
}; };
messages: Array<{ timestamp: string, messageType: string, data: any }>; messages: Array<{ timestamp: string, messageType: string, data: any }>;
filename: string; filename: string;
isProcessing?: boolean;
} }
class ClaudeChatWebviewProvider implements vscode.WebviewViewProvider { class ClaudeChatWebviewProvider implements vscode.WebviewViewProvider {
@@ -64,7 +63,7 @@ class ClaudeChatWebviewProvider implements vscode.WebviewViewProvider {
private readonly _extensionUri: vscode.Uri, private readonly _extensionUri: vscode.Uri,
private readonly _context: vscode.ExtensionContext, private readonly _context: vscode.ExtensionContext,
private readonly _chatProvider: ClaudeChatProvider private readonly _chatProvider: ClaudeChatProvider
) {} ) { }
public resolveWebviewView( public resolveWebviewView(
webviewView: vscode.WebviewView, webviewView: vscode.WebviewView,
@@ -333,7 +332,7 @@ class ClaudeChatProvider {
if (this._messageHandlerDisposable) { if (this._messageHandlerDisposable) {
this._messageHandlerDisposable.dispose(); this._messageHandlerDisposable.dispose();
} }
// Set up new message handler // Set up new message handler
this._messageHandlerDisposable = webview.onDidReceiveMessage( this._messageHandlerDisposable = webview.onDidReceiveMessage(
message => this._handleWebviewMessage(message), message => this._handleWebviewMessage(message),
@@ -349,7 +348,7 @@ class ClaudeChatProvider {
} }
} }
public showInWebview(webview: vscode.Webview, webviewView?: vscode.WebviewView) { public showInWebview(webview: vscode.Webview, webviewView?: vscode.WebviewView) {
// Close main panel if it's open // Close main panel if it's open
if (this._panel) { if (this._panel) {
console.log('Closing main panel because sidebar is opening'); console.log('Closing main panel because sidebar is opening');
@@ -436,11 +435,11 @@ class ClaudeChatProvider {
type: 'userInput', type: 'userInput',
data: message data: message
}); });
// Set processing state to true // Set processing state to true
this._postMessage({ this._postMessage({
type: 'setProcessing', type: 'setProcessing',
data: {isProcessing: true} data: { isProcessing: true }
}); });
// Create backup commit before Claude makes changes // Create backup commit before Claude makes changes
@@ -466,7 +465,7 @@ class ClaudeChatProvider {
// Get configuration // Get configuration
const config = vscode.workspace.getConfiguration('claudeCodeChat'); const config = vscode.workspace.getConfiguration('claudeCodeChat');
const yoloMode = config.get<boolean>('permissions.yoloMode', false); const yoloMode = config.get<boolean>('permissions.yoloMode', false);
if (yoloMode) { if (yoloMode) {
// Yolo mode: skip all permissions regardless of MCP config // Yolo mode: skip all permissions regardless of MCP config
args.push('--dangerously-skip-permissions'); args.push('--dangerously-skip-permissions');
@@ -573,6 +572,10 @@ class ClaudeChatProvider {
console.log('Claude process closed with code:', code); console.log('Claude process closed with code:', code);
console.log('Claude stderr output:', errorOutput); console.log('Claude stderr output:', errorOutput);
if (!this._currentClaudeProcess) {
return;
}
// Clear process reference // Clear process reference
this._currentClaudeProcess = undefined; this._currentClaudeProcess = undefined;
@@ -580,10 +583,16 @@ class ClaudeChatProvider {
this._postMessage({ this._postMessage({
type: 'clearLoading' type: 'clearLoading'
}); });
// Reset processing state // Reset processing state
this._isProcessing = false; this._isProcessing = false;
// Clear processing state
this._postMessage({
type: 'setProcessing',
data: { isProcessing: false }
});
if (code !== 0 && errorOutput.trim()) { if (code !== 0 && errorOutput.trim()) {
// Error with output // Error with output
this._sendAndSaveMessage({ this._sendAndSaveMessage({
@@ -595,14 +604,26 @@ class ClaudeChatProvider {
claudeProcess.on('error', (error) => { claudeProcess.on('error', (error) => {
console.log('Claude process error:', error.message); console.log('Claude process error:', error.message);
if (!this._currentClaudeProcess) {
return;
}
// Clear process reference // Clear process reference
this._currentClaudeProcess = undefined; this._currentClaudeProcess = undefined;
this._postMessage({ this._postMessage({
type: 'clearLoading' type: 'clearLoading'
}); });
this._isProcessing = false;
// Clear processing state
this._postMessage({
type: 'setProcessing',
data: { isProcessing: false }
});
// Check if claude command is not installed // Check if claude command is not installed
if (error.message.includes('ENOENT') || error.message.includes('command not found')) { if (error.message.includes('ENOENT') || error.message.includes('command not found')) {
this._sendAndSaveMessage({ this._sendAndSaveMessage({
@@ -624,6 +645,18 @@ class ClaudeChatProvider {
if (jsonData.subtype === 'init') { if (jsonData.subtype === 'init') {
// System initialization message - session ID will be captured from final result // System initialization message - session ID will be captured from final result
console.log('System initialized'); console.log('System initialized');
this._currentSessionId = jsonData.session_id;
//this._sendAndSaveMessage({ type: 'init', data: { sessionId: jsonData.session_id; } })
// Show session info in UI
this._sendAndSaveMessage({
type: 'sessionInfo',
data: {
sessionId: jsonData.session_id,
tools: jsonData.tools || [],
mcpServers: jsonData.mcp_servers || []
}
});
} }
break; break;
@@ -703,16 +736,16 @@ class ClaudeChatProvider {
for (const content of jsonData.message.content) { for (const content of jsonData.message.content) {
if (content.type === 'tool_result') { if (content.type === 'tool_result') {
let resultContent = content.content || 'Tool executed successfully'; let resultContent = content.content || 'Tool executed successfully';
// Stringify if content is an object or array // Stringify if content is an object or array
if (typeof resultContent === 'object' && resultContent !== null) { if (typeof resultContent === 'object' && resultContent !== null) {
resultContent = JSON.stringify(resultContent, null, 2); resultContent = JSON.stringify(resultContent, null, 2);
} }
const isError = content.is_error || false; const isError = content.is_error || false;
// Find the last tool use to get the tool name // Find the last tool use to get the tool name
const lastToolUse = this._currentConversation[this._currentConversation.length-1] const lastToolUse = this._currentConversation[this._currentConversation.length - 1]
const toolName = lastToolUse?.data?.toolName; const toolName = lastToolUse?.data?.toolName;
@@ -784,7 +817,7 @@ class ClaudeChatProvider {
// Clear processing state // Clear processing state
this._postMessage({ this._postMessage({
type: 'setProcessing', type: 'setProcessing',
data: {isProcessing: false} data: { isProcessing: false }
}); });
// Update cumulative tracking // Update cumulative tracking
@@ -819,6 +852,22 @@ class ClaudeChatProvider {
private _newSession() { private _newSession() {
this._isProcessing = false
// Update UI state
this._postMessage({
type: 'setProcessing',
data: { isProcessing: false }
});
// Try graceful termination first
if (this._currentClaudeProcess) {
const processToKill = this._currentClaudeProcess;
this._currentClaudeProcess = undefined;
processToKill.kill('SIGTERM');
}
// Clear current session // Clear current session
this._currentSessionId = undefined; this._currentSessionId = undefined;
@@ -842,10 +891,10 @@ class ClaudeChatProvider {
public newSessionOnConfigChange() { public newSessionOnConfigChange() {
// Reinitialize MCP config with new WSL paths // Reinitialize MCP config with new WSL paths
this._initializeMCPConfig(); this._initializeMCPConfig();
// Start a new session due to configuration change // Start a new session due to configuration change
this._newSession(); this._newSession();
// Show notification to user // Show notification to user
vscode.window.showInformationMessage( vscode.window.showInformationMessage(
'WSL configuration changed. Started a new Claude session.', 'WSL configuration changed. Started a new Claude session.',
@@ -866,7 +915,7 @@ class ClaudeChatProvider {
// Clear processing state // Clear processing state
this._postMessage({ this._postMessage({
type: 'setProcessing', type: 'setProcessing',
data: {isProcessing: false} data: { isProcessing: false }
}); });
// Show login required message // Show login required message
@@ -896,7 +945,7 @@ class ClaudeChatProvider {
'OK' 'OK'
); );
// Send message to UI about terminal // Send message to UI about terminal
this._postMessage({ this._postMessage({
type: 'terminalOpened', type: 'terminalOpened',
data: `Please login to Claude in the terminal, then come back to this chat to continue.`, data: `Please login to Claude in the terminal, then come back to this chat to continue.`,
@@ -906,7 +955,7 @@ class ClaudeChatProvider {
private async _initializeBackupRepo(): Promise<void> { private async _initializeBackupRepo(): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {return;} if (!workspaceFolder) { return; }
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) { if (!storagePath) {
@@ -939,7 +988,7 @@ class ClaudeChatProvider {
private async _createBackupCommit(userMessage: string): Promise<void> { private async _createBackupCommit(userMessage: string): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder || !this._backupRepoPath) {return;} if (!workspaceFolder || !this._backupRepoPath) { return; }
const workspacePath = workspaceFolder.uri.fsPath; const workspacePath = workspaceFolder.uri.fsPath;
const now = new Date(); const now = new Date();
@@ -1048,10 +1097,10 @@ class ClaudeChatProvider {
private async _initializeConversations(): Promise<void> { private async _initializeConversations(): Promise<void> {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {return;} if (!workspaceFolder) { return; }
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return;} if (!storagePath) { return; }
this._conversationsPath = path.join(storagePath, 'conversations'); this._conversationsPath = path.join(storagePath, 'conversations');
@@ -1070,7 +1119,7 @@ class ClaudeChatProvider {
private async _initializeMCPConfig(): Promise<void> { private async _initializeMCPConfig(): Promise<void> {
try { try {
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return;} if (!storagePath) { return; }
// Create MCP config directory // Create MCP config directory
const mcpConfigDir = path.join(storagePath, 'mcp'); const mcpConfigDir = path.join(storagePath, 'mcp');
@@ -1085,11 +1134,11 @@ class ClaudeChatProvider {
const mcpConfigPath = path.join(mcpConfigDir, 'mcp-servers.json'); const mcpConfigPath = path.join(mcpConfigDir, 'mcp-servers.json');
const mcpPermissionsPath = this.convertToWSLPath(path.join(this._extensionUri.fsPath, 'mcp-permissions.js')); const mcpPermissionsPath = this.convertToWSLPath(path.join(this._extensionUri.fsPath, 'mcp-permissions.js'));
const permissionRequestsPath = this.convertToWSLPath(path.join(storagePath, 'permission-requests')); const permissionRequestsPath = this.convertToWSLPath(path.join(storagePath, 'permission-requests'));
// Load existing config or create new one // Load existing config or create new one
let mcpConfig: any = { mcpServers: {} }; let mcpConfig: any = { mcpServers: {} };
const mcpConfigUri = vscode.Uri.file(mcpConfigPath); const mcpConfigUri = vscode.Uri.file(mcpConfigPath);
try { try {
const existingContent = await vscode.workspace.fs.readFile(mcpConfigUri); const existingContent = await vscode.workspace.fs.readFile(mcpConfigUri);
mcpConfig = JSON.parse(new TextDecoder().decode(existingContent)); mcpConfig = JSON.parse(new TextDecoder().decode(existingContent));
@@ -1097,12 +1146,12 @@ class ClaudeChatProvider {
} catch { } catch {
console.log('No existing MCP config found, creating new one'); console.log('No existing MCP config found, creating new one');
} }
// Ensure mcpServers exists // Ensure mcpServers exists
if (!mcpConfig.mcpServers) { if (!mcpConfig.mcpServers) {
mcpConfig.mcpServers = {}; mcpConfig.mcpServers = {};
} }
// Add or update the permissions server entry // Add or update the permissions server entry
mcpConfig.mcpServers['claude-code-chat-permissions'] = { mcpConfig.mcpServers['claude-code-chat-permissions'] = {
command: 'node', command: 'node',
@@ -1114,7 +1163,7 @@ class ClaudeChatProvider {
const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2)); const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2));
await vscode.workspace.fs.writeFile(mcpConfigUri, configContent); await vscode.workspace.fs.writeFile(mcpConfigUri, configContent);
console.log(`Updated MCP config at: ${mcpConfigPath}`); console.log(`Updated MCP config at: ${mcpConfigPath}`);
} catch (error: any) { } catch (error: any) {
console.error('Failed to initialize MCP config:', error.message); console.error('Failed to initialize MCP config:', error.message);
@@ -1124,13 +1173,13 @@ class ClaudeChatProvider {
private async _initializePermissions(): Promise<void> { private async _initializePermissions(): Promise<void> {
try { try {
if(this._permissionWatcher){ if (this._permissionWatcher) {
this._permissionWatcher.dispose(); this._permissionWatcher.dispose();
this._permissionWatcher = undefined; this._permissionWatcher = undefined;
} }
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return;} if (!storagePath) { return; }
// Create permission requests directory // Create permission requests directory
this._permissionRequestsPath = path.join(path.join(storagePath, 'permission-requests')); this._permissionRequestsPath = path.join(path.join(storagePath, 'permission-requests'));
@@ -1193,13 +1242,13 @@ class ClaudeChatProvider {
private async _showPermissionDialog(request: any): Promise<boolean> { private async _showPermissionDialog(request: any): Promise<boolean> {
const toolName = request.tool || 'Unknown Tool'; const toolName = request.tool || 'Unknown Tool';
// Generate pattern for Bash commands // Generate pattern for Bash commands
let pattern = undefined; let pattern = undefined;
if (toolName === 'Bash' && request.input?.command) { if (toolName === 'Bash' && request.input?.command) {
pattern = this.getCommandPattern(request.input.command); pattern = this.getCommandPattern(request.input.command);
} }
// Send permission request to the UI // Send permission request to the UI
this._postMessage({ this._postMessage({
type: 'permissionRequest', type: 'permissionRequest',
@@ -1225,7 +1274,7 @@ class ClaudeChatProvider {
if (resolver) { if (resolver) {
resolver(approved); resolver(approved);
this._pendingPermissionResolvers.delete(id); this._pendingPermissionResolvers.delete(id);
// Handle always allow setting // Handle always allow setting
if (alwaysAllow && approved) { if (alwaysAllow && approved) {
void this._saveAlwaysAllowPermission(id); void this._saveAlwaysAllowPermission(id);
@@ -1241,7 +1290,7 @@ class ClaudeChatProvider {
if (!storagePath) return; if (!storagePath) return;
const requestFileUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', `${requestId}.request`)); const requestFileUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', `${requestId}.request`));
let requestContent: Uint8Array; let requestContent: Uint8Array;
try { try {
requestContent = await vscode.workspace.fs.readFile(requestFileUri); requestContent = await vscode.workspace.fs.readFile(requestFileUri);
@@ -1250,11 +1299,11 @@ class ClaudeChatProvider {
} }
const request = JSON.parse(new TextDecoder().decode(requestContent)); const request = JSON.parse(new TextDecoder().decode(requestContent));
// Load existing workspace permissions // Load existing workspace permissions
const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json')); const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json'));
let permissions: any = { alwaysAllow: {} }; let permissions: any = { alwaysAllow: {} };
try { try {
const content = await vscode.workspace.fs.readFile(permissionsUri); const content = await vscode.workspace.fs.readFile(permissionsUri);
permissions = JSON.parse(new TextDecoder().decode(content)); permissions = JSON.parse(new TextDecoder().decode(content));
@@ -1292,7 +1341,7 @@ class ClaudeChatProvider {
// Save the permissions // Save the permissions
const permissionsContent = new TextEncoder().encode(JSON.stringify(permissions, null, 2)); const permissionsContent = new TextEncoder().encode(JSON.stringify(permissions, null, 2));
await vscode.workspace.fs.writeFile(permissionsUri, permissionsContent); await vscode.workspace.fs.writeFile(permissionsUri, permissionsContent);
console.log(`Saved always-allow permission for ${toolName}`); console.log(`Saved always-allow permission for ${toolName}`);
} catch (error) { } catch (error) {
console.error('Error saving always-allow permission:', error); console.error('Error saving always-allow permission:', error);
@@ -1302,10 +1351,10 @@ class ClaudeChatProvider {
private getCommandPattern(command: string): string { private getCommandPattern(command: string): string {
const parts = command.trim().split(/\s+/); const parts = command.trim().split(/\s+/);
if (parts.length === 0) return command; if (parts.length === 0) return command;
const baseCmd = parts[0]; const baseCmd = parts[0];
const subCmd = parts.length > 1 ? parts[1] : ''; const subCmd = parts.length > 1 ? parts[1] : '';
// Common patterns that should use wildcards // Common patterns that should use wildcards
const patterns = [ const patterns = [
// Package managers // Package managers
@@ -1322,7 +1371,7 @@ class ClaudeChatProvider {
['pnpm', 'install', 'pnpm install *'], ['pnpm', 'install', 'pnpm install *'],
['pnpm', 'add', 'pnpm add *'], ['pnpm', 'add', 'pnpm add *'],
['pnpm', 'remove', 'pnpm remove *'], ['pnpm', 'remove', 'pnpm remove *'],
// Git commands // Git commands
['git', 'add', 'git add *'], ['git', 'add', 'git add *'],
['git', 'commit', 'git commit *'], ['git', 'commit', 'git commit *'],
@@ -1335,7 +1384,7 @@ class ClaudeChatProvider {
['git', 'reset', 'git reset *'], ['git', 'reset', 'git reset *'],
['git', 'rebase', 'git rebase *'], ['git', 'rebase', 'git rebase *'],
['git', 'tag', 'git tag *'], ['git', 'tag', 'git tag *'],
// Docker commands // Docker commands
['docker', 'run', 'docker run *'], ['docker', 'run', 'docker run *'],
['docker', 'build', 'docker build *'], ['docker', 'build', 'docker build *'],
@@ -1347,7 +1396,7 @@ class ClaudeChatProvider {
['docker', 'rmi', 'docker rmi *'], ['docker', 'rmi', 'docker rmi *'],
['docker', 'pull', 'docker pull *'], ['docker', 'pull', 'docker pull *'],
['docker', 'push', 'docker push *'], ['docker', 'push', 'docker push *'],
// Build tools // Build tools
['make', '', 'make *'], ['make', '', 'make *'],
['cargo', 'build', 'cargo build *'], ['cargo', 'build', 'cargo build *'],
@@ -1359,7 +1408,7 @@ class ClaudeChatProvider {
['mvn', 'package', 'mvn package *'], ['mvn', 'package', 'mvn package *'],
['gradle', 'build', 'gradle build *'], ['gradle', 'build', 'gradle build *'],
['gradle', 'test', 'gradle test *'], ['gradle', 'test', 'gradle test *'],
// System commands // System commands
['curl', '', 'curl *'], ['curl', '', 'curl *'],
['wget', '', 'wget *'], ['wget', '', 'wget *'],
@@ -1369,7 +1418,7 @@ class ClaudeChatProvider {
['tar', '', 'tar *'], ['tar', '', 'tar *'],
['zip', '', 'zip *'], ['zip', '', 'zip *'],
['unzip', '', 'unzip *'], ['unzip', '', 'unzip *'],
// Development tools // Development tools
['node', '', 'node *'], ['node', '', 'node *'],
['python', '', 'python *'], ['python', '', 'python *'],
@@ -1381,14 +1430,14 @@ class ClaudeChatProvider {
['bundle', 'install', 'bundle install *'], ['bundle', 'install', 'bundle install *'],
['gem', 'install', 'gem install *'], ['gem', 'install', 'gem install *'],
]; ];
// Find matching pattern // Find matching pattern
for (const [cmd, sub, pattern] of patterns) { for (const [cmd, sub, pattern] of patterns) {
if (baseCmd === cmd && (sub === '' || subCmd === sub)) { if (baseCmd === cmd && (sub === '' || subCmd === sub)) {
return pattern; return pattern;
} }
} }
// Default: return exact command // Default: return exact command
return command; return command;
} }
@@ -1406,7 +1455,7 @@ class ClaudeChatProvider {
const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json')); const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json'));
let permissions: any = { alwaysAllow: {} }; let permissions: any = { alwaysAllow: {} };
try { try {
const content = await vscode.workspace.fs.readFile(permissionsUri); const content = await vscode.workspace.fs.readFile(permissionsUri);
permissions = JSON.parse(new TextDecoder().decode(content)); permissions = JSON.parse(new TextDecoder().decode(content));
@@ -1434,7 +1483,7 @@ class ClaudeChatProvider {
const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json')); const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json'));
let permissions: any = { alwaysAllow: {} }; let permissions: any = { alwaysAllow: {} };
try { try {
const content = await vscode.workspace.fs.readFile(permissionsUri); const content = await vscode.workspace.fs.readFile(permissionsUri);
permissions = JSON.parse(new TextDecoder().decode(content)); permissions = JSON.parse(new TextDecoder().decode(content));
@@ -1463,10 +1512,10 @@ class ClaudeChatProvider {
// Save updated permissions // Save updated permissions
const permissionsContent = new TextEncoder().encode(JSON.stringify(permissions, null, 2)); const permissionsContent = new TextEncoder().encode(JSON.stringify(permissions, null, 2));
await vscode.workspace.fs.writeFile(permissionsUri, permissionsContent); await vscode.workspace.fs.writeFile(permissionsUri, permissionsContent);
// Send updated permissions to UI // Send updated permissions to UI
this._sendPermissions(); this._sendPermissions();
console.log(`Removed permission for ${toolName}${command ? ` command: ${command}` : ''}`); console.log(`Removed permission for ${toolName}${command ? ` command: ${command}` : ''}`);
} catch (error) { } catch (error) {
console.error('Error removing permission:', error); console.error('Error removing permission:', error);
@@ -1480,7 +1529,7 @@ class ClaudeChatProvider {
const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json')); const permissionsUri = vscode.Uri.file(path.join(storagePath, 'permission-requests', 'permissions.json'));
let permissions: any = { alwaysAllow: {} }; let permissions: any = { alwaysAllow: {} };
try { try {
const content = await vscode.workspace.fs.readFile(permissionsUri); const content = await vscode.workspace.fs.readFile(permissionsUri);
permissions = JSON.parse(new TextDecoder().decode(content)); permissions = JSON.parse(new TextDecoder().decode(content));
@@ -1497,19 +1546,19 @@ class ClaudeChatProvider {
if (!permissions.alwaysAllow[toolName]) { if (!permissions.alwaysAllow[toolName]) {
permissions.alwaysAllow[toolName] = []; permissions.alwaysAllow[toolName] = [];
} }
// Convert to array if it's currently set to true // Convert to array if it's currently set to true
if (permissions.alwaysAllow[toolName] === true) { if (permissions.alwaysAllow[toolName] === true) {
permissions.alwaysAllow[toolName] = []; permissions.alwaysAllow[toolName] = [];
} }
if (Array.isArray(permissions.alwaysAllow[toolName])) { if (Array.isArray(permissions.alwaysAllow[toolName])) {
// For Bash commands, convert to pattern using existing logic // For Bash commands, convert to pattern using existing logic
let commandToAdd = command; let commandToAdd = command;
if (toolName === 'Bash') { if (toolName === 'Bash') {
commandToAdd = this.getCommandPattern(command); commandToAdd = this.getCommandPattern(command);
} }
// Add if not already present // Add if not already present
if (!permissions.alwaysAllow[toolName].includes(commandToAdd)) { if (!permissions.alwaysAllow[toolName].includes(commandToAdd)) {
permissions.alwaysAllow[toolName].push(commandToAdd); permissions.alwaysAllow[toolName].push(commandToAdd);
@@ -1528,10 +1577,10 @@ class ClaudeChatProvider {
// Save updated permissions // Save updated permissions
const permissionsContent = new TextEncoder().encode(JSON.stringify(permissions, null, 2)); const permissionsContent = new TextEncoder().encode(JSON.stringify(permissions, null, 2));
await vscode.workspace.fs.writeFile(permissionsUri, permissionsContent); await vscode.workspace.fs.writeFile(permissionsUri, permissionsContent);
// Send updated permissions to UI // Send updated permissions to UI
this._sendPermissions(); this._sendPermissions();
console.log(`Added permission for ${toolName}${command ? ` command: ${command}` : ' (all commands)'}`); console.log(`Added permission for ${toolName}${command ? ` command: ${command}` : ' (all commands)'}`);
} catch (error) { } catch (error) {
console.error('Error adding permission:', error); console.error('Error adding permission:', error);
@@ -1558,10 +1607,10 @@ class ClaudeChatProvider {
} }
// Filter out internal servers before sending to UI // Filter out internal servers before sending to UI
const filteredServers = Object.fromEntries( const filteredServers = Object.fromEntries(
Object.entries(mcpConfig.mcpServers || {}).filter(([name]) => name !== 'claude-code-chat-permissions') Object.entries(mcpConfig.mcpServers || {}).filter(([name]) => name !== 'claude-code-chat-permissions')
); );
this._postMessage({ type: 'mcpServers', data: filteredServers }); this._postMessage({ type: 'mcpServers', data: filteredServers });
} catch (error) { } catch (error) {
console.error('Error loading MCP servers:', error); console.error('Error loading MCP servers:', error);
this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to load MCP servers' } }); this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to load MCP servers' } });
@@ -1657,7 +1706,7 @@ class ClaudeChatProvider {
private async _sendCustomSnippets(): Promise<void> { private async _sendCustomSnippets(): Promise<void> {
try { try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {}); const customSnippets = this._context.globalState.get<{ [key: string]: any }>('customPromptSnippets', {});
this._postMessage({ this._postMessage({
type: 'customSnippetsData', type: 'customSnippetsData',
data: customSnippets data: customSnippets
@@ -1673,16 +1722,16 @@ class ClaudeChatProvider {
private async _saveCustomSnippet(snippet: any): Promise<void> { private async _saveCustomSnippet(snippet: any): Promise<void> {
try { try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {}); const customSnippets = this._context.globalState.get<{ [key: string]: any }>('customPromptSnippets', {});
customSnippets[snippet.id] = snippet; customSnippets[snippet.id] = snippet;
await this._context.globalState.update('customPromptSnippets', customSnippets); await this._context.globalState.update('customPromptSnippets', customSnippets);
this._postMessage({ this._postMessage({
type: 'customSnippetSaved', type: 'customSnippetSaved',
data: { snippet } data: { snippet }
}); });
console.log('Saved custom snippet:', snippet.name); console.log('Saved custom snippet:', snippet.name);
} catch (error) { } catch (error) {
console.error('Error saving custom snippet:', error); console.error('Error saving custom snippet:', error);
@@ -1695,17 +1744,17 @@ class ClaudeChatProvider {
private async _deleteCustomSnippet(snippetId: string): Promise<void> { private async _deleteCustomSnippet(snippetId: string): Promise<void> {
try { try {
const customSnippets = this._context.globalState.get<{[key: string]: any}>('customPromptSnippets', {}); const customSnippets = this._context.globalState.get<{ [key: string]: any }>('customPromptSnippets', {});
if (customSnippets[snippetId]) { if (customSnippets[snippetId]) {
delete customSnippets[snippetId]; delete customSnippets[snippetId];
await this._context.globalState.update('customPromptSnippets', customSnippets); await this._context.globalState.update('customPromptSnippets', customSnippets);
this._postMessage({ this._postMessage({
type: 'customSnippetDeleted', type: 'customSnippetDeleted',
data: { snippetId } data: { snippetId }
}); });
console.log('Deleted custom snippet:', snippetId); console.log('Deleted custom snippet:', snippetId);
} else { } else {
this._postMessage({ this._postMessage({
@@ -1725,24 +1774,24 @@ class ClaudeChatProvider {
private convertToWSLPath(windowsPath: string): string { private convertToWSLPath(windowsPath: string): string {
const config = vscode.workspace.getConfiguration('claudeCodeChat'); const config = vscode.workspace.getConfiguration('claudeCodeChat');
const wslEnabled = config.get<boolean>('wsl.enabled', false); const wslEnabled = config.get<boolean>('wsl.enabled', false);
if (wslEnabled && windowsPath.match(/^[a-zA-Z]:/)) { if (wslEnabled && windowsPath.match(/^[a-zA-Z]:/)) {
// Convert C:\Users\... to /mnt/c/Users/... // Convert C:\Users\... to /mnt/c/Users/...
return windowsPath.replace(/^([a-zA-Z]):/, '/mnt/$1').toLowerCase().replace(/\\/g, '/'); return windowsPath.replace(/^([a-zA-Z]):/, '/mnt/$1').toLowerCase().replace(/\\/g, '/');
} }
return windowsPath; return windowsPath;
} }
public getMCPConfigPath(): string | undefined { public getMCPConfigPath(): string | undefined {
const storagePath = this._context.storageUri?.fsPath; const storagePath = this._context.storageUri?.fsPath;
if (!storagePath) {return undefined;} if (!storagePath) { return undefined; }
const configPath = path.join(storagePath, 'mcp', 'mcp-servers.json'); const configPath = path.join(storagePath, 'mcp', 'mcp-servers.json');
return path.join(configPath); return path.join(configPath);
} }
private _sendAndSaveMessage(message: { type: string, data: any }, options?: { isProcessing?: boolean }): void { private _sendAndSaveMessage(message: { type: string, data: any }): void {
// Initialize conversation if this is the first message // Initialize conversation if this is the first message
if (this._currentConversation.length === 0) { if (this._currentConversation.length === 0) {
@@ -1764,10 +1813,8 @@ class ClaudeChatProvider {
} }
private async _saveCurrentConversation(): Promise<void> { private async _saveCurrentConversation(): Promise<void> {
if (!this._conversationsPath || this._currentConversation.length === 0) {return;} if (!this._conversationsPath || this._currentConversation.length === 0) { return; }
if(!this._currentSessionId) {return;} if (!this._currentSessionId) { return; }
console.log("IS PROCESSING", this._isProcessing)
try { try {
// Create filename from first user message and timestamp // Create filename from first user message and timestamp
@@ -1786,7 +1833,7 @@ class ClaudeChatProvider {
const datePrefix = startTime.substring(0, 16).replace('T', '_').replace(/:/g, '-'); const datePrefix = startTime.substring(0, 16).replace('T', '_').replace(/:/g, '-');
const filename = `${datePrefix}_${cleanMessage}.json`; const filename = `${datePrefix}_${cleanMessage}.json`;
const conversationData : ConversationData = { const conversationData: ConversationData = {
sessionId: sessionId, sessionId: sessionId,
startTime: this._conversationStartTime, startTime: this._conversationStartTime,
endTime: new Date().toISOString(), endTime: new Date().toISOString(),
@@ -1800,10 +1847,6 @@ class ClaudeChatProvider {
filename filename
}; };
if (this._isProcessing !== undefined){
conversationData.isProcessing = this._isProcessing || false
}
const filePath = path.join(this._conversationsPath, filename); const filePath = path.join(this._conversationsPath, filename);
const content = new TextEncoder().encode(JSON.stringify(conversationData, null, 2)); const content = new TextEncoder().encode(JSON.stringify(conversationData, null, 2));
await vscode.workspace.fs.writeFile(vscode.Uri.file(filePath), content); await vscode.workspace.fs.writeFile(vscode.Uri.file(filePath), content);
@@ -1854,11 +1897,11 @@ class ClaudeChatProvider {
fileList = fileList.filter(file => { fileList = fileList.filter(file => {
const fileName = file.name.toLowerCase(); const fileName = file.name.toLowerCase();
const filePath = file.path.toLowerCase(); const filePath = file.path.toLowerCase();
// Check if term matches filename or any part of the path // Check if term matches filename or any part of the path
return fileName.includes(term) || return fileName.includes(term) ||
filePath.includes(term) || filePath.includes(term) ||
filePath.split('/').some(segment => segment.includes(term)); filePath.split('/').some(segment => segment.includes(term));
}); });
} }
@@ -1892,7 +1935,7 @@ class ClaudeChatProvider {
'Images': ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp'] 'Images': ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp']
} }
}); });
if (result && result.length > 0) { if (result && result.length > 0) {
// Send the selected file paths back to webview // Send the selected file paths back to webview
result.forEach(uri => { result.forEach(uri => {
@@ -1902,7 +1945,7 @@ class ClaudeChatProvider {
}); });
}); });
} }
} catch (error) { } catch (error) {
console.error('Error selecting image files:', error); console.error('Error selecting image files:', error);
} }
@@ -1912,19 +1955,19 @@ class ClaudeChatProvider {
console.log('Stop request received'); console.log('Stop request received');
this._isProcessing = false this._isProcessing = false
// Update UI state // Update UI state
this._postMessage({ this._postMessage({
type: 'setProcessing', type: 'setProcessing',
data: {isProcessing: false} data: { isProcessing: false }
}); });
if (this._currentClaudeProcess) { if (this._currentClaudeProcess) {
console.log('Terminating Claude process...'); console.log('Terminating Claude process...');
// Try graceful termination first // Try graceful termination first
this._currentClaudeProcess.kill('SIGTERM'); this._currentClaudeProcess.kill('SIGTERM');
// Force kill after 2 seconds if still running // Force kill after 2 seconds if still running
setTimeout(() => { setTimeout(() => {
if (this._currentClaudeProcess && !this._currentClaudeProcess.killed) { if (this._currentClaudeProcess && !this._currentClaudeProcess.killed) {
@@ -1932,20 +1975,20 @@ class ClaudeChatProvider {
this._currentClaudeProcess.kill('SIGKILL'); this._currentClaudeProcess.kill('SIGKILL');
} }
}, 2000); }, 2000);
// Clear process reference // Clear process reference
this._currentClaudeProcess = undefined; this._currentClaudeProcess = undefined;
this._postMessage({ this._postMessage({
type: 'clearLoading' type: 'clearLoading'
}); });
// Send stop confirmation message directly to UI and save // Send stop confirmation message directly to UI and save
this._sendAndSaveMessage({ this._sendAndSaveMessage({
type: 'error', type: 'error',
data: '⏹️ Claude code was stopped.' data: '⏹️ Claude code was stopped.'
}); });
console.log('Claude process termination initiated'); console.log('Claude process termination initiated');
} else { } else {
console.log('No Claude process running to stop'); console.log('No Claude process running to stop');
@@ -1991,12 +2034,12 @@ class ClaudeChatProvider {
private async _loadConversationHistory(filename: string): Promise<void> { private async _loadConversationHistory(filename: string): Promise<void> {
console.log("_loadConversationHistory"); console.log("_loadConversationHistory");
if (!this._conversationsPath) {return;} if (!this._conversationsPath) { return; }
try { try {
const filePath = path.join(this._conversationsPath, filename); const filePath = path.join(this._conversationsPath, filename);
console.log("filePath", filePath); console.log("filePath", filePath);
let conversationData: ConversationData; let conversationData: ConversationData;
try { try {
const fileUri = vscode.Uri.file(filePath); const fileUri = vscode.Uri.file(filePath);
@@ -2005,8 +2048,7 @@ class ClaudeChatProvider {
} catch { } catch {
return; return;
} }
console.log("conversationData-----", conversationData);
// Load conversation into current state // Load conversation into current state
this._currentConversation = conversationData.messages || []; this._currentConversation = conversationData.messages || [];
this._conversationStartTime = conversationData.startTime; this._conversationStartTime = conversationData.startTime;
@@ -2030,10 +2072,10 @@ class ClaudeChatProvider {
type: message.messageType, type: message.messageType,
data: message.data data: message.data
}); });
if(message.messageType === 'userInput'){ if (message.messageType === 'userInput') {
try{ try {
requestStartTime = new Date(message.timestamp).getTime() requestStartTime = new Date(message.timestamp).getTime()
}catch(e){ } catch (e) {
console.log(e) console.log(e)
} }
} }
@@ -2051,11 +2093,10 @@ class ClaudeChatProvider {
}); });
// Restore processing state if the conversation was saved while processing // Restore processing state if the conversation was saved while processing
if (conversationData.isProcessing) { if (this._isProcessing) {
this._isProcessing = conversationData.isProcessing;
this._postMessage({ this._postMessage({
type: 'setProcessing', type: 'setProcessing',
data: {isProcessing: conversationData.isProcessing, requestStartTime} data: { isProcessing: this._isProcessing, requestStartTime }
}); });
} }
// Send ready message after conversation is loaded // Send ready message after conversation is loaded
@@ -2094,15 +2135,15 @@ class ClaudeChatProvider {
try { try {
// Update VS Code configuration to enable YOLO mode // Update VS Code configuration to enable YOLO mode
const config = vscode.workspace.getConfiguration('claudeCodeChat'); const config = vscode.workspace.getConfiguration('claudeCodeChat');
// Clear any global setting and set workspace setting // Clear any global setting and set workspace setting
await config.update('permissions.yoloMode', true, vscode.ConfigurationTarget.Workspace); await config.update('permissions.yoloMode', true, vscode.ConfigurationTarget.Workspace);
console.log('YOLO Mode enabled - all future permissions will be skipped'); console.log('YOLO Mode enabled - all future permissions will be skipped');
// Send updated settings to UI // Send updated settings to UI
this._sendCurrentSettings(); this._sendCurrentSettings();
} catch (error) { } catch (error) {
console.error('Error enabling YOLO mode:', error); console.error('Error enabling YOLO mode:', error);
} }
@@ -2110,7 +2151,7 @@ class ClaudeChatProvider {
private async _updateSettings(settings: { [key: string]: any }): Promise<void> { private async _updateSettings(settings: { [key: string]: any }): Promise<void> {
const config = vscode.workspace.getConfiguration('claudeCodeChat'); const config = vscode.workspace.getConfiguration('claudeCodeChat');
try { try {
for (const [key, value] of Object.entries(settings)) { for (const [key, value] of Object.entries(settings)) {
if (key === 'permissions.yoloMode') { if (key === 'permissions.yoloMode') {
@@ -2121,7 +2162,7 @@ class ClaudeChatProvider {
await config.update(key, value, vscode.ConfigurationTarget.Global); await config.update(key, value, vscode.ConfigurationTarget.Global);
} }
} }
console.log('Settings updated:', settings); console.log('Settings updated:', settings);
} catch (error) { } catch (error) {
console.error('Failed to update settings:', error); console.error('Failed to update settings:', error);
@@ -2147,10 +2188,10 @@ class ClaudeChatProvider {
if (validModels.includes(model)) { if (validModels.includes(model)) {
this._selectedModel = model; this._selectedModel = model;
console.log('Model selected:', model); console.log('Model selected:', model);
// Store the model preference in workspace state // Store the model preference in workspace state
this._context.workspaceState.update('claude.selectedModel', model); this._context.workspaceState.update('claude.selectedModel', model);
// Show confirmation // Show confirmation
vscode.window.showInformationMessage(`Claude model switched to: ${model.charAt(0).toUpperCase() + model.slice(1)}`); vscode.window.showInformationMessage(`Claude model switched to: ${model.charAt(0).toUpperCase() + model.slice(1)}`);
} else { } else {
@@ -2168,7 +2209,7 @@ class ClaudeChatProvider {
// Build command arguments // Build command arguments
const args = ['/model']; const args = ['/model'];
// Add session resume if we have a current session // Add session resume if we have a current session
if (this._currentSessionId) { if (this._currentSessionId) {
args.push('--resume', this._currentSessionId); args.push('--resume', this._currentSessionId);
@@ -2205,7 +2246,7 @@ class ClaudeChatProvider {
// Build command arguments // Build command arguments
const args = [`/${command}`]; const args = [`/${command}`];
// Add session resume if we have a current session // Add session resume if we have a current session
if (this._currentSessionId) { if (this._currentSessionId) {
args.push('--resume', this._currentSessionId); args.push('--resume', this._currentSessionId);
@@ -2236,7 +2277,7 @@ class ClaudeChatProvider {
private _sendPlatformInfo() { private _sendPlatformInfo() {
const platform = process.platform; const platform = process.platform;
const dismissed = this._context.globalState.get<boolean>('wslAlertDismissed', false); const dismissed = this._context.globalState.get<boolean>('wslAlertDismissed', false);
// Get WSL configuration // Get WSL configuration
const config = vscode.workspace.getConfiguration('claudeCodeChat'); const config = vscode.workspace.getConfiguration('claudeCodeChat');
const wslEnabled = config.get<boolean>('wsl.enabled', false); const wslEnabled = config.get<boolean>('wsl.enabled', false);
@@ -2270,23 +2311,23 @@ class ClaudeChatProvider {
private async _createImageFile(imageData: string, imageType: string) { private async _createImageFile(imageData: string, imageType: string) {
try { try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {return;} if (!workspaceFolder) { return; }
// Extract base64 data from data URL // Extract base64 data from data URL
const base64Data = imageData.split(',')[1]; const base64Data = imageData.split(',')[1];
const buffer = Buffer.from(base64Data, 'base64'); const buffer = Buffer.from(base64Data, 'base64');
// Get file extension from image type // Get file extension from image type
const extension = imageType.split('/')[1] || 'png'; const extension = imageType.split('/')[1] || 'png';
// Create unique filename with timestamp // Create unique filename with timestamp
const timestamp = Date.now(); const timestamp = Date.now();
const imageFileName = `image_${timestamp}.${extension}`; const imageFileName = `image_${timestamp}.${extension}`;
// Create images folder in workspace .claude directory // Create images folder in workspace .claude directory
const imagesDir = vscode.Uri.joinPath(workspaceFolder.uri, '.claude', 'claude-code-chat-images'); const imagesDir = vscode.Uri.joinPath(workspaceFolder.uri, '.claude', 'claude-code-chat-images');
await vscode.workspace.fs.createDirectory(imagesDir); await vscode.workspace.fs.createDirectory(imagesDir);
// Create .gitignore to ignore all images // Create .gitignore to ignore all images
const gitignorePath = vscode.Uri.joinPath(imagesDir, '.gitignore'); const gitignorePath = vscode.Uri.joinPath(imagesDir, '.gitignore');
try { try {
@@ -2296,11 +2337,11 @@ class ClaudeChatProvider {
const gitignoreContent = new TextEncoder().encode('*\n'); const gitignoreContent = new TextEncoder().encode('*\n');
await vscode.workspace.fs.writeFile(gitignorePath, gitignoreContent); await vscode.workspace.fs.writeFile(gitignorePath, gitignoreContent);
} }
// Create the image file // Create the image file
const imagePath = vscode.Uri.joinPath(imagesDir, imageFileName); const imagePath = vscode.Uri.joinPath(imagesDir, imageFileName);
await vscode.workspace.fs.writeFile(imagePath, buffer); await vscode.workspace.fs.writeFile(imagePath, buffer);
// Send the file path back to webview // Send the file path back to webview
this._postMessage({ this._postMessage({
type: 'imagePath', type: 'imagePath',
@@ -2308,7 +2349,7 @@ class ClaudeChatProvider {
filePath: imagePath.fsPath filePath: imagePath.fsPath
} }
}); });
} catch (error) { } catch (error) {
console.error('Error creating image file:', error); console.error('Error creating image file:', error);
vscode.window.showErrorMessage('Failed to create image file'); vscode.window.showErrorMessage('Failed to create image file');