feat(vscode-ide-companion): 添加 Qwen Code CLI 安装检测和提示功能

- 新增 CliDetector 类用于检测 Qwen Code CLI 安装状态
- 在 WebViewProvider 中集成 CLI 检测逻辑
- 添加 CLI 未安装时的提示和安装引导功能
- 优化 agent 连接流程,增加 CLI 安装检测步骤
This commit is contained in:
yiliang114
2025-11-18 01:52:46 +08:00
parent 28892996b3
commit d22d07a840
2 changed files with 319 additions and 17 deletions

View File

@@ -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<boolean>('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<void> {
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<void> {
try {
// Get the current active session ID
@@ -223,6 +280,116 @@ export class WebViewProvider {
}
}
private async promptCliInstallation(): Promise<void> {
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<void> {
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<void> {
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;

View File

@@ -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<CliDetectionResult> {
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',
};
}
}