From f623bfbb34cbe42a9cf98e4003df89c91e699080 Mon Sep 17 00:00:00 2001 From: yiliang114 <1204183885@qq.com> Date: Tue, 25 Nov 2025 13:39:07 +0800 Subject: [PATCH] chore(vscode-ide-companion): add qwen-code dependency to package files --- package-lock.json | 1 + packages/vscode-ide-companion/package.json | 9 ++ .../src/WebViewProvider.ts | 123 ++++++++---------- .../vscode-ide-companion/src/extension.ts | 12 ++ .../webview/components/icons/SpecialIcons.tsx | 2 +- .../components/messages/WaitingMessage.tsx | 2 +- .../webview/handlers/AuthMessageHandler.ts | 4 +- .../src/webview/handlers/MessageRouter.ts | 7 + .../webview/handlers/SessionMessageHandler.ts | 20 ++- .../src/webview/hooks/useCompletionTrigger.ts | 86 ------------ 10 files changed, 108 insertions(+), 158 deletions(-) diff --git a/package-lock.json b/package-lock.json index c1e96b14..5f1862ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17358,6 +17358,7 @@ "license": "LICENSE", "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", + "@qwen-code/qwen-code": "file:../cli", "@qwen-code/qwen-code-core": "file:../core", "cors": "^2.8.5", "express": "^5.1.0", diff --git a/packages/vscode-ide-companion/package.json b/packages/vscode-ide-companion/package.json index f45c56fa..9608cfd7 100644 --- a/packages/vscode-ide-companion/package.json +++ b/packages/vscode-ide-companion/package.json @@ -63,6 +63,10 @@ { "command": "qwenCode.clearAuthCache", "title": "Qwen Code: Clear Authentication Cache" + }, + { + "command": "qwenCode.login", + "title": "Qwen Code: Login" } ], "configuration": { @@ -109,6 +113,10 @@ { "command": "qwen.diff.cancel", "when": "qwen.diff.isVisible" + }, + { + "command": "qwenCode.login", + "when": "false" } ], "editor/title": [ @@ -190,6 +198,7 @@ "dependencies": { "@modelcontextprotocol/sdk": "^1.15.1", "@qwen-code/qwen-code-core": "file:../core", + "@qwen-code/qwen-code": "file:../cli", "cors": "^2.8.5", "express": "^5.1.0", "react": "^18.2.0", diff --git a/packages/vscode-ide-companion/src/WebViewProvider.ts b/packages/vscode-ide-companion/src/WebViewProvider.ts index e6ba9db3..b4b4e1fe 100644 --- a/packages/vscode-ide-companion/src/WebViewProvider.ts +++ b/packages/vscode-ide-companion/src/WebViewProvider.ts @@ -43,7 +43,7 @@ export class WebViewProvider { (message) => this.sendMessageToWebView(message), ); - // Set login handler for /login command - force re-login + // Set login handler for /login command - direct force re-login this.messageHandler.setLoginHandler(async () => { await this.forceReLogin(); }); @@ -293,33 +293,13 @@ export class WebViewProvider { }); } - // Check if we have valid auth cache and auto-reconnect + // Don't auto-login; user must use /login command + // Just initialize empty conversation for the UI if (!this.agentInitialized) { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - const workingDir = workspaceFolder?.uri.fsPath || process.cwd(); - const config = vscode.workspace.getConfiguration('qwenCode'); - const openaiApiKey = config.get('openaiApiKey', ''); - // Use the same authMethod logic as qwenConnectionHandler - const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth'; - - // Check if we have valid cached auth - const hasValidAuth = await this.authStateManager.hasValidAuth( - workingDir, - authMethod, + console.log( + '[WebViewProvider] Agent not initialized, waiting for /login command', ); - - if (hasValidAuth) { - console.log( - '[WebViewProvider] Found valid auth cache, auto-reconnecting...', - ); - // Auto-reconnect using cached auth - await this.initializeAgentConnection(); - } else { - console.log( - '[WebViewProvider] No valid auth cache, waiting for /login command', - ); - await this.initializeEmptyConversation(); - } + await this.initializeEmptyConversation(); } else { console.log( '[WebViewProvider] Agent already initialized, reusing existing connection', @@ -447,6 +427,39 @@ export class WebViewProvider { } } + /** + * Refresh connection without clearing auth cache + * Called when restoring WebView after VSCode restart + */ + async refreshConnection(): Promise { + console.log('[WebViewProvider] Refresh connection requested'); + + // Disconnect existing connection if any + if (this.agentInitialized) { + try { + this.agentManager.disconnect(); + console.log('[WebViewProvider] Existing connection disconnected'); + } catch (error) { + console.log('[WebViewProvider] Error disconnecting:', error); + } + this.agentInitialized = false; + } + + // Wait a moment for cleanup to complete + await new Promise((resolve) => setTimeout(resolve, 500)); + + // Reinitialize connection (will use cached auth if available) + try { + await this.initializeAgentConnection(); + console.log( + '[WebViewProvider] Connection refresh completed successfully', + ); + } catch (error) { + console.error('[WebViewProvider] Connection refresh failed:', error); + throw error; + } + } + /** * Load messages from current Qwen session * Creates a new ACP session for immediate message sending @@ -540,7 +553,7 @@ export class WebViewProvider { * Restore an existing WebView panel (called during VSCode restart) * This sets up the panel with all event listeners */ - restorePanel(panel: vscode.WebviewPanel): void { + async restorePanel(panel: vscode.WebviewPanel): Promise { console.log('[WebViewProvider] Restoring WebView panel'); this.panelManager.setPanel(panel); @@ -630,49 +643,25 @@ export class WebViewProvider { console.log('[WebViewProvider] Panel restored successfully'); - // Check if we have valid auth cache and auto-reconnect on restore - if (!this.agentInitialized) { - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - const workingDir = workspaceFolder?.uri.fsPath || process.cwd(); - const config = vscode.workspace.getConfiguration('qwenCode'); - const openaiApiKey = config.get('openaiApiKey', ''); - // Use the same authMethod logic as qwenConnectionHandler - const authMethod = openaiApiKey ? 'openai' : 'qwen-oauth'; - - // Check if we have valid cached auth - this.authStateManager - .hasValidAuth(workingDir, authMethod) - .then(async (hasValidAuth) => { - if (hasValidAuth) { - console.log( - '[WebViewProvider] Found valid auth cache on restore, auto-reconnecting...', - ); - await this.initializeAgentConnection(); - } else { - console.log( - '[WebViewProvider] No valid auth cache after restore, waiting for /login command', - ); - await this.initializeEmptyConversation(); - } - }) - .catch((error) => { - console.error( - '[WebViewProvider] Failed to check auth cache after restore:', - error, - ); - this.initializeEmptyConversation().catch(console.error); - }); + // Refresh connection on restore (will use cached auth if available) + if (this.agentInitialized) { + console.log( + '[WebViewProvider] Agent was initialized, refreshing connection...', + ); + try { + await this.refreshConnection(); + console.log('[WebViewProvider] Connection refreshed successfully'); + } catch (error) { + console.error('[WebViewProvider] Failed to refresh connection:', error); + // Fall back to empty conversation if refresh fails + this.agentInitialized = false; + await this.initializeEmptyConversation(); + } } else { console.log( - '[WebViewProvider] Agent already initialized, loading current session...', + '[WebViewProvider] Agent not initialized, waiting for /login command', ); - // Reload current session messages - this.loadCurrentSessionMessages().catch((error) => { - console.error( - '[WebViewProvider] Failed to load session messages after restore:', - error, - ); - }); + await this.initializeEmptyConversation(); } } diff --git a/packages/vscode-ide-companion/src/extension.ts b/packages/vscode-ide-companion/src/extension.ts index 084b3f2a..bbef0b60 100644 --- a/packages/vscode-ide-companion/src/extension.ts +++ b/packages/vscode-ide-companion/src/extension.ts @@ -231,6 +231,18 @@ export async function activate(context: vscode.ExtensionContext) { ); log('Auth cache cleared by user'); }), + vscode.commands.registerCommand('qwenCode.login', async () => { + // Get the current WebViewProvider instance - must already exist + if (webViewProviders.length > 0) { + const provider = webViewProviders[webViewProviders.length - 1]; + await provider.forceReLogin(); + } else { + // No WebViewProvider exists, show a message to user + vscode.window.showInformationMessage( + 'Please open Qwen Code chat first before logging in.', + ); + } + }), ); ideServer = new IDEServer(log, diffManager); diff --git a/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx b/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx index b8e536de..3c528d66 100644 --- a/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx +++ b/packages/vscode-ide-companion/src/webview/components/icons/SpecialIcons.tsx @@ -38,7 +38,7 @@ export const ThinkingIcon: React.FC = ({ {...props} > = ({ loadingMessage, }) => ( -
+
diff --git a/packages/vscode-ide-companion/src/webview/handlers/AuthMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/AuthMessageHandler.ts index 38125196..77391408 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/AuthMessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/AuthMessageHandler.ts @@ -47,14 +47,14 @@ export class AuthMessageHandler extends BaseMessageHandler { try { console.log('[AuthMessageHandler] Login requested'); + // Direct login without additional confirmation if (this.loginHandler) { await this.loginHandler(); } else { + // Fallback: show message and use command vscode.window.showInformationMessage( 'Please wait while we connect to Qwen Code...', ); - - // Fallback: trigger WebViewProvider's forceReLogin await vscode.commands.executeCommand('qwenCode.login'); } } catch (error) { diff --git a/packages/vscode-ide-companion/src/webview/handlers/MessageRouter.ts b/packages/vscode-ide-companion/src/webview/handlers/MessageRouter.ts index 2ba3ca22..9ddeec8b 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/MessageRouter.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/MessageRouter.ts @@ -150,6 +150,13 @@ export class MessageRouter { */ setLoginHandler(handler: () => Promise): void { this.authHandler.setLoginHandler(handler); + // Also set login handler for SessionMessageHandler + if ( + this.sessionHandler && + typeof this.sessionHandler.setLoginHandler === 'function' + ) { + this.sessionHandler.setLoginHandler(handler); + } } /** diff --git a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts index 953c9be9..dbd3898f 100644 --- a/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts +++ b/packages/vscode-ide-companion/src/webview/handlers/SessionMessageHandler.ts @@ -15,6 +15,7 @@ import type { ChatMessage } from '../../agents/qwenAgentManager.js'; export class SessionMessageHandler extends BaseMessageHandler { private currentStreamContent = ''; private isSavingCheckpoint = false; + private loginHandler: (() => Promise) | null = null; canHandle(messageType: string): boolean { return [ @@ -27,6 +28,13 @@ export class SessionMessageHandler extends BaseMessageHandler { ].includes(messageType); } + /** + * Set login handler + */ + setLoginHandler(handler: () => Promise): void { + this.loginHandler = handler; + } + async handle(message: { type: string; data?: unknown }): Promise { const data = message.data as Record | undefined; @@ -231,13 +239,23 @@ export class SessionMessageHandler extends BaseMessageHandler { if (!this.agentManager.isConnected) { 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') { - vscode.commands.executeCommand('qwenCode.login'); + // 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('qwenCode.login'); + } } return; } diff --git a/packages/vscode-ide-companion/src/webview/hooks/useCompletionTrigger.ts b/packages/vscode-ide-companion/src/webview/hooks/useCompletionTrigger.ts index e4f285d7..ca79c059 100644 --- a/packages/vscode-ide-companion/src/webview/hooks/useCompletionTrigger.ts +++ b/packages/vscode-ide-companion/src/webview/hooks/useCompletionTrigger.ts @@ -109,36 +109,12 @@ export function useCompletionTrigger( const handleInput = async () => { const text = inputElement.textContent || ''; const selection = window.getSelection(); - - console.log( - '[useCompletionTrigger] handleInput - text:', - JSON.stringify(text), - 'length:', - text.length, - ); - if (!selection || selection.rangeCount === 0) { console.log('[useCompletionTrigger] No selection or rangeCount === 0'); return; } const range = selection.getRangeAt(0); - console.log( - '[useCompletionTrigger] range.startContainer:', - range.startContainer, - 'startOffset:', - range.startOffset, - ); - console.log( - '[useCompletionTrigger] startContainer === inputElement:', - range.startContainer === inputElement, - ); - console.log( - '[useCompletionTrigger] startContainer.nodeType:', - range.startContainer.nodeType, - 'TEXT_NODE:', - Node.TEXT_NODE, - ); // Get cursor position more reliably // For contentEditable, we need to calculate the actual text offset @@ -157,14 +133,6 @@ export function useCompletionTrigger( offset += inputElement.childNodes[i].textContent?.length || 0; } cursorPosition = offset || text.length; - console.log( - '[useCompletionTrigger] Container mode - childIndex:', - childIndex, - 'offset:', - offset, - 'cursorPosition:', - cursorPosition, - ); } else if (range.startContainer.nodeType === Node.TEXT_NODE) { // Cursor is in a text node - calculate offset from start of input const walker = document.createTreeWalker( @@ -187,40 +155,17 @@ export function useCompletionTrigger( } // If we found the node, use the calculated offset; otherwise use text length cursorPosition = found ? offset : text.length; - console.log( - '[useCompletionTrigger] Text node mode - found:', - found, - 'offset:', - offset, - 'cursorPosition:', - cursorPosition, - ); } // Find trigger character before cursor // Use text length if cursorPosition is 0 but we have text (edge case for first character) const effectiveCursorPosition = cursorPosition === 0 && text.length > 0 ? text.length : cursorPosition; - console.log( - '[useCompletionTrigger] cursorPosition:', - cursorPosition, - 'effectiveCursorPosition:', - effectiveCursorPosition, - ); const textBeforeCursor = text.substring(0, effectiveCursorPosition); const lastAtMatch = textBeforeCursor.lastIndexOf('@'); const lastSlashMatch = textBeforeCursor.lastIndexOf('/'); - console.log( - '[useCompletionTrigger] textBeforeCursor:', - JSON.stringify(textBeforeCursor), - 'lastAtMatch:', - lastAtMatch, - 'lastSlashMatch:', - lastSlashMatch, - ); - // Check if we're in a trigger context let triggerPos = -1; let triggerChar: '@' | '/' | null = null; @@ -233,46 +178,19 @@ export function useCompletionTrigger( triggerChar = '/'; } - console.log( - '[useCompletionTrigger] triggerPos:', - triggerPos, - 'triggerChar:', - triggerChar, - ); - // Check if trigger is at word boundary (start of line or after space) if (triggerPos >= 0 && triggerChar) { const charBefore = triggerPos > 0 ? text[triggerPos - 1] : ' '; const isValidTrigger = charBefore === ' ' || charBefore === '\n' || triggerPos === 0; - console.log( - '[useCompletionTrigger] charBefore:', - JSON.stringify(charBefore), - 'isValidTrigger:', - isValidTrigger, - ); - if (isValidTrigger) { const query = text.substring(triggerPos + 1, effectiveCursorPosition); - console.log( - '[useCompletionTrigger] query:', - JSON.stringify(query), - 'hasSpace:', - query.includes(' '), - 'hasNewline:', - query.includes('\n'), - ); - // Only show if query doesn't contain spaces (still typing the reference) if (!query.includes(' ') && !query.includes('\n')) { // Get precise cursor position for menu const cursorPos = getCursorPosition(); - console.log( - '[useCompletionTrigger] Opening completion - cursorPos:', - cursorPos, - ); if (cursorPos) { await openCompletion(triggerChar, query, cursorPos); return; @@ -282,10 +200,6 @@ export function useCompletionTrigger( } // Close if no valid trigger - console.log( - '[useCompletionTrigger] No valid trigger, state.isOpen:', - state.isOpen, - ); if (state.isOpen) { closeCompletion(); }