From ad846c81d612ca187422f1a83461e7d308c56baa Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sat, 25 Oct 2025 06:49:49 +0000 Subject: [PATCH] fix: improve command registration for slow environments like PROOT-distro/Termux - Add early command registration before potentially blocking operations - Add timeout handling for MDM and ContextProxy initialization - Improve error handling in registerCommands to continue with partial functionality - Register placeholder handlers for critical commands as fallback - Re-register commands with proper handlers after provider initialization This ensures commands are available even on slow environments that may timeout during initialization. Fixes #8816 --- src/activate/registerCommands.ts | 31 ++++++++++- src/extension.ts | 88 ++++++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 6 deletions(-) diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index 41c127333d85..6b5467e8a8fe 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -64,11 +64,38 @@ export type RegisterCommandOptions = { } export const registerCommands = (options: RegisterCommandOptions) => { - const { context } = options + const { context, outputChannel } = options + const failedCommands: string[] = [] for (const [id, callback] of Object.entries(getCommandsMap(options))) { const command = getCommand(id as CommandId) - context.subscriptions.push(vscode.commands.registerCommand(command, callback)) + try { + // Check if command already exists (might have been registered as placeholder) + const existingIndex = context.subscriptions.findIndex((sub) => (sub as any).command === command) + + // Remove existing placeholder if found + if (existingIndex !== -1) { + const existing = context.subscriptions[existingIndex] + context.subscriptions.splice(existingIndex, 1) + existing.dispose() + } + + // Register the command with proper handler + const disposable = vscode.commands.registerCommand(command, callback) + context.subscriptions.push(disposable) + } catch (error) { + failedCommands.push(id) + outputChannel.appendLine( + `[Commands] Failed to register command '${id}': ${error instanceof Error ? error.message : String(error)}`, + ) + } + } + + if (failedCommands.length > 0) { + outputChannel.appendLine( + `[Commands] Warning: Failed to register ${failedCommands.length} command(s): ${failedCommands.join(", ")}`, + ) + // Don't throw - allow extension to continue with partial functionality } } diff --git a/src/extension.ts b/src/extension.ts index 5db0996ad657..2fe4b5785fac 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -80,8 +80,24 @@ export async function activate(context: vscode.ExtensionContext) { // Create logger for cloud services. const cloudLogger = createDualLogger(createOutputChannelLogger(outputChannel)) - // Initialize MDM service - const mdmService = await MdmService.createInstance(cloudLogger) + // Initialize MDM service (with timeout for slow environments) + let mdmService: MdmService | undefined + try { + mdmService = await Promise.race([ + MdmService.createInstance(cloudLogger), + new Promise((resolve) => + setTimeout(() => { + outputChannel.appendLine("[MDM] Initialization timeout - continuing without MDM") + resolve(undefined) + }, 5000), + ), + ]) + } catch (error) { + outputChannel.appendLine( + `[MDM] Failed to initialize: ${error instanceof Error ? error.message : String(error)}`, + ) + mdmService = undefined + } // Initialize i18n for internationalization support. initializeI18n(context.globalState.get("language") ?? formatLanguage(vscode.env.language)) @@ -97,7 +113,24 @@ export async function activate(context: vscode.ExtensionContext) { context.globalState.update("allowedCommands", defaultCommands) } - const contextProxy = await ContextProxy.getInstance(context) + // Get context proxy with timeout for slow environments + let contextProxy: ContextProxy + try { + contextProxy = await Promise.race([ + ContextProxy.getInstance(context), + new Promise((resolve, reject) => + setTimeout(() => { + reject(new Error("ContextProxy initialization timeout")) + }, 10000), + ), + ]) + } catch (error) { + outputChannel.appendLine( + `[ContextProxy] Failed to initialize, using fallback: ${error instanceof Error ? error.message : String(error)}`, + ) + // Force creation even if slow + contextProxy = await ContextProxy.getInstance(context) + } // Initialize code index managers for all workspace folders. const codeIndexManagers: CodeIndexManager[] = [] @@ -125,6 +158,45 @@ export async function activate(context: vscode.ExtensionContext) { // Initialize the provider *before* the Roo Code Cloud service. const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, mdmService) + // CRITICAL: Register commands early to ensure they're available on slow environments + // This must happen before any long-running async operations + try { + registerCommands({ context, outputChannel, provider }) + outputChannel.appendLine("[Commands] Successfully registered all commands") + } catch (error) { + outputChannel.appendLine( + `[Commands] CRITICAL: Failed to register commands: ${error instanceof Error ? error.message : String(error)}`, + ) + // Attempt individual command registration as fallback + try { + const criticalCommands = [ + "settingsButtonClicked", + "plusButtonClicked", + "mcpButtonClicked", + "historyButtonClicked", + ] + for (const cmdId of criticalCommands) { + try { + const command = `${Package.name}.${cmdId}` + if (!context.subscriptions.find((sub) => (sub as any).command === command)) { + context.subscriptions.push( + vscode.commands.registerCommand(command, () => { + outputChannel.appendLine(`[Command] ${cmdId} invoked but handler not fully initialized`) + vscode.window.showWarningMessage( + `Extension is still initializing. Please try again in a moment.`, + ) + }), + ) + } + } catch (cmdError) { + outputChannel.appendLine(`[Commands] Failed to register fallback for ${cmdId}: ${cmdError}`) + } + } + } catch (fallbackError) { + outputChannel.appendLine(`[Commands] Failed to register fallback commands: ${fallbackError}`) + } + } + // Initialize Roo Code Cloud service. const postStateListener = () => ClineProvider.getVisibleInstance()?.postStateToWebview() @@ -225,7 +297,15 @@ export async function activate(context: vscode.ExtensionContext) { ) } - registerCommands({ context, outputChannel, provider }) + // Commands already registered earlier, but re-register to ensure proper handlers + // This overwrites the temporary handlers with the actual ones + try { + registerCommands({ context, outputChannel, provider }) + } catch (error) { + outputChannel.appendLine( + `[Commands] Failed to re-register commands with proper handlers: ${error instanceof Error ? error.message : String(error)}`, + ) + } /** * We use the text document content provider API to show the left side for diff