diff --git a/backend/app/utils/toolkit/hybrid_browser_toolkit.py b/backend/app/utils/toolkit/hybrid_browser_toolkit.py index 4846fb49..adf6c3d0 100644 --- a/backend/app/utils/toolkit/hybrid_browser_toolkit.py +++ b/backend/app/utils/toolkit/hybrid_browser_toolkit.py @@ -78,102 +78,9 @@ async def _receive_loop(self): self.websocket = None async def start(self): - # Check if node_modules exists (dependencies installed) - node_modules_path = os.path.join(self.ts_dir, "node_modules") - if not os.path.exists(node_modules_path): - logger.warning("Node modules not found. Running npm install...") - install_result = subprocess.run( - [uv(), "run", "npm", "install"], - cwd=self.ts_dir, - capture_output=True, - text=True, - ) - if install_result.returncode != 0: - logger.error(f"npm install failed: {install_result.stderr}") - raise RuntimeError( - f"Failed to install npm dependencies: {install_result.stderr}\n" # noqa:E501 - f"Please run 'npm install' in {self.ts_dir} manually." - ) - logger.info("npm dependencies installed successfully") - - # Ensure the TypeScript code is built - build_result = subprocess.run( - [uv(), "run", "npm", "run", "build"], - cwd=self.ts_dir, - capture_output=True, - text=True, - ) - if build_result.returncode != 0: - logger.error(f"TypeScript build failed: {build_result.stderr}") - raise RuntimeError(f"TypeScript build failed: {build_result.stderr}") - else: - # Log warnings but don't fail on them - if build_result.stderr: - logger.warning(f"TypeScript build warnings: {build_result.stderr}") - logger.info("TypeScript build completed successfully") - - # Start the WebSocket server - self.process = subprocess.Popen( - [uv(), "run", "node", "websocket-server.js"], # bun not support playwright, use uv nodejs-bin - cwd=self.ts_dir, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - - # Wait for server to output the port - server_ready = False - timeout = 10 # 10 seconds timeout - start_time = time.time() - - while not server_ready and time.time() - start_time < timeout: - if self.process.poll() is not None: - # Process died - stderr = self.process.stderr.read() # type: ignore - raise RuntimeError(f"WebSocket server failed to start: {stderr}") - - try: - line = self.process.stdout.readline() # type: ignore - logger.debug(f"WebSocket server output: {line}") - if line.startswith("SERVER_READY:"): - self.server_port = int(line.split(":")[1].strip()) - server_ready = True - logger.info(f"WebSocket server ready on port {self.server_port}") - except (ValueError, IndexError): - continue - - if not server_ready: - self.process.kill() - raise RuntimeError("WebSocket server failed to start within timeout") - - # Connect to the WebSocket server - try: - self.websocket = await websockets.connect( - f"ws://localhost:{self.server_port}", - ping_interval=30, - ping_timeout=10, - max_size=50 * 1024 * 1024, # 50MB limit to match server - ) - logger.info("Connected to WebSocket server") - except Exception as e: - self.process.kill() - raise RuntimeError(f"Failed to connect to WebSocket server: {e}") from e - - # Start the background receiver task - THIS WAS MISSING! - self._receive_task = asyncio.create_task(self._receive_loop()) - logger.debug("Started WebSocket receiver task") - - # Initialize the browser toolkit - logger.debug(f"send init {self.config}") - try: - await self._send_command("init", self.config) - logger.debug("WebSocket server initialized successfully") - except RuntimeError as e: - if "Timeout waiting for response to command: init" in str(e): - logger.warning("Init timeout - continuing anyway (CDP connection may be slow)") - # Continue without error - the WebSocket server is likely still initializing - else: - raise + # Simply use the parent implementation which uses system npm/node + logger.info("Starting WebSocket server using parent implementation (system npm/node)") + await super().start() async def _send_command(self, command: str, params: Dict[str, Any]) -> Dict[str, Any]: """Send a command to the WebSocket server with enhanced error handling.""" diff --git a/electron/main/init.ts b/electron/main/init.ts index b761e028..11ed6ef3 100644 --- a/electron/main/init.ts +++ b/electron/main/init.ts @@ -153,11 +153,17 @@ export async function startBackend(setPort?: (port: number) => void): Promise => { + try { + // Find the hybrid_browser_toolkit ts directory in the virtual environment + // Need to determine the Python version to construct the correct path + let sitePackagesPath: string | null = null; + const libPath = path.join(venvPath, 'lib'); + + // Try to find the site-packages directory (it varies by Python version) + if (fs.existsSync(libPath)) { + const libContents = fs.readdirSync(libPath); + const pythonDir = libContents.find(name => name.startsWith('python')); + if (pythonDir) { + sitePackagesPath = path.join(libPath, pythonDir, 'site-packages'); + } + } + + if (!sitePackagesPath || !fs.existsSync(sitePackagesPath)) { + log.warn('[DEPS INSTALL] site-packages directory not found in venv, skipping npm install'); + return true; // Not an error if the venv structure is different + } + + const toolkitPath = path.join(sitePackagesPath, 'camel', 'toolkits', 'hybrid_browser_toolkit', 'ts'); + + if (!fs.existsSync(toolkitPath)) { + log.warn('[DEPS INSTALL] hybrid_browser_toolkit ts directory not found at ' + toolkitPath + ', skipping npm install'); + return true; // Not an error if the toolkit isn't installed + } + + log.info('[DEPS INSTALL] Installing hybrid_browser_toolkit npm dependencies...'); + safeMainWindowSend('install-dependencies-log', { + type: 'stdout', + data: 'Installing browser toolkit dependencies...\n' + }); + + // Try to find npm - first try system npm, then try uv run npm + let npmCommand: string[]; + const testNpm = spawn('npm', ['--version'], { shell: true }); + const npmExists = await new Promise(resolve => { + testNpm.on('close', (code) => resolve(code === 0)); + testNpm.on('error', () => resolve(false)); + }); + + if (npmExists) { + // Use system npm directly + npmCommand = ['npm']; + log.info('[DEPS INSTALL] Using system npm for installation'); + } else { + // Try uv run npm (might not work if nodejs-wheel isn't properly set up) + npmCommand = [uv_path, 'run', 'npm']; + log.info('[DEPS INSTALL] Attempting to use uv run npm'); + } + + // Run npm install + const npmCacheDir = path.join(venvPath, '.npm-cache'); + if (!fs.existsSync(npmCacheDir)) { + fs.mkdirSync(npmCacheDir, { recursive: true }); + } + + const npmInstall = spawn(npmCommand[0], [...npmCommand.slice(1), 'install'], { + cwd: toolkitPath, + env: { + ...process.env, + UV_PROJECT_ENVIRONMENT: venvPath, + npm_config_cache: npmCacheDir, + }, + shell: true // Important for Windows + }); + + await new Promise((resolve, reject) => { + if (npmInstall.stdout) { + npmInstall.stdout.on('data', (data) => { + log.info(`[DEPS INSTALL] npm install: ${data}`); + safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString() }); + }); + } + + if (npmInstall.stderr) { + npmInstall.stderr.on('data', (data) => { + log.warn(`[DEPS INSTALL] npm install stderr: ${data}`); + safeMainWindowSend('install-dependencies-log', { type: 'stderr', data: data.toString() }); + }); + } + + npmInstall.on('close', (code) => { + if (code === 0) { + log.info('[DEPS INSTALL] npm install completed successfully'); + resolve(); + } else { + log.error(`[DEPS INSTALL] npm install failed with code ${code}`); + reject(new Error(`npm install failed with code ${code}`)); + } + }); + + npmInstall.on('error', (err) => { + log.error(`[DEPS INSTALL] npm install process error: ${err}`); + reject(err); + }); + }); + + // Run npm build (use the same npm command as install) + log.info('[DEPS INSTALL] Building hybrid_browser_toolkit TypeScript...'); + safeMainWindowSend('install-dependencies-log', { + type: 'stdout', + data: 'Building browser toolkit TypeScript...\n' + }); + + const buildArgs = npmCommand[0] === 'npm' ? ['run', 'build'] : [...npmCommand.slice(1), 'run', 'build']; + const npmBuild = spawn(npmCommand[0], buildArgs, { + cwd: toolkitPath, + env: { + ...process.env, + UV_PROJECT_ENVIRONMENT: venvPath, + npm_config_cache: npmCacheDir, + }, + shell: true // Important for Windows + }); + + await new Promise((resolve, reject) => { + if (npmBuild.stdout) { + npmBuild.stdout.on('data', (data) => { + log.info(`[DEPS INSTALL] npm build: ${data}`); + safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString() }); + }); + } + + if (npmBuild.stderr) { + npmBuild.stderr.on('data', (data) => { + // TypeScript build warnings are common, don't treat as errors + log.info(`[DEPS INSTALL] npm build output: ${data}`); + safeMainWindowSend('install-dependencies-log', { type: 'stdout', data: data.toString() }); + }); + } + + npmBuild.on('close', (code) => { + if (code === 0) { + log.info('[DEPS INSTALL] TypeScript build completed successfully'); + resolve(); + } else { + log.error(`[DEPS INSTALL] TypeScript build failed with code ${code}`); + reject(new Error(`TypeScript build failed with code ${code}`)); + } + }); + + npmBuild.on('error', (err) => { + log.error(`[DEPS INSTALL] npm build process error: ${err}`); + reject(err); + }); + }); + + // Optionally install Playwright browsers + try { + log.info('[DEPS INSTALL] Installing Playwright browsers...'); + const npxCommand = npmCommand[0] === 'npm' ? ['npx'] : [uv_path, 'run', 'npx']; + const playwrightInstall = spawn(npxCommand[0], [...npxCommand.slice(1), 'playwright', 'install'], { + cwd: toolkitPath, + env: { + ...process.env, + UV_PROJECT_ENVIRONMENT: venvPath, + }, + shell: true + }); + + await new Promise((resolve) => { + playwrightInstall.on('close', (code) => { + if (code === 0) { + log.info('[DEPS INSTALL] Playwright browsers installed successfully'); + // Create marker file + const markerPath = path.join(toolkitPath, '.playwright_installed'); + fs.writeFileSync(markerPath, 'installed'); + } else { + log.warn('[DEPS INSTALL] Playwright installation failed, but continuing anyway'); + } + resolve(); + }); + + playwrightInstall.on('error', (err) => { + log.warn('[DEPS INSTALL] Playwright installation process error:', err); + resolve(); // Non-critical, continue + }); + }); + } catch (error) { + log.warn('[DEPS INSTALL] Failed to install Playwright browsers:', error); + // Non-critical, continue + } + + log.info('[DEPS INSTALL] hybrid_browser_toolkit dependencies installed successfully'); + return true; + } catch (error) { + log.error('[DEPS INSTALL] Failed to install hybrid_browser_toolkit dependencies:', error); + // Don't fail the entire installation if this fails + return false; + } } } @@ -352,6 +545,10 @@ export async function installDependencies(version: string): Promise