mirror of
https://github.com/eigent-ai/eigent.git
synced 2026-05-09 19:45:41 +00:00
fix: browser_install (#451)
This commit is contained in:
commit
71014bfe46
3 changed files with 210 additions and 96 deletions
|
|
@ -78,102 +78,9 @@ class WebSocketBrowserWrapper(BaseWebSocketBrowserWrapper):
|
|||
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."""
|
||||
|
|
|
|||
|
|
@ -153,11 +153,17 @@ export async function startBackend(setPort?: (port: number) => void): Promise<an
|
|||
setPort(port);
|
||||
}
|
||||
|
||||
const npmCacheDir = path.join(venvPath, '.npm-cache');
|
||||
if (!fs.existsSync(npmCacheDir)) {
|
||||
fs.mkdirSync(npmCacheDir, { recursive: true });
|
||||
}
|
||||
|
||||
const env = {
|
||||
...process.env,
|
||||
SERVER_URL: "https://dev.eigent.ai/api",
|
||||
PYTHONIOENCODING: 'utf-8',
|
||||
UV_PROJECT_ENVIRONMENT: venvPath,
|
||||
npm_config_cache: npmCacheDir,
|
||||
}
|
||||
|
||||
//Redirect output
|
||||
|
|
|
|||
|
|
@ -329,6 +329,199 @@ export async function installDependencies(version: string): Promise<PromiseRetur
|
|||
log.warn('[DEPS INSTALL] Main window not available, continuing installation without UI updates');
|
||||
}
|
||||
return success;
|
||||
},
|
||||
installHybridBrowserDependencies: async (): Promise<boolean> => {
|
||||
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<boolean>(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<void>((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<void>((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<void>((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<PromiseRetur
|
|||
// try default install
|
||||
const installSuccess = await runInstall([], version)
|
||||
if (installSuccess.success) {
|
||||
// Install hybrid_browser_toolkit npm dependencies after Python packages are installed
|
||||
log.info('[DEPS INSTALL] Installing hybrid_browser_toolkit dependencies...')
|
||||
await handleInstallOperations.installHybridBrowserDependencies()
|
||||
|
||||
handleInstallOperations.spawnBabel()
|
||||
|
||||
// Clean up old venvs after successful installation
|
||||
|
|
@ -369,6 +566,10 @@ export async function installDependencies(version: string): Promise<PromiseRetur
|
|||
mirrorInstallSuccess = (timezone === 'Asia/Shanghai')? await runInstall(proxyArgs, version) :await runInstall([], version)
|
||||
|
||||
if (mirrorInstallSuccess.success) {
|
||||
// Install hybrid_browser_toolkit npm dependencies after Python packages are installed
|
||||
log.info('[DEPS INSTALL] Installing hybrid_browser_toolkit dependencies...')
|
||||
await handleInstallOperations.installHybridBrowserDependencies()
|
||||
|
||||
handleInstallOperations.spawnBabel("mirror")
|
||||
|
||||
// Clean up old venvs after successful installation
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue