diff --git a/backend/app/utils/toolkit/terminal_toolkit.py b/backend/app/utils/toolkit/terminal_toolkit.py index 07733239..9109dcab 100644 --- a/backend/app/utils/toolkit/terminal_toolkit.py +++ b/backend/app/utils/toolkit/terminal_toolkit.py @@ -152,13 +152,16 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit): logger.warning("Falling back to system Python") def _get_venv_path(self): + """Return the cloned venv path for shell activation.""" + cloned_env_path = getattr(self, 'cloned_env_path', None) + if cloned_env_path and os.path.exists(cloned_env_path): + return cloned_env_path return None def _clone_venv_with_symlinks(self, source_venv: str, target_venv: str): """Clone a venv using symlinks for efficiency. - Only creates the minimum structure needed: pyvenv.cfg, bin/python, and lib symlink. - Activation scripts are not needed since we use python_executable directly. + Creates the structure needed: pyvenv.cfg, bin/python, lib symlink, and activate scripts. """ is_windows = platform.system() == 'Windows' @@ -187,6 +190,16 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit): src = os.path.join(source_scripts, exe) if os.path.exists(src): shutil.copy2(src, os.path.join(target_bin, exe)) + # Copy activate scripts (need to modify VIRTUAL_ENV path) + for script in ["activate.bat", "activate.ps1", "deactivate.bat"]: + src = os.path.join(source_scripts, script) + if os.path.exists(src): + with open(src, 'r', encoding='utf-8') as f: + content = f.read() + content = content.replace(source_venv, target_venv) + dst = os.path.join(target_bin, script) + with open(dst, 'w', encoding='utf-8') as f: + f.write(content) # Use directory junction for Lib (no admin rights needed, unlike symlink) source_lib = os.path.join(source_venv, "Lib") target_lib = os.path.join(target_venv, "Lib") @@ -204,6 +217,19 @@ class TerminalToolkit(BaseTerminalToolkit, AbstractToolkit): os.symlink(python_exe, os.path.join(target_bin, "python")) os.symlink("python", os.path.join(target_bin, "python3")) + # Copy activate scripts (need to modify VIRTUAL_ENV path) + source_bin = os.path.join(source_venv, "bin") + for script in ["activate", "activate.csh", "activate.fish"]: + src = os.path.join(source_bin, script) + if os.path.exists(src): + with open(src, 'r') as f: + content = f.read() + # Replace source venv path with target venv path + content = content.replace(source_venv, target_venv) + dst = os.path.join(target_bin, script) + with open(dst, 'w') as f: + f.write(content) + # Symlink lib directory source_lib = os.path.join(source_venv, "lib") os.symlink(source_lib, os.path.join(target_venv, "lib")) diff --git a/electron/main/install-deps.ts b/electron/main/install-deps.ts index e5babcc7..88307d6c 100644 --- a/electron/main/install-deps.ts +++ b/electron/main/install-deps.ts @@ -493,51 +493,64 @@ async function installTerminalBaseVenv(version: string): Promise((resolve, reject) => { - const createVenv = spawn( - uv_path, - ['venv', '--python', '3.10', terminalVenvPath], - { - env: { - ...process.env, - UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'), - }, - } - ); + // Create the venv using uv (skip if only need package install) + if (!needsPackageInstall) { + await new Promise((resolve, reject) => { + const createVenv = spawn( + uv_path, + ['venv', '--python', '3.10', terminalVenvPath], + { + env: { + ...process.env, + UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'), + }, + } + ); - createVenv.stdout.on('data', (data) => { - log.info(`[DEPS INSTALL] terminal venv: ${data}`); + createVenv.stdout.on('data', (data) => { + log.info(`[DEPS INSTALL] terminal venv: ${data}`); + }); + + createVenv.stderr.on('data', (data) => { + log.info(`[DEPS INSTALL] terminal venv: ${data}`); + }); + + createVenv.on('close', (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Failed to create terminal venv, exit code: ${code}`)); + } + }); + + createVenv.on('error', reject); }); - - createVenv.stderr.on('data', (data) => { - log.info(`[DEPS INSTALL] terminal venv: ${data}`); - }); - - createVenv.on('close', (code) => { - if (code === 0) { - resolve(); - } else { - reject(new Error(`Failed to create terminal venv, exit code: ${code}`)); - } - }); - - createVenv.on('error', reject); - }); + } // Install base packages log.info('[DEPS INSTALL] Installing terminal base packages...'); @@ -591,6 +604,8 @@ async function installTerminalBaseVenv(version: string): Promise