chore(vscode-ide-companion): wip

This commit is contained in:
yiliang114
2025-12-13 17:50:15 +08:00
parent 9e392b3035
commit 0ac191e2db
9 changed files with 393 additions and 294 deletions

View File

@@ -50,15 +50,9 @@ export class CliDetector {
this.cachedResult && this.cachedResult &&
now - this.lastCheckTime < this.CACHE_DURATION_MS now - this.lastCheckTime < this.CACHE_DURATION_MS
) { ) {
console.log('[CliDetector] Returning cached result');
return this.cachedResult; return this.cachedResult;
} }
console.log(
'[CliDetector] Starting lightweight CLI detection, current PATH:',
process.env.PATH,
);
try { try {
const isWindows = process.platform === 'win32'; const isWindows = process.platform === 'win32';
const whichCommand = isWindows ? 'where' : 'which'; const whichCommand = isWindows ? 'where' : 'which';
@@ -70,11 +64,6 @@ export class CliDetector {
? `${whichCommand} qwen` ? `${whichCommand} qwen`
: `${whichCommand} qwen`; : `${whichCommand} qwen`;
console.log(
'[CliDetector] Detecting CLI with lightweight command:',
detectionCommand,
);
// Execute command to detect CLI path, set shorter timeout (3 seconds) // Execute command to detect CLI path, set shorter timeout (3 seconds)
const { stdout } = await execAsync(detectionCommand, { const { stdout } = await execAsync(detectionCommand, {
timeout: 3000, // Reduced timeout for faster detection timeout: 3000, // Reduced timeout for faster detection
@@ -88,8 +77,6 @@ export class CliDetector {
.filter((line) => line.trim()); .filter((line) => line.trim());
const cliPath = lines[0]; // Take only the first path const cliPath = lines[0]; // Take only the first path
console.log('[CliDetector] Found CLI at:', cliPath);
// Build successful detection result, note no version information // Build successful detection result, note no version information
this.cachedResult = { this.cachedResult = {
isInstalled: true, isInstalled: true,

View File

@@ -5,121 +5,154 @@
*/ */
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { CliContextManager } from './cliContextManager.js'; import { CliDetector, type CliDetectionResult } from './cliDetector.js';
import { CliVersionManager } from './cliVersionManager.js'; import { CliVersionManager } from './cliVersionManager.js';
import { MIN_CLI_VERSION_FOR_SESSION_METHODS } from './cliVersionManager.js'; import semver from 'semver';
import type { CliVersionInfo } from './cliVersionManager.js';
// Track which versions have already been warned about to avoid repetitive warnings
// Using a Map with timestamps to allow warnings to be shown again after a certain period
const warnedVersions = new Map<string, number>();
const WARNING_COOLDOWN_MS = 24 * 60 * 60 * 1000; // 24 hours cooldown
/** /**
* Check CLI version and show warning if below minimum requirement * CLI Version Checker
* Provides an "Upgrade Now" option for unsupported versions
* *
* @returns Version information * Handles CLI version checking with throttling to prevent frequent notifications.
* This class manages version checking and provides version information without
* constantly bothering the user with popups.
*/ */
export async function checkCliVersionAndWarn(): Promise<CliVersionInfo> { export class CliVersionChecker {
private static instance: CliVersionChecker;
private lastNotificationTime: number = 0;
private static readonly NOTIFICATION_COOLDOWN_MS = 300000; // 5 minutes cooldown
private context: vscode.ExtensionContext;
private constructor(context: vscode.ExtensionContext) {
this.context = context;
}
/**
* Get singleton instance
*/
static getInstance(context?: vscode.ExtensionContext): CliVersionChecker {
if (!CliVersionChecker.instance && context) {
CliVersionChecker.instance = new CliVersionChecker(context);
}
return CliVersionChecker.instance;
}
/**
* Check CLI version with cooldown to prevent spamming notifications
*
* @param showNotifications - Whether to show notifications for issues
* @returns Promise with version check result
*/
async checkCliVersion(showNotifications: boolean = true): Promise<{
isInstalled: boolean;
version?: string;
isSupported: boolean;
needsUpdate: boolean;
error?: string;
}> {
try { try {
const cliContextManager = CliContextManager.getInstance(); // Detect CLI installation
const versionInfo = const detectionResult: CliDetectionResult =
await CliVersionManager.getInstance().detectCliVersion(true); await CliDetector.detectQwenCli();
cliContextManager.setCurrentVersionInfo(versionInfo);
if (!versionInfo.isSupported) { if (!detectionResult.isInstalled) {
// Only show warning if we haven't already warned about this specific version recently if (showNotifications && this.canShowNotification()) {
const versionKey = versionInfo.version || 'unknown'; vscode.window.showWarningMessage(
const lastWarningTime = warnedVersions.get(versionKey); `Qwen Code CLI not found. Please install it using: npm install -g @qwen-code/qwen-code@latest`,
const currentTime = Date.now();
// Show warning if we haven't warned about this version or if enough time has passed
if (
!lastWarningTime ||
currentTime - lastWarningTime > WARNING_COOLDOWN_MS
) {
// Wait to determine release version number
const selection = await vscode.window.showWarningMessage(
`Qwen Code CLI version ${versionInfo.version} is below the minimum required version. Some features may not work properly. Please upgrade to version ${MIN_CLI_VERSION_FOR_SESSION_METHODS} or later.`,
'Upgrade Now',
); );
this.lastNotificationTime = Date.now();
// Handle the user's selection
if (selection === 'Upgrade Now') {
// Open terminal and run npm install command
const terminal = vscode.window.createTerminal(
'Qwen Code CLI Upgrade',
);
terminal.show();
terminal.sendText('npm install -g @qwen-code/qwen-code@latest');
} }
// Update the last warning time
warnedVersions.set(versionKey, currentTime);
}
}
return versionInfo;
} catch (error) {
console.error('[CliVersionChecker] Failed to check CLI version:', error);
// Return a default version info in case of error
return { return {
version: undefined, isInstalled: false,
error: detectionResult.error,
isSupported: false, isSupported: false,
features: { needsUpdate: false,
supportsSessionList: false, };
supportsSessionLoad: false, }
},
detectionResult: { // Get version information
const versionManager = CliVersionManager.getInstance();
const versionInfo = await versionManager.detectCliVersion();
const currentVersion = detectionResult.version;
const isSupported = versionInfo.isSupported;
// Check if update is needed (version is too old)
const minRequiredVersion = '0.5.0'; // This should match MIN_CLI_VERSION_FOR_SESSION_METHODS from CliVersionManager
const needsUpdate = currentVersion
? !semver.satisfies(currentVersion, `>=${minRequiredVersion}`)
: false;
// Show notification only if needed and within cooldown period
if (showNotifications && !isSupported && this.canShowNotification()) {
vscode.window.showWarningMessage(
`Qwen Code CLI version is outdated. Current: ${currentVersion || 'unknown'}, Minimum required: ${minRequiredVersion}. Please update using: npm install -g @qwen-code/qwen-code@latest`,
);
this.lastNotificationTime = Date.now();
}
return {
isInstalled: true,
version: currentVersion,
isSupported,
needsUpdate,
};
} catch (error) {
console.error('[CliVersionChecker] Version check failed:', error);
if (showNotifications && this.canShowNotification()) {
vscode.window.showErrorMessage(
`Failed to check Qwen Code CLI version: ${error instanceof Error ? error.message : String(error)}`,
);
this.lastNotificationTime = Date.now();
}
return {
isInstalled: false, isInstalled: false,
error: error instanceof Error ? error.message : String(error), error: error instanceof Error ? error.message : String(error),
}, isSupported: false,
needsUpdate: false,
}; };
} }
} }
/** /**
* Process server version information from initialize response * Check if notification can be shown based on cooldown period
*
* @param init - Initialize response object
*/ */
export function processServerVersion(init: unknown): void { private canShowNotification(): boolean {
try { return (
const obj = (init || {}) as Record<string, unknown>; Date.now() - this.lastNotificationTime >
CliVersionChecker.NOTIFICATION_COOLDOWN_MS
// Extract version information from initialize response
const serverVersion =
obj['version'] || obj['serverVersion'] || obj['cliVersion'];
if (serverVersion) {
console.log(
'[CliVersionChecker] Server version from initialize response:',
serverVersion,
); );
// Update CLI context with version info from server
const cliContextManager = CliContextManager.getInstance();
// Create version info directly without async call
const versionInfo: CliVersionInfo = {
version: String(serverVersion),
isSupported: true, // Assume supported for now
features: {
supportsSessionList: true,
supportsSessionLoad: true,
},
detectionResult: {
isInstalled: true,
version: String(serverVersion),
},
};
cliContextManager.setCurrentVersionInfo(versionInfo);
} }
} catch (error) {
console.error( /**
'[CliVersionChecker] Failed to process server version:', * Clear notification cooldown (allows immediate next notification)
error, */
); clearCooldown(): void {
this.lastNotificationTime = 0;
}
/**
* Get version status for display in status bar or other UI elements
*/
async getVersionStatus(): Promise<string> {
try {
const versionManager = CliVersionManager.getInstance();
const versionInfo = await versionManager.detectCliVersion();
if (!versionInfo.detectionResult.isInstalled) {
return 'CLI: Not installed';
}
const version = versionInfo.version || 'Unknown';
if (!versionInfo.isSupported) {
return `CLI: ${version} (Outdated)`;
}
return `CLI: ${version}`;
} catch (_) {
return 'CLI: Error';
}
} }
} }

View File

@@ -43,6 +43,14 @@ vi.mock('vscode', () => ({
registerWebviewPanelSerializer: vi.fn(() => ({ registerWebviewPanelSerializer: vi.fn(() => ({
dispose: vi.fn(), dispose: vi.fn(),
})), })),
createStatusBarItem: vi.fn(() => ({
text: '',
tooltip: '',
command: '',
show: vi.fn(),
hide: vi.fn(),
dispose: vi.fn(),
})),
}, },
workspace: { workspace: {
workspaceFolders: [], workspaceFolders: [],
@@ -58,6 +66,10 @@ vi.mock('vscode', () => ({
Uri: { Uri: {
joinPath: vi.fn(), joinPath: vi.fn(),
}, },
StatusBarAlignment: {
Left: 1,
Right: 2,
},
ExtensionMode: { ExtensionMode: {
Development: 1, Development: 1,
Production: 2, Production: 2,

View File

@@ -26,7 +26,6 @@ import { QwenSessionUpdateHandler } from './qwenSessionUpdateHandler.js';
import { CliContextManager } from '../cli/cliContextManager.js'; import { CliContextManager } from '../cli/cliContextManager.js';
import { authMethod } from '../types/acpTypes.js'; import { authMethod } from '../types/acpTypes.js';
import { MIN_CLI_VERSION_FOR_SESSION_METHODS } from '../cli/cliVersionManager.js'; import { MIN_CLI_VERSION_FOR_SESSION_METHODS } from '../cli/cliVersionManager.js';
import { processServerVersion } from '../cli/cliVersionChecker.js';
import { isAuthenticationRequiredError } from '../utils/authErrors.js'; import { isAuthenticationRequiredError } from '../utils/authErrors.js';
import { handleAuthenticateUpdate } from '../utils/authNotificationHandler.js'; import { handleAuthenticateUpdate } from '../utils/authNotificationHandler.js';
@@ -163,9 +162,6 @@ export class QwenAgentManager {
// Initialize callback to surface available modes and current mode to UI // Initialize callback to surface available modes and current mode to UI
this.connection.onInitialized = (init: unknown) => { this.connection.onInitialized = (init: unknown) => {
try { try {
// Process server version information
processServerVersion(init);
const obj = (init || {}) as Record<string, unknown>; const obj = (init || {}) as Record<string, unknown>;
const modes = obj['modes'] as const modes = obj['modes'] as
| { | {
@@ -288,17 +284,6 @@ export class QwenAgentManager {
'[QwenAgentManager] Getting session list with version-aware strategy', '[QwenAgentManager] Getting session list with version-aware strategy',
); );
// Check if CLI supports session/list method
const cliContextManager = CliContextManager.getInstance();
const supportsSessionList = cliContextManager.supportsSessionList();
console.log(
'[QwenAgentManager] CLI supports session/list:',
supportsSessionList,
);
// Try ACP method first if supported
if (supportsSessionList) {
try { try {
console.log( console.log(
'[QwenAgentManager] Attempting to get session list via ACP method', '[QwenAgentManager] Attempting to get session list via ACP method',
@@ -353,7 +338,6 @@ export class QwenAgentManager {
error, error,
); );
} }
}
// Always fall back to file system method // Always fall back to file system method
try { try {
@@ -409,10 +393,6 @@ export class QwenAgentManager {
const size = params?.size ?? 20; const size = params?.size ?? 20;
const cursor = params?.cursor; const cursor = params?.cursor;
const cliContextManager = CliContextManager.getInstance();
const supportsSessionList = cliContextManager.supportsSessionList();
if (supportsSessionList) {
try { try {
const response = await this.connection.listSessions({ const response = await this.connection.listSessions({
size, size,
@@ -428,9 +408,7 @@ export class QwenAgentManager {
const responseObject = res as { const responseObject = res as {
items?: Array<Record<string, unknown>>; items?: Array<Record<string, unknown>>;
}; };
items = Array.isArray(responseObject.items) items = Array.isArray(responseObject.items) ? responseObject.items : [];
? responseObject.items
: [];
} }
const mapped = items.map((item) => ({ const mapped = items.map((item) => ({
@@ -459,13 +437,9 @@ export class QwenAgentManager {
return { sessions: mapped, nextCursor, hasMore }; return { sessions: mapped, nextCursor, hasMore };
} catch (error) { } catch (error) {
console.warn( console.warn('[QwenAgentManager] Paged ACP session list failed:', error);
'[QwenAgentManager] Paged ACP session list failed:',
error,
);
// fall through to file system // fall through to file system
} }
}
// Fallback: file system for current project only (to match ACP semantics) // Fallback: file system for current project only (to match ACP semantics)
try { try {
@@ -513,9 +487,6 @@ export class QwenAgentManager {
*/ */
async getSessionMessages(sessionId: string): Promise<ChatMessage[]> { async getSessionMessages(sessionId: string): Promise<ChatMessage[]> {
try { try {
// Prefer reading CLI's JSONL if we can find filePath from session/list
const cliContextManager = CliContextManager.getInstance();
if (cliContextManager.supportsSessionList()) {
try { try {
const list = await this.getSessionList(); const list = await this.getSessionList();
const item = list.find( const item = list.find(
@@ -539,7 +510,6 @@ export class QwenAgentManager {
} catch (e) { } catch (e) {
console.warn('[QwenAgentManager] JSONL read path lookup failed:', e); console.warn('[QwenAgentManager] JSONL read path lookup failed:', e);
} }
}
// Fallback: legacy JSON session files // Fallback: legacy JSON session files
const session = await this.sessionReader.getSession( const session = await this.sessionReader.getSession(

View File

@@ -15,7 +15,6 @@ import type { QwenSessionReader } from '../services/qwenSessionReader.js';
import { CliDetector } from '../cli/cliDetector.js'; import { CliDetector } from '../cli/cliDetector.js';
import { authMethod } from '../types/acpTypes.js'; import { authMethod } from '../types/acpTypes.js';
import { isAuthenticationRequiredError } from '../utils/authErrors.js'; import { isAuthenticationRequiredError } from '../utils/authErrors.js';
import { checkCliVersionAndWarn } from '../cli/cliVersionChecker.js';
export interface QwenConnectionResult { export interface QwenConnectionResult {
sessionCreated: boolean; sessionCreated: boolean;
@@ -50,18 +49,15 @@ export class QwenConnectionHandler {
let sessionCreated = false; let sessionCreated = false;
let requiresAuth = false; let requiresAuth = false;
// Lightweight check if CLI exists (without version info for faster performance) // Check if CLI exists using standard detection (with cached results for better performance)
const detectionResult = await CliDetector.detectQwenCliLightweight( const detectionResult = await CliDetector.detectQwenCli(
/* forceRefresh */ true, /* forceRefresh */ false, // Use cached results when available for better performance
); );
if (!detectionResult.isInstalled) { if (!detectionResult.isInstalled) {
throw new Error(detectionResult.error || 'Qwen CLI not found'); throw new Error(detectionResult.error || 'Qwen CLI not found');
} }
console.log('[QwenAgentManager] CLI detected at:', detectionResult.cliPath); console.log('[QwenAgentManager] CLI detected at:', detectionResult.cliPath);
// Show warning if CLI version is below minimum requirement
await checkCliVersionAndWarn();
// Build extra CLI arguments (only essential parameters) // Build extra CLI arguments (only essential parameters)
const extraArgs: string[] = []; const extraArgs: string[] = [];

View File

@@ -7,6 +7,9 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import type { AuthenticateUpdateNotification } from '../types/acpTypes.js'; import type { AuthenticateUpdateNotification } from '../types/acpTypes.js';
// Store reference to the authentication notification to allow auto-closing
let authNotificationDisposable: { dispose: () => void } | null = null;
/** /**
* Handle authentication update notifications by showing a VS Code notification * Handle authentication update notifications by showing a VS Code notification
* with the authentication URI and a copy button. * with the authentication URI and a copy button.
@@ -18,14 +21,27 @@ export function handleAuthenticateUpdate(
): void { ): void {
const authUri = data._meta.authUri; const authUri = data._meta.authUri;
// Dismiss any existing authentication notification
if (authNotificationDisposable) {
authNotificationDisposable.dispose();
authNotificationDisposable = null;
}
// Show an information message with the auth URI and copy button // Show an information message with the auth URI and copy button
vscode.window const notificationPromise = vscode.window.showInformationMessage(
.showInformationMessage(
`Qwen Code needs authentication. Click the button below to open the authentication page or copy the link to your browser.`, `Qwen Code needs authentication. Click the button below to open the authentication page or copy the link to your browser.`,
'Open in Browser', 'Open in Browser',
'Copy Link', 'Copy Link',
) );
.then((selection) => {
// Create a simple disposable object
authNotificationDisposable = {
dispose: () => {
// We can't actually cancel the promise, but we can clear our reference
},
};
notificationPromise.then((selection) => {
if (selection === 'Open in Browser') { if (selection === 'Open in Browser') {
// Open the authentication URI in the default browser // Open the authentication URI in the default browser
vscode.env.openExternal(vscode.Uri.parse(authUri)); vscode.env.openExternal(vscode.Uri.parse(authUri));
@@ -36,5 +52,18 @@ export function handleAuthenticateUpdate(
'Authentication link copied to clipboard!', 'Authentication link copied to clipboard!',
); );
} }
// Clear the notification reference after user interaction
authNotificationDisposable = null;
}); });
} }
/**
* Dismiss the authentication notification if it's currently shown
*/
export function dismissAuthenticateUpdate(): void {
if (authNotificationDisposable) {
authNotificationDisposable.dispose();
authNotificationDisposable = null;
}
}

View File

@@ -69,6 +69,7 @@ export const App: React.FC = () => {
} | null>(null); } | null>(null);
const [planEntries, setPlanEntries] = useState<PlanEntry[]>([]); const [planEntries, setPlanEntries] = useState<PlanEntry[]>([]);
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null); const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true); // Track if we're still initializing/loading
const messagesEndRef = useRef<HTMLDivElement>( const messagesEndRef = useRef<HTMLDivElement>(
null, null,
) as React.RefObject<HTMLDivElement>; ) as React.RefObject<HTMLDivElement>;
@@ -360,6 +361,14 @@ export const App: React.FC = () => {
completedToolCalls, completedToolCalls,
]); ]);
// Set loading state to false after initial mount and when we have authentication info
useEffect(() => {
// If we have determined authentication status, we're done loading
if (isAuthenticated !== null) {
setIsLoading(false);
}
}, [isAuthenticated]);
// Handle permission response // Handle permission response
const handlePermissionResponse = useCallback( const handlePermissionResponse = useCallback(
(optionId: string) => { (optionId: string) => {
@@ -666,7 +675,19 @@ export const App: React.FC = () => {
allMessages.length > 0; allMessages.length > 0;
return ( return (
<div className="chat-container"> <div className="chat-container relative">
{/* Top-level loading overlay */}
{isLoading && (
<div className="bg-background/80 absolute inset-0 z-50 flex items-center justify-center backdrop-blur-sm">
<div className="text-center">
<div className="border-primary mx-auto mb-2 h-8 w-8 animate-spin rounded-full border-b-2"></div>
<p className="text-muted-foreground text-sm">
Preparing Qwen Code...
</p>
</div>
</div>
)}
<SessionSelector <SessionSelector
visible={sessionManagement.showSessionSelector} visible={sessionManagement.showSessionSelector}
sessions={sessionManagement.filteredSessions} sessions={sessionManagement.filteredSessions}
@@ -693,7 +714,7 @@ export const App: React.FC = () => {
ref={messagesContainerRef} ref={messagesContainerRef}
className="chat-messages messages-container flex-1 overflow-y-auto overflow-x-hidden pt-5 pr-5 pl-5 pb-[140px] flex flex-col relative min-w-0 focus:outline-none [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-white/20 [&::-webkit-scrollbar-thumb]:rounded-sm [&::-webkit-scrollbar-thumb]:hover:bg-white/30 [&>*]:flex [&>*]:gap-0 [&>*]:items-start [&>*]:text-left [&>*]:py-2 [&>*:not(:last-child)]:pb-[8px] [&>*]:flex-col [&>*]:relative [&>*]:animate-[fadeIn_0.2s_ease-in]" className="chat-messages messages-container flex-1 overflow-y-auto overflow-x-hidden pt-5 pr-5 pl-5 pb-[140px] flex flex-col relative min-w-0 focus:outline-none [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-transparent [&::-webkit-scrollbar-thumb]:bg-white/20 [&::-webkit-scrollbar-thumb]:rounded-sm [&::-webkit-scrollbar-thumb]:hover:bg-white/30 [&>*]:flex [&>*]:gap-0 [&>*]:items-start [&>*]:text-left [&>*]:py-2 [&>*:not(:last-child)]:pb-[8px] [&>*]:flex-col [&>*]:relative [&>*]:animate-[fadeIn_0.2s_ease-in]"
> >
{!hasContent ? ( {!hasContent && !isLoading ? (
isAuthenticated === false ? ( isAuthenticated === false ? (
<Onboarding <Onboarding
onLogin={() => { onLogin={() => {

View File

@@ -13,9 +13,11 @@ import { PanelManager } from '../webview/PanelManager.js';
import { MessageHandler } from '../webview/MessageHandler.js'; import { MessageHandler } from '../webview/MessageHandler.js';
import { WebViewContent } from '../webview/WebViewContent.js'; import { WebViewContent } from '../webview/WebViewContent.js';
import { CliInstaller } from '../cli/cliInstaller.js'; import { CliInstaller } from '../cli/cliInstaller.js';
import { CliVersionChecker } from '../cli/cliVersionChecker.js';
import { getFileName } from './utils/webviewUtils.js'; import { getFileName } from './utils/webviewUtils.js';
import { type ApprovalModeValue } from '../types/approvalModeValueTypes.js'; import { type ApprovalModeValue } from '../types/approvalModeValueTypes.js';
import { isAuthenticationRequiredError } from '../utils/authErrors.js'; import { isAuthenticationRequiredError } from '../utils/authErrors.js';
import { dismissAuthenticateUpdate } from '../utils/authNotificationHandler.js';
/** /**
* WebView Provider Class * WebView Provider Class
@@ -46,7 +48,7 @@ export class WebViewProvider {
private currentModeId: ApprovalModeValue | null = null; private currentModeId: ApprovalModeValue | null = null;
constructor( constructor(
context: vscode.ExtensionContext, private context: vscode.ExtensionContext,
private extensionUri: vscode.Uri, private extensionUri: vscode.Uri,
) { ) {
this.agentManager = new QwenAgentManager(); this.agentManager = new QwenAgentManager();
@@ -619,6 +621,21 @@ export class WebViewProvider {
console.log('[WebViewProvider] CLI path:', cliDetection.cliPath); console.log('[WebViewProvider] CLI path:', cliDetection.cliPath);
console.log('[WebViewProvider] CLI version:', cliDetection.version); console.log('[WebViewProvider] CLI version:', cliDetection.version);
// Perform version check with throttled notifications
const versionChecker = CliVersionChecker.getInstance(this.context);
const versionCheckResult = await versionChecker.checkCliVersion(false); // Silent check to avoid popup spam
if (!versionCheckResult.isSupported) {
console.log(
'[WebViewProvider] Qwen CLI version is outdated or unsupported',
versionCheckResult,
);
// Log to output channel instead of showing popup
console.warn(
`Qwen Code CLI version issue: Installed=${versionCheckResult.version || 'unknown'}, Supported=${versionCheckResult.isSupported}`,
);
}
try { try {
console.log('[WebViewProvider] Connecting to agent...'); console.log('[WebViewProvider] Connecting to agent...');
@@ -630,6 +647,22 @@ export class WebViewProvider {
); );
console.log('[WebViewProvider] Agent connected successfully'); console.log('[WebViewProvider] Agent connected successfully');
this.agentInitialized = true; this.agentInitialized = true;
// If authentication is required and autoAuthenticate is false,
// send authState message and return without creating session
if (connectResult.requiresAuth && !autoAuthenticate) {
console.log(
'[WebViewProvider] Authentication required but auto-auth disabled, sending authState and returning',
);
this.sendMessageToWebView({
type: 'authState',
data: { authenticated: false },
});
// Initialize empty conversation to allow browsing history
await this.initializeEmptyConversation();
return;
}
if (connectResult.requiresAuth) { if (connectResult.requiresAuth) {
this.sendMessageToWebView({ this.sendMessageToWebView({
type: 'authState', type: 'authState',
@@ -641,6 +674,9 @@ export class WebViewProvider {
const sessionReady = await this.loadCurrentSessionMessages(options); const sessionReady = await this.loadCurrentSessionMessages(options);
if (sessionReady) { if (sessionReady) {
// Dismiss any authentication notifications
dismissAuthenticateUpdate();
// Notify webview that agent is connected // Notify webview that agent is connected
this.sendMessageToWebView({ this.sendMessageToWebView({
type: 'agentConnected', type: 'agentConnected',
@@ -715,6 +751,9 @@ export class WebViewProvider {
'[WebViewProvider] Force re-login completed successfully', '[WebViewProvider] Force re-login completed successfully',
); );
// Dismiss any authentication notifications
dismissAuthenticateUpdate();
// Send success notification to WebView // Send success notification to WebView
this.sendMessageToWebView({ this.sendMessageToWebView({
type: 'loginSuccess', type: 'loginSuccess',
@@ -769,6 +808,9 @@ export class WebViewProvider {
'[WebViewProvider] Connection refresh completed successfully', '[WebViewProvider] Connection refresh completed successfully',
); );
// Dismiss any authentication notifications
dismissAuthenticateUpdate();
// Notify webview that agent is connected after refresh // Notify webview that agent is connected after refresh
this.sendMessageToWebView({ this.sendMessageToWebView({
type: 'agentConnected', type: 'agentConnected',

View File

@@ -27,24 +27,33 @@ export const EmptyState: React.FC<EmptyStateProps> = ({
return ( return (
<div className="flex flex-col items-center justify-center h-full p-5 md:p-10"> <div className="flex flex-col items-center justify-center h-full p-5 md:p-10">
{/* Loading overlay */}
{loadingMessage && (
<div className="bg-background/80 absolute inset-0 z-50 flex items-center justify-center backdrop-blur-sm">
<div className="text-center">
<div className="border-primary mx-auto mb-2 h-8 w-8 animate-spin rounded-full border-b-2"></div>
<p className="text-muted-foreground text-sm">{loadingMessage}</p>
</div>
</div>
)}
<div className="flex flex-col items-center gap-8 w-full"> <div className="flex flex-col items-center gap-8 w-full">
{/* Qwen Logo */} {/* Qwen Logo */}
<div className="flex flex-col items-center gap-6"> <div className="flex flex-col items-center gap-6">
{iconUri ? (
<img <img
src={iconUri} src={iconUri}
alt="Qwen Logo" alt="Qwen Logo"
className="w-[60px] h-[60px] object-contain" className="w-[60px] h-[60px] object-contain"
onError={(e) => {
// Fallback to a div with text if image fails to load
const target = e.target as HTMLImageElement;
target.style.display = 'none';
const parent = target.parentElement;
if (parent) {
const fallback = document.createElement('div');
fallback.className =
'w-[60px] h-[60px] flex items-center justify-center text-2xl font-bold';
fallback.textContent = 'Q';
parent.appendChild(fallback);
}
}}
/> />
) : (
<div className="w-[60px] h-[60px] flex items-center justify-center text-2xl font-bold bg-gray-200 rounded">
Q
</div>
)}
<div className="text-center"> <div className="text-center">
<div className="text-[15px] text-app-primary-foreground leading-normal font-normal max-w-[400px]"> <div className="text-[15px] text-app-primary-foreground leading-normal font-normal max-w-[400px]">
{description} {description}