Compare commits

...

4 Commits

Author SHA1 Message Date
openhands
7066f52593 Merge main into simple-vscode-extension-fix
- Resolved conflicts in openhands/cli/commands.py by including both VSCode extension and MCP functionality
- Resolved conflicts in openhands/cli/tui.py by including both command entries
- Both /vscode-extension and /mcp commands are now available
2025-07-29 17:24:53 +00:00
openhands
f0c537f32f Use dynamic VSIX file detection instead of hardcoded version 2025-07-15 15:42:29 +00:00
openhands
454776e040 Remove remote environment detection, simplify solution 2025-07-15 15:32:17 +00:00
openhands
bf5f68d080 Add /vscode-extension command for manual VSCode extension installation 2025-07-15 15:29:27 +00:00
3 changed files with 129 additions and 27 deletions

View File

@@ -1,5 +1,7 @@
import asyncio
import os
import re
import shutil
import sys
from pathlib import Path
from typing import Any
@@ -33,6 +35,7 @@ from openhands.cli.utils import (
read_file,
write_to_file,
)
from openhands.cli.vscode_extension import get_vsix_path
from openhands.core.config import (
OpenHandsConfig,
)
@@ -159,6 +162,8 @@ async def handle_commands(
await handle_settings_command(config, settings_store)
elif command == '/resume':
close_repl, new_session_requested = await handle_resume_command(event_stream)
elif command == '/vscode-extension':
handle_vscode_extension_command()
elif command == '/mcp':
await handle_mcp_command(config)
else:
@@ -411,6 +416,60 @@ def check_folder_security_agreement(config: OpenHandsConfig, current_dir: str) -
return True
def handle_vscode_extension_command() -> None:
"""
Handle the /vscode-extension command.
This command helps users install the OpenHands VSCode extension manually
by providing the path to the VSIX file.
"""
print_formatted_text('\nOpenHands VSCode Extension Installation\n')
vsix_path = get_vsix_path()
if not vsix_path:
print_formatted_text(
'❌ Could not find or download the VSCode extension VSIX file.'
)
print_formatted_text('Please check your internet connection and try again.')
return
# Extract version information from the filename if possible
version_info = ''
if vsix_path.name.startswith('openhands-vscode-'):
version_match = re.search(r'openhands-vscode-([0-9.]+)\.vsix', vsix_path.name)
if version_match:
version_info = f' (version {version_match.group(1)})'
# Create a user-friendly location for the VSIX file
home_dir = Path.home()
downloads_dir = home_dir / 'Downloads'
if not downloads_dir.exists():
downloads_dir = home_dir
target_path = downloads_dir / vsix_path.name
try:
shutil.copy(vsix_path, target_path)
print_formatted_text(
f'✅ VSCode extension{version_info} VSIX file saved to: {target_path}'
)
print_formatted_text('\nTo install the extension:')
print_formatted_text('1. Open VSCode')
print_formatted_text('2. Go to Extensions view (Ctrl+Shift+X)')
print_formatted_text(
'3. Click on the "..." menu at the top of the Extensions view'
)
print_formatted_text('4. Select "Install from VSIX..."')
print_formatted_text(f'5. Navigate to and select: {target_path}')
print_formatted_text(
'\nAlternatively, you can run this command in your terminal:'
)
print_formatted_text(f'code --install-extension {target_path}')
except Exception as e:
print_formatted_text(f'❌ Error saving VSIX file: {e}')
print_formatted_text(f'The VSIX file is available at: {vsix_path}')
async def handle_mcp_command(config: OpenHandsConfig) -> None:
"""Handle MCP command with interactive menu."""
action = cli_confirm(

View File

@@ -81,6 +81,7 @@ COMMANDS = {
'/new': 'Create a new conversation',
'/settings': 'Display and modify current settings',
'/resume': 'Resume the agent when paused',
'/vscode-extension': 'Get the VSCode extension VSIX file for manual installation',
'/mcp': 'Manage MCP server configuration and view errors',
}

View File

@@ -2,9 +2,12 @@ import importlib.resources
import json
import os
import pathlib
import shutil
import subprocess
import tempfile
import urllib.request
from pathlib import Path
from typing import Optional
from urllib.error import URLError
from openhands.core.logger import openhands_logger as logger
@@ -124,7 +127,7 @@ def attempt_vscode_extension_install():
# If all attempts failed, inform the user (but don't create flag - allow retry).
print(
'INFO: Automatic installation failed. Please check the OpenHands documentation for manual installation instructions.'
'INFO: Automatic installation failed. You can use the /vscode-extension command to get the extension VSIX file for manual installation.'
)
print(
f'INFO: Will retry installation next time you run OpenHands in {editor_name}.'
@@ -240,35 +243,39 @@ def _attempt_bundled_install(editor_command: str, editor_name: str) -> bool:
bool: True if installation succeeded, False otherwise
"""
try:
vsix_filename = 'openhands-vscode-0.0.1.vsix'
with importlib.resources.as_file(
importlib.resources.files('openhands').joinpath(
'integrations', 'vscode', vsix_filename
)
) as vsix_path:
if vsix_path.exists():
process = subprocess.run(
[
editor_command,
'--install-extension',
str(vsix_path),
'--force',
],
capture_output=True,
text=True,
check=False,
)
if process.returncode == 0:
print(
f'INFO: Bundled {editor_name} extension installed successfully.'
vscode_dir = importlib.resources.files('openhands').joinpath(
'integrations', 'vscode'
)
with importlib.resources.as_file(vscode_dir) as vscode_path:
if vscode_path.exists() and vscode_path.is_dir():
# Find any .vsix file in the directory
vsix_files = list(vscode_path.glob('*.vsix'))
if vsix_files:
vsix_path = vsix_files[0] # Use the first .vsix file found
process = subprocess.run(
[
editor_command,
'--install-extension',
str(vsix_path),
'--force',
],
capture_output=True,
text=True,
check=False,
)
return True
if process.returncode == 0:
print(
f'INFO: Bundled {editor_name} extension installed successfully.'
)
return True
else:
logger.debug(
f'Bundled .vsix installation failed: {process.stderr.strip()}'
)
else:
logger.debug(
f'Bundled .vsix installation failed: {process.stderr.strip()}'
)
logger.debug(f'No .vsix files found in {vscode_path}.')
else:
logger.debug(f'Bundled .vsix not found at {vsix_path}.')
logger.debug(f'Bundled vscode directory not found at {vscode_path}.')
except Exception as e:
logger.warning(
f'Could not auto-install extension. Please make sure "code" command is in PATH. Error: {e}'
@@ -277,6 +284,41 @@ def _attempt_bundled_install(editor_command: str, editor_name: str) -> bool:
return False
def get_vsix_path() -> Optional[Path]:
"""
Get the path to the bundled VSIX file or download it from GitHub if not available.
Returns:
Path to the VSIX file, or None if not found
"""
# First try to get the bundled VSIX by looking for any .vsix file in the integrations/vscode directory
try:
vscode_dir = importlib.resources.files('openhands').joinpath(
'integrations', 'vscode'
)
with importlib.resources.as_file(vscode_dir) as vscode_path:
if vscode_path.exists() and vscode_path.is_dir():
# Find any .vsix file in the directory
vsix_files = list(vscode_path.glob('*.vsix'))
if vsix_files:
vsix_path = vsix_files[0] # Use the first .vsix file found
# Create a copy in a temporary location that will persist
temp_dir = Path(tempfile.gettempdir()) / 'openhands'
temp_dir.mkdir(exist_ok=True)
temp_path = temp_dir / vsix_path.name
shutil.copy(vsix_path, temp_path)
return temp_path
except Exception as e:
logger.debug(f'Could not access bundled VSIX: {e}')
# If bundled VSIX is not available, try to download from GitHub
github_vsix = download_latest_vsix_from_github()
if github_vsix:
return Path(github_vsix)
return None
def _attempt_marketplace_install(
editor_command: str, editor_name: str, extension_id: str
) -> bool: