mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
feat(vscode-ide-companion): 添加 Qwen Code CLI 安装检测和提示功能
- 新增 CliDetector 类用于检测 Qwen Code CLI 安装状态 - 在 WebViewProvider 中集成 CLI 检测逻辑 - 添加 CLI 未安装时的提示和安装引导功能 - 优化 agent 连接流程,增加 CLI 安装检测步骤
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
import { ConversationStore } from './storage/ConversationStore.js';
|
import { ConversationStore } from './storage/ConversationStore.js';
|
||||||
import type { AcpPermissionRequest } from './shared/acpTypes.js';
|
import type { AcpPermissionRequest } from './shared/acpTypes.js';
|
||||||
import { AuthStateManager } from './auth/AuthStateManager.js';
|
import { AuthStateManager } from './auth/AuthStateManager.js';
|
||||||
|
import { CliDetector } from './utils/CliDetector.js';
|
||||||
|
|
||||||
export class WebViewProvider {
|
export class WebViewProvider {
|
||||||
private panel: vscode.WebviewPanel | null = null;
|
private panel: vscode.WebviewPanel | null = null;
|
||||||
@@ -135,26 +136,51 @@ export class WebViewProvider {
|
|||||||
const qwenEnabled = config.get<boolean>('qwen.enabled', true);
|
const qwenEnabled = config.get<boolean>('qwen.enabled', true);
|
||||||
|
|
||||||
if (qwenEnabled) {
|
if (qwenEnabled) {
|
||||||
try {
|
// Check if CLI is installed before attempting to connect
|
||||||
console.log('[WebViewProvider] Connecting to agent...');
|
const cliDetection = await CliDetector.detectQwenCli();
|
||||||
const authInfo = await this.authStateManager.getAuthInfo();
|
|
||||||
console.log('[WebViewProvider] Auth cache status:', authInfo);
|
|
||||||
|
|
||||||
await this.agentManager.connect(workingDir, this.authStateManager);
|
if (!cliDetection.isInstalled) {
|
||||||
console.log('[WebViewProvider] Agent connected successfully');
|
console.log(
|
||||||
this.agentInitialized = true;
|
'[WebViewProvider] Qwen CLI not detected, skipping agent connection',
|
||||||
|
|
||||||
// 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.`,
|
|
||||||
);
|
);
|
||||||
// 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();
|
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 {
|
} else {
|
||||||
console.log('[WebViewProvider] Qwen agent is disabled in settings');
|
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> {
|
private async loadCurrentSessionMessages(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Get the current active session ID
|
// 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> {
|
private async initializeEmptyConversation(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
console.log('[WebViewProvider] Initializing empty conversation');
|
console.log('[WebViewProvider] Initializing empty conversation');
|
||||||
@@ -299,6 +466,12 @@ export class WebViewProvider {
|
|||||||
await this.handleSwitchQwenSession(message.data?.sessionId || '');
|
await this.handleSwitchQwenSession(message.data?.sessionId || '');
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'recheckCli':
|
||||||
|
// Clear cache and recheck CLI installation
|
||||||
|
CliDetector.clearCache();
|
||||||
|
await this.checkCliInstallation();
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.warn('[WebViewProvider] Unknown message type:', message.type);
|
console.warn('[WebViewProvider] Unknown message type:', message.type);
|
||||||
break;
|
break;
|
||||||
|
|||||||
129
packages/vscode-ide-companion/src/utils/CliDetector.ts
Normal file
129
packages/vscode-ide-companion/src/utils/CliDetector.ts
Normal 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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user