From 25261ab88d7e061fef2345899829f941be64541b Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Fri, 12 Dec 2025 01:14:28 +0800 Subject: [PATCH] fix(vscode-ide-companion): slight delay to ensure auth state settlement --- .../src/services/authStateManager.ts | 36 ++- .../src/services/qwenAgentManager.ts | 1 + .../src/services/qwenConnectionHandler.ts | 6 +- .../src/webview/WebViewProvider.ts | 4 +- .../webview/handlers/SessionMessageHandler.ts | 221 +++++------------- 5 files changed, 93 insertions(+), 175 deletions(-) diff --git a/packages/vscode-ide-companion/src/services/authStateManager.ts b/packages/vscode-ide-companion/src/services/authStateManager.ts index 824a642c..e56f9fdf 100644 --- a/packages/vscode-ide-companion/src/services/authStateManager.ts +++ b/packages/vscode-ide-companion/src/services/authStateManager.ts @@ -21,8 +21,8 @@ export class AuthStateManager { private static context: vscode.ExtensionContext | null = null; private static readonly AUTH_STATE_KEY = 'qwen.authState'; private static readonly AUTH_CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours - // Deduplicate concurrent auth flows (e.g., multiple tabs prompting login) - private static authFlowInFlight: Promise | null = null; + // Deduplicate concurrent auth processes (e.g., multiple tabs prompting login) + private static authProcessInFlight: Promise | null = null; private constructor() {} /** @@ -42,24 +42,38 @@ export class AuthStateManager { } /** - * Run an auth-related flow exclusively. If another flow is already running, - * return the same promise to prevent duplicate login prompts. + * Run an auth-related flow with optional queueing. + * - 默认:复用在跑的 promise,避免重复弹窗。 + * - forceNew: true 时,等待当前 flow 结束后再串行启动新的,用于强制重登。 */ - static runExclusiveAuth(task: () => Promise): Promise { - if (AuthStateManager.authFlowInFlight) { - return AuthStateManager.authFlowInFlight as Promise; + static runExclusiveAuth( + task: () => Promise, + options?: { forceNew?: boolean }, + ): Promise { + if (AuthStateManager.authProcessInFlight) { + if (!options?.forceNew) { + return AuthStateManager.authProcessInFlight as Promise; + } + // queue a new flow after current finishes + const next = AuthStateManager.authProcessInFlight + .catch(() => { + /* ignore previous failure for next run */ + }) + .then(() => + AuthStateManager.runExclusiveAuth(task, { forceNew: false }), + ); + return next as Promise; } const p = Promise.resolve() .then(task) .finally(() => { - // Clear only if this promise is still the active one - if (AuthStateManager.authFlowInFlight === p) { - AuthStateManager.authFlowInFlight = null; + if (AuthStateManager.authProcessInFlight === p) { + AuthStateManager.authProcessInFlight = null; } }); - AuthStateManager.authFlowInFlight = p; + AuthStateManager.authProcessInFlight = p; return p as Promise; } diff --git a/packages/vscode-ide-companion/src/services/qwenAgentManager.ts b/packages/vscode-ide-companion/src/services/qwenAgentManager.ts index 89214139..7851d926 100644 --- a/packages/vscode-ide-companion/src/services/qwenAgentManager.ts +++ b/packages/vscode-ide-companion/src/services/qwenAgentManager.ts @@ -1222,6 +1222,7 @@ export class QwenAgentManager { if (effectiveAuth) { await effectiveAuth.saveAuthState(workingDir, authMethod); } + await setTimeout(() => Promise.resolve(), 100); // slight delay to ensure auth state is settled await this.connection.newSession(workingDir); } catch (reauthErr) { // Clear potentially stale cache on failure and rethrow diff --git a/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts b/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts index 11e7199a..9e2f5a81 100644 --- a/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts +++ b/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts @@ -119,11 +119,6 @@ export class QwenConnectionHandler { // Save auth state if (authStateManager) { - console.log( - '[QwenAgentManager] Saving auth state after successful authentication', - ); - console.log('[QwenAgentManager] Working dir for save:', workingDir); - console.log('[QwenAgentManager] Auth method for save:', authMethod); await authStateManager.saveAuthState(workingDir, authMethod); console.log('[QwenAgentManager] Auth state save completed'); } @@ -145,6 +140,7 @@ export class QwenConnectionHandler { } try { + await setTimeout(() => Promise.resolve(), 100); // slight delay to ensure auth state is settled console.log( '[QwenAgentManager] Creating new session after authentication...', ); diff --git a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts b/packages/vscode-ide-companion/src/webview/WebViewProvider.ts index faa81226..bfa9a567 100644 --- a/packages/vscode-ide-companion/src/webview/WebViewProvider.ts +++ b/packages/vscode-ide-companion/src/webview/WebViewProvider.ts @@ -134,7 +134,7 @@ export class WebViewProvider { // Note: Tool call updates are handled in handleSessionUpdate within QwenAgentManager // and sent via onStreamChunk callback this.agentManager.onToolCall((update) => { - // Always surface tool calls; they are part of the live assistant flow. + // Always surface tool calls; they are part of the live assistant process. // Cast update to access sessionUpdate property const updateData = update as unknown as Record; @@ -673,7 +673,7 @@ export class WebViewProvider { !!this.authStateManager, ); - // If a login/connection flow is already running, reuse it to avoid double prompts + // If a login/connection process is already running, reuse it to avoid double prompts const p = Promise.resolve( vscode.window.withProgress( { diff --git a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts index a46febcd..0df3e0da 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts @@ -149,6 +149,50 @@ export class SessionMessageHandler extends BaseMessageHandler { return this.isSavingCheckpoint; } + /** + * Prompt user to login and invoke the registered login handler/command. + * Returns true if a login was initiated. + */ + private async promptLogin(message: string): Promise { + const result = await vscode.window.showWarningMessage(message, 'Login Now'); + if (result === 'Login Now') { + if (this.loginHandler) { + await this.loginHandler(); + } else { + await vscode.commands.executeCommand('qwen-code.login'); + } + return true; + } + return false; + } + + /** + * Prompt user to login or view offline. Returns 'login', 'offline', or 'dismiss'. + * When login is chosen, it triggers the login handler/command. + */ + private async promptLoginOrOffline( + message: string, + ): Promise<'login' | 'offline' | 'dismiss'> { + const selection = await vscode.window.showWarningMessage( + message, + 'Login Now', + 'View Offline', + ); + + if (selection === 'Login Now') { + if (this.loginHandler) { + await this.loginHandler(); + } else { + await vscode.commands.executeCommand('qwen-code.login'); + } + return 'login'; + } + if (selection === 'View Offline') { + return 'offline'; + } + return 'dismiss'; + } + /** * Handle send message request */ @@ -271,23 +315,7 @@ export class SessionMessageHandler extends BaseMessageHandler { console.warn('[SessionMessageHandler] Agent not connected'); // Show non-modal notification with Login button - const result = await vscode.window.showWarningMessage( - 'You need to login first to use Qwen Code.', - 'Login Now', - ); - - if (result === 'Login Now') { - // Use login handler directly - if (this.loginHandler) { - await this.loginHandler(); - } else { - // Fallback to command - vscode.window.showInformationMessage( - 'Please wait while we connect to Qwen Code...', - ); - await vscode.commands.executeCommand('qwen-code.login'); - } - } + await this.promptLogin('You need to login first to use Qwen Code.'); return; } @@ -308,17 +336,9 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('Authentication required') || errorMsg.includes('(code: -32000)') ) { - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to continue using Qwen Code.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } return; } vscode.window.showErrorMessage(`Failed to create session: ${errorMsg}`); @@ -426,19 +446,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('Invalid token') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to continue using Qwen Code.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -463,17 +474,10 @@ export class SessionMessageHandler extends BaseMessageHandler { // Ensure connection (login) before creating a new session if (!this.agentManager.isConnected) { - const result = await vscode.window.showWarningMessage( + const proceeded = await this.promptLogin( 'You need to login before creating a new session.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } else { + if (!proceeded) { return; } } @@ -524,19 +528,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to create a new session.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -560,19 +555,11 @@ export class SessionMessageHandler extends BaseMessageHandler { // If not connected yet, offer to login or view offline if (!this.agentManager.isConnected) { - const selection = await vscode.window.showWarningMessage( + const choice = await this.promptLoginOrOffline( 'You are not logged in. Login now to fully restore this session, or view it offline.', - 'Login Now', - 'View Offline', ); - if (selection === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } else if (selection === 'View Offline') { + if (choice === 'offline') { // Show messages from local cache only const messages = await this.agentManager.getSessionMessages(sessionId); @@ -585,7 +572,7 @@ export class SessionMessageHandler extends BaseMessageHandler { 'Showing cached session content. Login to interact with the AI.', ); return; - } else { + } else if (choice !== 'login') { // User dismissed; do nothing return; } @@ -672,19 +659,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to switch sessions.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -741,19 +719,10 @@ export class SessionMessageHandler extends BaseMessageHandler { createErrorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to switch sessions.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -790,19 +759,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to switch sessions.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -854,19 +814,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to view sessions.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -918,19 +869,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to save sessions.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -966,19 +908,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to save sessions.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -1031,19 +964,11 @@ export class SessionMessageHandler extends BaseMessageHandler { try { // If not connected, offer to login or view offline if (!this.agentManager.isConnected) { - const selection = await vscode.window.showWarningMessage( + const choice = await this.promptLoginOrOffline( 'You are not logged in. Login now to fully restore this session, or view it offline.', - 'Login Now', - 'View Offline', ); - if (selection === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } else if (selection === 'View Offline') { + if (choice === 'offline') { const messages = await this.agentManager.getSessionMessages(sessionId); this.currentConversationId = sessionId; @@ -1055,7 +980,7 @@ export class SessionMessageHandler extends BaseMessageHandler { 'Showing cached session content. Login to interact with the AI.', ); return; - } else { + } else if (choice !== 'login') { return; } } @@ -1089,19 +1014,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to resume sessions.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired', @@ -1140,19 +1056,10 @@ export class SessionMessageHandler extends BaseMessageHandler { errorMsg.includes('No active ACP session') ) { // Show a more user-friendly error message for expired sessions - const result = await vscode.window.showWarningMessage( + await this.promptLogin( 'Your login session has expired or is invalid. Please login again to resume sessions.', - 'Login Now', ); - if (result === 'Login Now') { - if (this.loginHandler) { - await this.loginHandler(); - } else { - await vscode.commands.executeCommand('qwen-code.login'); - } - } - // Send a specific error to the webview for better UI handling this.sendToWebView({ type: 'sessionExpired',