feat(auth): 优化认证流程并添加认证状态管理

- 新增 AuthStateManager 类用于管理认证状态
- 修改 createNewSession 方法以使用缓存的认证信息
- 添加清除认证缓存的功能
- 优化登录命令处理,增加加载状态显示
- 新增登录成功和失败的消息处理
This commit is contained in:
yiliang114
2025-11-27 01:41:56 +08:00
parent 4f63d92bb1
commit b986692f94
5 changed files with 108 additions and 47 deletions

View File

@@ -429,21 +429,60 @@ export class QwenAgentManager {
* @param workingDir - Working directory
* @returns Newly created session ID
*/
async createNewSession(workingDir: string): Promise<string | null> {
async createNewSession(
workingDir: string,
authStateManager?: AuthStateManager,
): Promise<string | null> {
console.log('[QwenAgentManager] Creating new session...');
// Authenticate first
console.log('[QwenAgentManager] Authenticating before creating session...');
try {
const config = vscode.workspace.getConfiguration('qwenCode');
const openaiApiKey = config.get<string>('qwen.openaiApiKey', '');
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
// Check if we have valid cached authentication
let hasValidAuth = false;
const config = vscode.workspace.getConfiguration('qwenCode');
const openaiApiKey = config.get<string>('qwen.openaiApiKey', '');
const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth';
await this.connection.authenticate(authMethod);
console.log('[QwenAgentManager] Authentication successful');
} catch (authError) {
console.error('[QwenAgentManager] Authentication failed:', authError);
throw authError;
if (authStateManager) {
hasValidAuth = await authStateManager.hasValidAuth(
workingDir,
authMethod,
);
console.log(
'[QwenAgentManager] Has valid cached auth for new session:',
hasValidAuth,
);
}
// Only authenticate if we don't have valid cached auth
if (!hasValidAuth) {
console.log(
'[QwenAgentManager] Authenticating before creating session...',
);
try {
await this.connection.authenticate(authMethod);
console.log('[QwenAgentManager] Authentication successful');
// Save auth state
if (authStateManager) {
console.log(
'[QwenAgentManager] Saving auth state after successful authentication',
);
await authStateManager.saveAuthState(workingDir, authMethod);
}
} catch (authError) {
console.error('[QwenAgentManager] Authentication failed:', authError);
// Clear potentially invalid cache
if (authStateManager) {
console.log(
'[QwenAgentManager] Clearing auth cache due to authentication failure',
);
await authStateManager.clearAuthState();
}
throw authError;
}
} else {
console.log(
'[QwenAgentManager] Skipping authentication - using valid cached auth',
);
}
await this.connection.newSession(workingDir);

View File

@@ -15,7 +15,6 @@ import {
type IdeInfo,
} from '@qwen-code/qwen-code-core/src/ide/detect-ide.js';
import { WebViewProvider } from './webview/WebViewProvider.js';
import { AuthStateManager } from './auth/authStateManager.js';
const CLI_IDE_COMPANION_IDENTIFIER = 'qwenlm.qwen-code-vscode-ide-companion';
const INFO_MESSAGE_SHOWN_KEY = 'qwenCodeInfoMessageShown';
@@ -34,7 +33,6 @@ const HIDE_INSTALLATION_GREETING_IDES: ReadonlySet<IdeInfo['name']> = new Set([
let ideServer: IDEServer;
let logger: vscode.OutputChannel;
let webViewProviders: WebViewProvider[] = []; // Track multiple chat tabs
let authStateManager: AuthStateManager;
let log: (message: string) => void = () => {};
@@ -114,30 +112,10 @@ export async function activate(context: vscode.ExtensionContext) {
const diffContentProvider = new DiffContentProvider();
const diffManager = new DiffManager(log, diffContentProvider);
// Initialize Auth State Manager
console.log('[Extension] Initializing global AuthStateManager');
authStateManager = new AuthStateManager(context);
console.log(
'[Extension] Global AuthStateManager initialized:',
!!authStateManager,
);
// Helper function to create a new WebView provider instance
const createWebViewProvider = (): WebViewProvider => {
console.log(
'[Extension] Creating WebViewProvider with global AuthStateManager:',
!!authStateManager,
);
const provider = new WebViewProvider(
context,
context.extensionUri,
authStateManager,
);
const provider = new WebViewProvider(context, context.extensionUri);
webViewProviders.push(provider);
console.log(
'[Extension] WebViewProvider created, total providers:',
webViewProviders.length,
);
return provider;
};
@@ -243,12 +221,10 @@ export async function activate(context: vscode.ExtensionContext) {
provider.show();
}),
vscode.commands.registerCommand('qwenCode.clearAuthCache', async () => {
await authStateManager.clearAuthState();
// Reset all WebView agent states to force re-authentication
webViewProviders.forEach((provider) => {
provider.resetAgentState();
});
// Clear auth state for all WebView providers
for (const provider of webViewProviders) {
await provider.clearAuthCache();
}
vscode.window.showInformationMessage(
'Qwen Code authentication cache cleared. You will need to login again on next connection.',

View File

@@ -28,12 +28,10 @@ export class WebViewProvider {
constructor(
context: vscode.ExtensionContext,
private extensionUri: vscode.Uri,
authStateManager?: AuthStateManager, // Optional global AuthStateManager instance
) {
this.agentManager = new QwenAgentManager();
this.conversationStore = new ConversationStore(context);
// If a global authStateManager is provided, use it, otherwise create a new instance
this.authStateManager = authStateManager || new AuthStateManager(context);
this.authStateManager = new AuthStateManager(context);
this.panelManager = new PanelManager(extensionUri, () => {
// Panel dispose callback
this.disposables.forEach((d) => d.dispose());
@@ -527,7 +525,10 @@ export class WebViewProvider {
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
try {
await this.agentManager.createNewSession(workingDir);
await this.agentManager.createNewSession(
workingDir,
this.authStateManager,
);
console.log('[WebViewProvider] ACP session created successfully');
} catch (sessionError) {
console.error(
@@ -601,6 +602,17 @@ export class WebViewProvider {
this.agentManager.disconnect();
}
/**
* Clear authentication cache for this WebViewProvider instance
*/
async clearAuthCache(): Promise<void> {
console.log('[WebViewProvider] Clearing auth cache for this instance');
if (this.authStateManager) {
await this.authStateManager.clearAuthState();
this.resetAgentState();
}
}
/**
* Restore an existing WebView panel (called during VSCode restart)
* This sets up the panel with all event listeners
@@ -818,7 +830,10 @@ export class WebViewProvider {
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
// Create new Qwen session via agent manager
await this.agentManager.createNewSession(workingDir);
await this.agentManager.createNewSession(
workingDir,
this.authStateManager,
);
// Clear current conversation UI
this.sendMessageToWebView({

View File

@@ -49,7 +49,7 @@ export const useMessageSubmit = ({
return;
}
// Handle /login command
// Handle /login command - show inline loading while extension authenticates
if (inputText.trim() === '/login') {
setInputText('');
if (inputFieldRef.current) {
@@ -59,6 +59,12 @@ export const useMessageSubmit = ({
type: 'login',
data: {},
});
// Show a friendly loading message in the chat while logging in
try {
messageHandling.setWaitingForResponse('Logging in to Qwen Code...');
} catch (_err) {
// Best-effort UI hint; ignore if hook not available
}
return;
}

View File

@@ -135,6 +135,31 @@ export const useWebViewMessages = ({
const handlers = handlersRef.current;
switch (message.type) {
case 'loginSuccess': {
// Clear loading state and show a short assistant notice
handlers.messageHandling.clearWaitingForResponse();
handlers.messageHandling.addMessage({
role: 'assistant',
content: 'Successfully logged in. You can continue chatting.',
timestamp: Date.now(),
});
break;
}
case 'loginError': {
// Clear loading state and show error notice
handlers.messageHandling.clearWaitingForResponse();
const errorMsg =
(message?.data?.message as string) ||
'Login failed. Please try again.';
handlers.messageHandling.addMessage({
role: 'assistant',
content: errorMsg,
timestamp: Date.now(),
});
break;
}
case 'conversationLoaded': {
const conversation = message.data as Conversation;
handlers.messageHandling.setMessages(conversation.messages);