diff --git a/packages/vscode-ide-companion/src/WebViewProvider.ts b/packages/vscode-ide-companion/src/WebViewProvider.ts index 43f323d3..58a53227 100644 --- a/packages/vscode-ide-companion/src/WebViewProvider.ts +++ b/packages/vscode-ide-companion/src/WebViewProvider.ts @@ -12,6 +12,7 @@ import { import { ConversationStore } from './storage/ConversationStore.js'; import type { AcpPermissionRequest } from './shared/acpTypes.js'; import { AuthStateManager } from './auth/AuthStateManager.js'; +import { CliDetector } from './utils/CliDetector.js'; export class WebViewProvider { private panel: vscode.WebviewPanel | null = null; @@ -135,26 +136,51 @@ export class WebViewProvider { const qwenEnabled = config.get('qwen.enabled', true); if (qwenEnabled) { - try { - console.log('[WebViewProvider] Connecting to agent...'); - const authInfo = await this.authStateManager.getAuthInfo(); - console.log('[WebViewProvider] Auth cache status:', authInfo); + // Check if CLI is installed before attempting to connect + const cliDetection = await CliDetector.detectQwenCli(); - await this.agentManager.connect(workingDir, this.authStateManager); - console.log('[WebViewProvider] Agent connected successfully'); - this.agentInitialized = true; - - // Load messages from the current Qwen session - await this.loadCurrentSessionMessages(); - } catch (error) { - console.error('[WebViewProvider] Agent connection error:', error); - // Clear auth cache on error - await this.authStateManager.clearAuthState(); - vscode.window.showWarningMessage( - `Failed to connect to Qwen CLI: ${error}\nYou can still use the chat UI, but messages won't be sent to AI.`, + if (!cliDetection.isInstalled) { + console.log( + '[WebViewProvider] Qwen CLI not detected, skipping agent connection', ); - // Fallback to empty conversation + console.log( + '[WebViewProvider] CLI detection error:', + cliDetection.error, + ); + + // Show VSCode notification with installation option + await this.promptCliInstallation(); + + // Initialize empty conversation (can still browse history) await this.initializeEmptyConversation(); + } else { + console.log( + '[WebViewProvider] Qwen CLI detected, attempting connection...', + ); + console.log('[WebViewProvider] CLI path:', cliDetection.cliPath); + console.log('[WebViewProvider] CLI version:', cliDetection.version); + + try { + console.log('[WebViewProvider] Connecting to agent...'); + const authInfo = await this.authStateManager.getAuthInfo(); + console.log('[WebViewProvider] Auth cache status:', authInfo); + + await this.agentManager.connect(workingDir, this.authStateManager); + console.log('[WebViewProvider] Agent connected successfully'); + this.agentInitialized = true; + + // Load messages from the current Qwen session + await this.loadCurrentSessionMessages(); + } catch (error) { + console.error('[WebViewProvider] Agent connection error:', error); + // Clear auth cache on error (might be auth issue) + await this.authStateManager.clearAuthState(); + vscode.window.showWarningMessage( + `Failed to connect to Qwen CLI: ${error}\nYou can still use the chat UI, but messages won't be sent to AI.`, + ); + // Fallback to empty conversation + await this.initializeEmptyConversation(); + } } } else { console.log('[WebViewProvider] Qwen agent is disabled in settings'); @@ -170,6 +196,37 @@ export class WebViewProvider { } } + private async checkCliInstallation(): Promise { + try { + const result = await CliDetector.detectQwenCli(); + + this.sendMessageToWebView({ + type: 'cliDetectionResult', + data: { + isInstalled: result.isInstalled, + cliPath: result.cliPath, + version: result.version, + error: result.error, + installInstructions: result.isInstalled + ? undefined + : CliDetector.getInstallationInstructions(), + }, + }); + + if (!result.isInstalled) { + console.log('[WebViewProvider] Qwen CLI not detected:', result.error); + } else { + console.log( + '[WebViewProvider] Qwen CLI detected:', + result.cliPath, + result.version, + ); + } + } catch (error) { + console.error('[WebViewProvider] CLI detection error:', error); + } + } + private async loadCurrentSessionMessages(): Promise { try { // Get the current active session ID @@ -223,6 +280,116 @@ export class WebViewProvider { } } + private async promptCliInstallation(): Promise { + const selection = await vscode.window.showWarningMessage( + 'Qwen Code CLI is not installed. You can browse conversation history, but cannot send new messages.', + 'Install Now', + 'View Documentation', + 'Remind Me Later', + ); + + if (selection === 'Install Now') { + await this.installQwenCli(); + } else if (selection === 'View Documentation') { + vscode.env.openExternal( + vscode.Uri.parse('https://github.com/QwenLM/qwen-code#installation'), + ); + } + } + + private async installQwenCli(): Promise { + try { + // Show progress notification + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: 'Installing Qwen Code CLI', + cancellable: false, + }, + async (progress) => { + progress.report({ + message: 'Running: npm install -g @qwen-code/qwen-code@latest', + }); + + const { exec } = await import('child_process'); + const { promisify } = await import('util'); + const execAsync = promisify(exec); + + try { + const { stdout, stderr } = await execAsync( + 'npm install -g @qwen-code/qwen-code@latest', + { timeout: 120000 }, // 2 minutes timeout + ); + + console.log('[WebViewProvider] Installation output:', stdout); + if (stderr) { + console.warn('[WebViewProvider] Installation stderr:', stderr); + } + + // Clear cache and recheck + CliDetector.clearCache(); + const detection = await CliDetector.detectQwenCli(); + + if (detection.isInstalled) { + vscode.window + .showInformationMessage( + `✅ Qwen Code CLI installed successfully! Version: ${detection.version}`, + 'Reload Window', + ) + .then((selection) => { + if (selection === 'Reload Window') { + vscode.commands.executeCommand( + 'workbench.action.reloadWindow', + ); + } + }); + + // Update webview with new detection result + await this.checkCliInstallation(); + } else { + throw new Error( + 'Installation completed but CLI still not detected', + ); + } + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + console.error( + '[WebViewProvider] Installation failed:', + errorMessage, + ); + + vscode.window + .showErrorMessage( + `Failed to install Qwen Code CLI: ${errorMessage}`, + 'Try Manual Installation', + 'View Documentation', + ) + .then((selection) => { + if (selection === 'Try Manual Installation') { + const terminal = vscode.window.createTerminal( + 'Qwen Code Installation', + ); + terminal.show(); + terminal.sendText( + 'npm install -g @qwen-code/qwen-code@latest', + ); + } else if (selection === 'View Documentation') { + vscode.env.openExternal( + vscode.Uri.parse( + 'https://github.com/QwenLM/qwen-code#installation', + ), + ); + } + }); + } + }, + ); + } catch (error) { + console.error('[WebViewProvider] Install CLI error:', error); + } + } + private async initializeEmptyConversation(): Promise { try { console.log('[WebViewProvider] Initializing empty conversation'); @@ -299,6 +466,12 @@ export class WebViewProvider { await this.handleSwitchQwenSession(message.data?.sessionId || ''); break; + case 'recheckCli': + // Clear cache and recheck CLI installation + CliDetector.clearCache(); + await this.checkCliInstallation(); + break; + default: console.warn('[WebViewProvider] Unknown message type:', message.type); break; diff --git a/packages/vscode-ide-companion/src/utils/CliDetector.ts b/packages/vscode-ide-companion/src/utils/CliDetector.ts new file mode 100644 index 00000000..c09ed250 --- /dev/null +++ b/packages/vscode-ide-companion/src/utils/CliDetector.ts @@ -0,0 +1,129 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +export interface CliDetectionResult { + isInstalled: boolean; + cliPath?: string; + version?: string; + error?: string; +} + +/** + * Detects if Qwen Code CLI is installed and accessible + */ +export class CliDetector { + private static cachedResult: CliDetectionResult | null = null; + private static lastCheckTime: number = 0; + private static readonly CACHE_DURATION_MS = 30000; // 30 seconds + + /** + * Checks if the Qwen Code CLI is installed + * @param forceRefresh - Force a new check, ignoring cache + * @returns Detection result with installation status and details + */ + static async detectQwenCli( + forceRefresh = false, + ): Promise { + const now = Date.now(); + + // Return cached result if available and not expired + if ( + !forceRefresh && + this.cachedResult && + now - this.lastCheckTime < this.CACHE_DURATION_MS + ) { + return this.cachedResult; + } + + try { + const isWindows = process.platform === 'win32'; + const whichCommand = isWindows ? 'where' : 'which'; + + // Check if qwen command exists + try { + const { stdout } = await execAsync(`${whichCommand} qwen`, { + timeout: 5000, + }); + const cliPath = stdout.trim().split('\n')[0]; + + // Try to get version + let version: string | undefined; + try { + const { stdout: versionOutput } = await execAsync('qwen --version', { + timeout: 5000, + }); + version = versionOutput.trim(); + } catch { + // Version check failed, but CLI is installed + } + + this.cachedResult = { + isInstalled: true, + cliPath, + version, + }; + this.lastCheckTime = now; + return this.cachedResult; + } catch (_error) { + // CLI not found + this.cachedResult = { + isInstalled: false, + error: `Qwen Code CLI not found in PATH. Please install it using: npm install -g @qwen-code/qwen-code@latest`, + }; + this.lastCheckTime = now; + return this.cachedResult; + } + } catch (error) { + const errorMessage = + error instanceof Error ? error.message : String(error); + this.cachedResult = { + isInstalled: false, + error: `Failed to detect Qwen Code CLI: ${errorMessage}`, + }; + this.lastCheckTime = now; + return this.cachedResult; + } + } + + /** + * Clears the cached detection result + */ + static clearCache(): void { + this.cachedResult = null; + this.lastCheckTime = 0; + } + + /** + * Gets installation instructions based on the platform + */ + static getInstallationInstructions(): { + title: string; + steps: string[]; + documentationUrl: string; + } { + return { + title: 'Qwen Code CLI is not installed', + steps: [ + 'Install via npm:', + ' npm install -g @qwen-code/qwen-code@latest', + '', + 'Or install from source:', + ' git clone https://github.com/QwenLM/qwen-code.git', + ' cd qwen-code', + ' npm install', + ' npm install -g .', + '', + 'After installation, reload VS Code or restart the extension.', + ], + documentationUrl: 'https://github.com/QwenLM/qwen-code#installation', + }; + } +}