mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
chore(vscode-ide-companion): rm authState manager in vscode-ide-companion to simplify the login architecture
This commit is contained in:
@@ -32,8 +32,6 @@ export class AcpConnection {
|
||||
private pendingRequests = new Map<number, PendingRequest<unknown>>();
|
||||
private nextRequestId = { value: 0 };
|
||||
|
||||
// Deduplicate concurrent authenticate calls (across retry paths)
|
||||
private static authInFlight: Promise<AcpResponse> | null = null;
|
||||
// Remember the working dir provided at connect() so later ACP calls
|
||||
// that require cwd (e.g. session/list) can include it.
|
||||
private workingDir: string = process.cwd();
|
||||
@@ -274,23 +272,12 @@ export class AcpConnection {
|
||||
* @returns Authentication response
|
||||
*/
|
||||
async authenticate(methodId?: string): Promise<AcpResponse> {
|
||||
if (AcpConnection.authInFlight) {
|
||||
return AcpConnection.authInFlight;
|
||||
}
|
||||
|
||||
const p = this.sessionManager
|
||||
.authenticate(
|
||||
return this.sessionManager.authenticate(
|
||||
methodId,
|
||||
this.child,
|
||||
this.pendingRequests,
|
||||
this.nextRequestId,
|
||||
)
|
||||
.finally(() => {
|
||||
AcpConnection.authInFlight = null;
|
||||
});
|
||||
|
||||
AcpConnection.authInFlight = p;
|
||||
return p;
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,253 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Qwen Team
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import type * as vscode from 'vscode';
|
||||
|
||||
interface AuthState {
|
||||
isAuthenticated: boolean;
|
||||
authMethod: string;
|
||||
timestamp: number;
|
||||
workingDir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages authentication state caching to avoid repeated logins
|
||||
*/
|
||||
export class AuthStateManager {
|
||||
private static instance: AuthStateManager | null = null;
|
||||
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 processes (e.g., multiple tabs prompting login)
|
||||
private static authProcessInFlight: Promise<unknown> | null = null;
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* Get singleton instance of AuthStateManager
|
||||
*/
|
||||
static getInstance(context?: vscode.ExtensionContext): AuthStateManager {
|
||||
if (!AuthStateManager.instance) {
|
||||
AuthStateManager.instance = new AuthStateManager();
|
||||
}
|
||||
|
||||
// If a context is provided, update the static context
|
||||
if (context) {
|
||||
AuthStateManager.context = context;
|
||||
}
|
||||
|
||||
return AuthStateManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an auth-related flow with optional queueing.
|
||||
* - Default: Reuse existing promise to avoid duplicate popups.
|
||||
* - When forceNew: true, wait for current flow to finish before starting a new one serially, used for forced re-login.
|
||||
*/
|
||||
static runExclusiveAuth<T>(
|
||||
task: () => Promise<T>,
|
||||
options?: { forceNew?: boolean },
|
||||
): Promise<T> {
|
||||
if (AuthStateManager.authProcessInFlight) {
|
||||
if (!options?.forceNew) {
|
||||
return AuthStateManager.authProcessInFlight as Promise<T>;
|
||||
}
|
||||
// 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<T>;
|
||||
}
|
||||
|
||||
const p = Promise.resolve()
|
||||
.then(task)
|
||||
.finally(() => {
|
||||
if (AuthStateManager.authProcessInFlight === p) {
|
||||
AuthStateManager.authProcessInFlight = null;
|
||||
}
|
||||
});
|
||||
|
||||
AuthStateManager.authProcessInFlight = p;
|
||||
return p as Promise<T>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's a valid cached authentication
|
||||
*/
|
||||
async hasValidAuth(workingDir: string, authMethod: string): Promise<boolean> {
|
||||
const state = await this.getAuthState();
|
||||
|
||||
if (!state) {
|
||||
console.log('[AuthStateManager] No cached auth state found');
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('[AuthStateManager] Found cached auth state:', {
|
||||
workingDir: state.workingDir,
|
||||
authMethod: state.authMethod,
|
||||
timestamp: new Date(state.timestamp).toISOString(),
|
||||
isAuthenticated: state.isAuthenticated,
|
||||
});
|
||||
console.log('[AuthStateManager] Checking against:', {
|
||||
workingDir,
|
||||
authMethod,
|
||||
});
|
||||
|
||||
// Check if auth is still valid (within cache duration)
|
||||
const now = Date.now();
|
||||
const isExpired =
|
||||
now - state.timestamp > AuthStateManager.AUTH_CACHE_DURATION;
|
||||
|
||||
if (isExpired) {
|
||||
console.log('[AuthStateManager] Cached auth expired');
|
||||
console.log(
|
||||
'[AuthStateManager] Cache age:',
|
||||
Math.floor((now - state.timestamp) / 1000 / 60),
|
||||
'minutes',
|
||||
);
|
||||
await this.clearAuthState();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's for the same working directory and auth method
|
||||
const isSameContext =
|
||||
state.workingDir === workingDir && state.authMethod === authMethod;
|
||||
|
||||
if (!isSameContext) {
|
||||
console.log('[AuthStateManager] Working dir or auth method changed');
|
||||
console.log('[AuthStateManager] Cached workingDir:', state.workingDir);
|
||||
console.log('[AuthStateManager] Current workingDir:', workingDir);
|
||||
console.log('[AuthStateManager] Cached authMethod:', state.authMethod);
|
||||
console.log('[AuthStateManager] Current authMethod:', authMethod);
|
||||
return false;
|
||||
}
|
||||
|
||||
console.log('[AuthStateManager] Valid cached auth found');
|
||||
return state.isAuthenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force check auth state without clearing cache
|
||||
* This is useful for debugging to see what's actually cached
|
||||
*/
|
||||
async debugAuthState(): Promise<void> {
|
||||
const state = await this.getAuthState();
|
||||
console.log('[AuthStateManager] DEBUG - Current auth state:', state);
|
||||
|
||||
if (state) {
|
||||
const now = Date.now();
|
||||
const age = Math.floor((now - state.timestamp) / 1000 / 60);
|
||||
const isExpired =
|
||||
now - state.timestamp > AuthStateManager.AUTH_CACHE_DURATION;
|
||||
|
||||
console.log('[AuthStateManager] DEBUG - Auth state age:', age, 'minutes');
|
||||
console.log('[AuthStateManager] DEBUG - Auth state expired:', isExpired);
|
||||
console.log(
|
||||
'[AuthStateManager] DEBUG - Auth state valid:',
|
||||
state.isAuthenticated,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save successful authentication state
|
||||
*/
|
||||
async saveAuthState(workingDir: string, authMethod: string): Promise<void> {
|
||||
// Ensure we have a valid context
|
||||
if (!AuthStateManager.context) {
|
||||
throw new Error(
|
||||
'[AuthStateManager] No context available for saving auth state',
|
||||
);
|
||||
}
|
||||
|
||||
const state: AuthState = {
|
||||
isAuthenticated: true,
|
||||
authMethod,
|
||||
workingDir,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
console.log('[AuthStateManager] Saving auth state:', {
|
||||
workingDir,
|
||||
authMethod,
|
||||
timestamp: new Date(state.timestamp).toISOString(),
|
||||
});
|
||||
|
||||
await AuthStateManager.context.globalState.update(
|
||||
AuthStateManager.AUTH_STATE_KEY,
|
||||
state,
|
||||
);
|
||||
console.log('[AuthStateManager] Auth state saved');
|
||||
|
||||
// Verify the state was saved correctly
|
||||
const savedState = await this.getAuthState();
|
||||
console.log('[AuthStateManager] Verified saved state:', savedState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication state
|
||||
*/
|
||||
async clearAuthState(): Promise<void> {
|
||||
// Ensure we have a valid context
|
||||
if (!AuthStateManager.context) {
|
||||
throw new Error(
|
||||
'[AuthStateManager] No context available for clearing auth state',
|
||||
);
|
||||
}
|
||||
|
||||
console.log('[AuthStateManager] Clearing auth state');
|
||||
const currentState = await this.getAuthState();
|
||||
console.log(
|
||||
'[AuthStateManager] Current state before clearing:',
|
||||
currentState,
|
||||
);
|
||||
|
||||
await AuthStateManager.context.globalState.update(
|
||||
AuthStateManager.AUTH_STATE_KEY,
|
||||
undefined,
|
||||
);
|
||||
console.log('[AuthStateManager] Auth state cleared');
|
||||
|
||||
// Verify the state was cleared
|
||||
const newState = await this.getAuthState();
|
||||
console.log('[AuthStateManager] State after clearing:', newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current auth state
|
||||
*/
|
||||
private async getAuthState(): Promise<AuthState | undefined> {
|
||||
// Ensure we have a valid context
|
||||
if (!AuthStateManager.context) {
|
||||
console.log(
|
||||
'[AuthStateManager] No context available for getting auth state',
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const a = AuthStateManager.context.globalState.get<AuthState>(
|
||||
AuthStateManager.AUTH_STATE_KEY,
|
||||
);
|
||||
console.log('[AuthStateManager] Auth state:', a);
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get auth state info for debugging
|
||||
*/
|
||||
async getAuthInfo(): Promise<string> {
|
||||
const state = await this.getAuthState();
|
||||
if (!state) {
|
||||
return 'No cached auth';
|
||||
}
|
||||
|
||||
const age = Math.floor((Date.now() - state.timestamp) / 1000 / 60);
|
||||
return `Auth cached ${age}m ago, method: ${state.authMethod}`;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import type {
|
||||
} from '../types/acpTypes.js';
|
||||
import { QwenSessionReader, type QwenSession } from './qwenSessionReader.js';
|
||||
import { QwenSessionManager } from './qwenSessionManager.js';
|
||||
import type { AuthStateManager } from './authStateManager.js';
|
||||
import type {
|
||||
ChatMessage,
|
||||
PlanEntry,
|
||||
@@ -42,9 +41,7 @@ export class QwenAgentManager {
|
||||
// session/update notifications. We set this flag to route message chunks
|
||||
// (user/assistant) as discrete chat messages instead of live streaming.
|
||||
private rehydratingSessionId: string | null = null;
|
||||
// Cache the last used AuthStateManager so internal calls (e.g. fallback paths)
|
||||
// can reuse it and avoid forcing a fresh authentication unnecessarily.
|
||||
private defaultAuthStateManager?: AuthStateManager;
|
||||
// CLI is now the single source of truth for authentication state
|
||||
// Deduplicate concurrent session/new attempts
|
||||
private sessionCreateInFlight: Promise<string | null> | null = null;
|
||||
|
||||
@@ -165,22 +162,14 @@ export class QwenAgentManager {
|
||||
* Connect to Qwen service
|
||||
*
|
||||
* @param workingDir - Working directory
|
||||
* @param authStateManager - Authentication state manager (optional)
|
||||
* @param cliPath - CLI path (optional, if provided will override the path in configuration)
|
||||
*/
|
||||
async connect(
|
||||
workingDir: string,
|
||||
authStateManager?: AuthStateManager,
|
||||
_cliPath?: string,
|
||||
): Promise<void> {
|
||||
async connect(workingDir: string, _cliPath?: string): Promise<void> {
|
||||
this.currentWorkingDir = workingDir;
|
||||
// Remember the provided authStateManager for future calls
|
||||
this.defaultAuthStateManager = authStateManager;
|
||||
await this.connectionHandler.connect(
|
||||
this.connection,
|
||||
this.sessionReader,
|
||||
workingDir,
|
||||
authStateManager,
|
||||
_cliPath,
|
||||
);
|
||||
}
|
||||
@@ -1181,10 +1170,7 @@ export class QwenAgentManager {
|
||||
* @param workingDir - Working directory
|
||||
* @returns Newly created session ID
|
||||
*/
|
||||
async createNewSession(
|
||||
workingDir: string,
|
||||
authStateManager?: AuthStateManager,
|
||||
): Promise<string | null> {
|
||||
async createNewSession(workingDir: string): Promise<string | null> {
|
||||
// Reuse existing session if present
|
||||
if (this.connection.currentSessionId) {
|
||||
return this.connection.currentSessionId;
|
||||
@@ -1195,15 +1181,10 @@ export class QwenAgentManager {
|
||||
}
|
||||
|
||||
console.log('[QwenAgentManager] Creating new session...');
|
||||
// Prefer the provided authStateManager, otherwise fall back to the one
|
||||
// remembered during connect(). This prevents accidental re-auth in
|
||||
// fallback paths (e.g. session switching) when the handler didn't pass it.
|
||||
const effectiveAuth = authStateManager || this.defaultAuthStateManager;
|
||||
|
||||
this.sessionCreateInFlight = (async () => {
|
||||
try {
|
||||
// Try to create a new ACP session. If Qwen asks for auth despite our
|
||||
// cached flag (e.g. fresh process or expired tokens), re-authenticate and retry.
|
||||
// Try to create a new ACP session. If Qwen asks for auth, let it handle authentication.
|
||||
try {
|
||||
await this.connection.newSession(workingDir);
|
||||
} catch (err) {
|
||||
@@ -1217,18 +1198,16 @@ export class QwenAgentManager {
|
||||
'[QwenAgentManager] session/new requires authentication. Retrying with authenticate...',
|
||||
);
|
||||
try {
|
||||
// Let CLI handle authentication - it's the single source of truth
|
||||
await this.connection.authenticate(authMethod);
|
||||
// Persist auth cache so subsequent calls can skip the web flow.
|
||||
if (effectiveAuth) {
|
||||
await effectiveAuth.saveAuthState(workingDir, authMethod);
|
||||
}
|
||||
await setTimeout(() => Promise.resolve(), 300); // slight delay to ensure auth state is settled
|
||||
// Add a slight delay to ensure auth state is settled
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
await this.connection.newSession(workingDir);
|
||||
} catch (reauthErr) {
|
||||
// Clear potentially stale cache on failure and rethrow
|
||||
if (effectiveAuth) {
|
||||
await effectiveAuth.clearAuthState();
|
||||
}
|
||||
console.error(
|
||||
'[QwenAgentManager] Re-authentication failed:',
|
||||
reauthErr,
|
||||
);
|
||||
throw reauthErr;
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
import * as vscode from 'vscode';
|
||||
import type { AcpConnection } from './acpConnection.js';
|
||||
import type { QwenSessionReader } from '../services/qwenSessionReader.js';
|
||||
import type { AuthStateManager } from '../services/authStateManager.js';
|
||||
import {
|
||||
CliVersionManager,
|
||||
MIN_CLI_VERSION_FOR_SESSION_METHODS,
|
||||
@@ -32,14 +31,12 @@ export class QwenConnectionHandler {
|
||||
* @param connection - ACP connection instance
|
||||
* @param sessionReader - Session reader instance
|
||||
* @param workingDir - Working directory
|
||||
* @param authStateManager - Authentication state manager (optional)
|
||||
* @param cliPath - CLI path (optional, if provided will override the path in configuration)
|
||||
*/
|
||||
async connect(
|
||||
connection: AcpConnection,
|
||||
sessionReader: QwenSessionReader,
|
||||
workingDir: string,
|
||||
authStateManager?: AuthStateManager,
|
||||
cliPath?: string,
|
||||
): Promise<void> {
|
||||
const connectId = Date.now();
|
||||
@@ -72,21 +69,6 @@ export class QwenConnectionHandler {
|
||||
|
||||
await connection.connect(effectiveCliPath, workingDir, extraArgs);
|
||||
|
||||
// Check if we have valid cached authentication
|
||||
if (authStateManager) {
|
||||
console.log('[QwenAgentManager] Checking for cached authentication...');
|
||||
console.log('[QwenAgentManager] Working dir:', workingDir);
|
||||
console.log('[QwenAgentManager] Auth method:', authMethod);
|
||||
|
||||
const hasValidAuth = await authStateManager.hasValidAuth(
|
||||
workingDir,
|
||||
authMethod,
|
||||
);
|
||||
console.log('[QwenAgentManager] Has valid auth:', hasValidAuth);
|
||||
} else {
|
||||
console.log('[QwenAgentManager] No authStateManager provided');
|
||||
}
|
||||
|
||||
// Try to restore existing session or create new session
|
||||
// Note: Auto-restore on connect is disabled to avoid surprising loads
|
||||
// when user opens a "New Chat" tab. Restoration is now an explicit action
|
||||
@@ -99,77 +81,15 @@ export class QwenConnectionHandler {
|
||||
'[QwenAgentManager] no sessionRestored, Creating new session...',
|
||||
);
|
||||
|
||||
// Check if we have valid cached authentication
|
||||
let hasValidAuth = false;
|
||||
if (authStateManager) {
|
||||
hasValidAuth = await authStateManager.hasValidAuth(
|
||||
workingDir,
|
||||
authMethod,
|
||||
);
|
||||
}
|
||||
|
||||
// Only authenticate if we don't have valid cached auth
|
||||
if (!hasValidAuth) {
|
||||
console.log(
|
||||
'[QwenAgentManager] Authenticating before creating session...',
|
||||
);
|
||||
try {
|
||||
await connection.authenticate(authMethod);
|
||||
console.log('[QwenAgentManager] Authentication successful');
|
||||
|
||||
// Save auth state
|
||||
if (authStateManager) {
|
||||
await authStateManager.saveAuthState(workingDir, authMethod);
|
||||
console.log('[QwenAgentManager] Auth state save completed');
|
||||
}
|
||||
} 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',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await setTimeout(() => Promise.resolve(), 300); // slight delay to ensure auth state is settled
|
||||
console.log(
|
||||
'[QwenAgentManager] Creating new session after authentication...',
|
||||
);
|
||||
await this.newSessionWithRetry(
|
||||
connection,
|
||||
workingDir,
|
||||
3,
|
||||
authMethod,
|
||||
authStateManager,
|
||||
'[QwenAgentManager] Creating new session (letting CLI handle authentication)...',
|
||||
);
|
||||
await this.newSessionWithRetry(connection, workingDir, 3, authMethod);
|
||||
console.log('[QwenAgentManager] New session created successfully');
|
||||
|
||||
// Ensure auth state is saved (prevent repeated authentication)
|
||||
if (authStateManager) {
|
||||
console.log(
|
||||
'[QwenAgentManager] Saving auth state after successful session creation',
|
||||
);
|
||||
await authStateManager.saveAuthState(workingDir, authMethod);
|
||||
}
|
||||
} catch (sessionError) {
|
||||
console.log(`\n⚠️ [SESSION FAILED] newSessionWithRetry threw error\n`);
|
||||
console.log(`[QwenAgentManager] Error details:`, sessionError);
|
||||
|
||||
// Clear cache
|
||||
if (authStateManager) {
|
||||
console.log('[QwenAgentManager] Clearing auth cache due to failure');
|
||||
await authStateManager.clearAuthState();
|
||||
}
|
||||
|
||||
throw sessionError;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +111,6 @@ export class QwenConnectionHandler {
|
||||
workingDir: string,
|
||||
maxRetries: number,
|
||||
authMethod: string,
|
||||
authStateManager?: AuthStateManager,
|
||||
): Promise<void> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
@@ -219,10 +138,10 @@ export class QwenConnectionHandler {
|
||||
'[QwenAgentManager] Qwen requires authentication. Authenticating and retrying session/new...',
|
||||
);
|
||||
try {
|
||||
// Let CLI handle authentication - it's the single source of truth
|
||||
await connection.authenticate(authMethod);
|
||||
if (authStateManager) {
|
||||
await authStateManager.saveAuthState(workingDir, authMethod);
|
||||
}
|
||||
// Add a slight delay to ensure auth state is settled
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
// Retry immediately after successful auth
|
||||
await connection.newSession(workingDir);
|
||||
console.log(
|
||||
@@ -234,9 +153,6 @@ export class QwenConnectionHandler {
|
||||
'[QwenAgentManager] Re-authentication failed:',
|
||||
authErr,
|
||||
);
|
||||
if (authStateManager) {
|
||||
await authStateManager.clearAuthState();
|
||||
}
|
||||
// Fall through to retry logic below
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,20 +9,18 @@ import { QwenAgentManager } from '../services/qwenAgentManager.js';
|
||||
import { ConversationStore } from '../services/conversationStore.js';
|
||||
import type { AcpPermissionRequest } from '../types/acpTypes.js';
|
||||
import { CliDetector } from '../cli/cliDetector.js';
|
||||
import { AuthStateManager } from '../services/authStateManager.js';
|
||||
import { PanelManager } from '../webview/PanelManager.js';
|
||||
import { MessageHandler } from '../webview/MessageHandler.js';
|
||||
import { WebViewContent } from '../webview/WebViewContent.js';
|
||||
import { CliInstaller } from '../cli/cliInstaller.js';
|
||||
import { getFileName } from './utils/webviewUtils.js';
|
||||
import { authMethod, type ApprovalModeValue } from '../types/acpTypes.js';
|
||||
import { type ApprovalModeValue } from '../types/acpTypes.js';
|
||||
|
||||
export class WebViewProvider {
|
||||
private panelManager: PanelManager;
|
||||
private messageHandler: MessageHandler;
|
||||
private agentManager: QwenAgentManager;
|
||||
private conversationStore: ConversationStore;
|
||||
private authStateManager: AuthStateManager;
|
||||
private disposables: vscode.Disposable[] = [];
|
||||
private agentInitialized = false; // Track if agent has been initialized
|
||||
// Track a pending permission request and its resolver so extension commands
|
||||
@@ -39,7 +37,6 @@ export class WebViewProvider {
|
||||
) {
|
||||
this.agentManager = new QwenAgentManager();
|
||||
this.conversationStore = new ConversationStore(context);
|
||||
this.authStateManager = AuthStateManager.getInstance(context);
|
||||
this.panelManager = new PanelManager(extensionUri, () => {
|
||||
// Panel dispose callback
|
||||
this.disposables.forEach((d) => d.dispose());
|
||||
@@ -519,43 +516,21 @@ export class WebViewProvider {
|
||||
/**
|
||||
* Attempt to restore authentication state and initialize connection
|
||||
* This is called when the webview is first shown
|
||||
*
|
||||
* In the new architecture, let CLI handle authentication state management
|
||||
*/
|
||||
private async attemptAuthStateRestoration(): Promise<void> {
|
||||
try {
|
||||
if (this.authStateManager) {
|
||||
// Debug current auth state
|
||||
await this.authStateManager.debugAuthState();
|
||||
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
||||
const hasValidAuth = await this.authStateManager.hasValidAuth(
|
||||
workingDir,
|
||||
authMethod,
|
||||
);
|
||||
console.log('[WebViewProvider] Has valid cached auth:', hasValidAuth);
|
||||
|
||||
if (hasValidAuth) {
|
||||
console.log(
|
||||
'[WebViewProvider] Valid auth found, attempting connection...',
|
||||
'[WebViewProvider] Attempting connection (letting CLI handle authentication)...',
|
||||
);
|
||||
// Try to connect with cached auth
|
||||
// In the new architecture, always attempt connection and let CLI handle authentication
|
||||
await this.initializeAgentConnection();
|
||||
} else {
|
||||
console.log(
|
||||
'[WebViewProvider] No valid auth found, rendering empty conversation',
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'[WebViewProvider] Error in attemptAuthStateRestoration:',
|
||||
error,
|
||||
);
|
||||
// Render the chat UI immediately without connecting
|
||||
await this.initializeEmptyConversation();
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
'[WebViewProvider] No auth state manager, rendering empty conversation',
|
||||
);
|
||||
await this.initializeEmptyConversation();
|
||||
}
|
||||
} catch (_error) {
|
||||
console.error('[WebViewProvider] Auth state restoration failed:', _error);
|
||||
// Fallback to rendering empty conversation
|
||||
await this.initializeEmptyConversation();
|
||||
}
|
||||
}
|
||||
@@ -565,9 +540,8 @@ export class WebViewProvider {
|
||||
* Can be called from show() or via /login command
|
||||
*/
|
||||
async initializeAgentConnection(): Promise<void> {
|
||||
return AuthStateManager.runExclusiveAuth(() =>
|
||||
this.doInitializeAgentConnection(),
|
||||
);
|
||||
// In the new architecture, let CLI handle authentication without local state caching
|
||||
return this.doInitializeAgentConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -582,10 +556,7 @@ export class WebViewProvider {
|
||||
'[WebViewProvider] Starting initialization, workingDir:',
|
||||
workingDir,
|
||||
);
|
||||
console.log(
|
||||
'[WebViewProvider] AuthStateManager available:',
|
||||
!!this.authStateManager,
|
||||
);
|
||||
console.log('[WebViewProvider] Using CLI-managed authentication');
|
||||
|
||||
// Check if CLI is installed before attempting to connect
|
||||
const cliDetection = await CliDetector.detectQwenCli();
|
||||
@@ -613,19 +584,10 @@ export class WebViewProvider {
|
||||
|
||||
try {
|
||||
console.log('[WebViewProvider] Connecting to agent...');
|
||||
console.log(
|
||||
'[WebViewProvider] Using authStateManager:',
|
||||
!!this.authStateManager,
|
||||
);
|
||||
const authInfo = await this.authStateManager.getAuthInfo();
|
||||
console.log('[WebViewProvider] Auth cache status:', authInfo);
|
||||
|
||||
// Pass the detected CLI path to ensure we use the correct installation
|
||||
await this.agentManager.connect(
|
||||
workingDir,
|
||||
this.authStateManager,
|
||||
cliDetection.cliPath,
|
||||
);
|
||||
// In the new architecture, let CLI handle authentication without local state caching
|
||||
await this.agentManager.connect(workingDir, cliDetection.cliPath);
|
||||
console.log('[WebViewProvider] Agent connected successfully');
|
||||
this.agentInitialized = true;
|
||||
|
||||
@@ -639,8 +601,6 @@ export class WebViewProvider {
|
||||
});
|
||||
} 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.`,
|
||||
);
|
||||
@@ -668,37 +628,22 @@ export class WebViewProvider {
|
||||
*/
|
||||
async forceReLogin(): Promise<void> {
|
||||
console.log('[WebViewProvider] Force re-login requested');
|
||||
console.log(
|
||||
'[WebViewProvider] Current authStateManager:',
|
||||
!!this.authStateManager,
|
||||
);
|
||||
|
||||
// If a login/connection process is already running, reuse it to avoid double prompts
|
||||
const p = Promise.resolve(
|
||||
vscode.window.withProgress(
|
||||
return vscode.window.withProgress(
|
||||
{
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: 'Logging in to Qwen Code... ',
|
||||
cancellable: false,
|
||||
},
|
||||
async (progress) => {
|
||||
try {
|
||||
progress.report({ message: 'Preparing sign-in...' });
|
||||
|
||||
// Clear existing auth cache
|
||||
if (this.authStateManager) {
|
||||
await this.authStateManager.clearAuthState();
|
||||
console.log('[WebViewProvider] Auth cache cleared');
|
||||
} else {
|
||||
console.log('[WebViewProvider] No authStateManager to clear');
|
||||
}
|
||||
|
||||
// Disconnect existing connection if any
|
||||
if (this.agentInitialized) {
|
||||
try {
|
||||
this.agentManager.disconnect();
|
||||
console.log(
|
||||
'[WebViewProvider] Existing connection disconnected',
|
||||
);
|
||||
console.log('[WebViewProvider] Existing connection disconnected');
|
||||
} catch (_error) {
|
||||
console.log('[WebViewProvider] Error disconnecting:', _error);
|
||||
}
|
||||
@@ -718,14 +663,6 @@ export class WebViewProvider {
|
||||
'[WebViewProvider] Force re-login completed successfully',
|
||||
);
|
||||
|
||||
// Ensure auth state is saved after successful re-login
|
||||
if (this.authStateManager) {
|
||||
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
|
||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
||||
await this.authStateManager.saveAuthState(workingDir, authMethod);
|
||||
console.log('[WebViewProvider] Auth state saved after re-login');
|
||||
}
|
||||
|
||||
// Send success notification to WebView
|
||||
this.sendMessageToWebView({
|
||||
type: 'loginSuccess',
|
||||
@@ -749,10 +686,7 @@ export class WebViewProvider {
|
||||
throw _error;
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
return AuthStateManager.runExclusiveAuth(() => p);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -819,19 +753,14 @@ export class WebViewProvider {
|
||||
// avoid creating another session if connect() already created one.
|
||||
if (!this.agentManager.currentSessionId) {
|
||||
try {
|
||||
await this.agentManager.createNewSession(
|
||||
workingDir,
|
||||
this.authStateManager,
|
||||
);
|
||||
await this.agentManager.createNewSession(workingDir);
|
||||
console.log('[WebViewProvider] ACP session created successfully');
|
||||
|
||||
// Ensure auth state is saved after successful session creation
|
||||
if (this.authStateManager) {
|
||||
await this.authStateManager.saveAuthState(workingDir, authMethod);
|
||||
// In the new architecture, CLI handles authentication state
|
||||
// No need to save auth state locally anymore
|
||||
console.log(
|
||||
'[WebViewProvider] Auth state saved after session creation',
|
||||
'[WebViewProvider] Session created successfully (CLI manages auth state)',
|
||||
);
|
||||
}
|
||||
} catch (sessionError) {
|
||||
console.error(
|
||||
'[WebViewProvider] Failed to create ACP session:',
|
||||
@@ -1003,17 +932,6 @@ 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
|
||||
@@ -1021,8 +939,7 @@ export class WebViewProvider {
|
||||
async restorePanel(panel: vscode.WebviewPanel): Promise<void> {
|
||||
console.log('[WebViewProvider] Restoring WebView panel');
|
||||
console.log(
|
||||
'[WebViewProvider] Current authStateManager in restore:',
|
||||
!!this.authStateManager,
|
||||
'[WebViewProvider] Using CLI-managed authentication in restore',
|
||||
);
|
||||
this.panelManager.setPanel(panel);
|
||||
|
||||
@@ -1225,10 +1142,7 @@ export class WebViewProvider {
|
||||
const workingDir = workspaceFolder?.uri.fsPath || process.cwd();
|
||||
|
||||
// Create new Qwen session via agent manager
|
||||
await this.agentManager.createNewSession(
|
||||
workingDir,
|
||||
this.authStateManager,
|
||||
);
|
||||
await this.agentManager.createNewSession(workingDir);
|
||||
|
||||
// Clear current conversation UI
|
||||
this.sendMessageToWebView({
|
||||
|
||||
Reference in New Issue
Block a user