Skip to content

Commit adbd0e9

Browse files
authored
fix: install env path issue (#773)
2 parents 644d3a9 + d0090c2 commit adbd0e9

7 files changed

Lines changed: 955 additions & 843 deletions

File tree

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ body:
1212
id: version
1313
attributes:
1414
label: What version of eigent are you using?
15-
placeholder: E.g., 0.0.72
15+
placeholder: E.g., 0.0.73
1616
validations:
1717
required: true
1818

backend/pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ name = "backend"
33
version = "0.1.0"
44
description = "Add your description here"
55
readme = "README.md"
6-
requires-python = "==3.10.16"
6+
requires-python = ">=3.10,<3.11"
77
dependencies = [
8-
"camel-ai[eigent]==0.2.80a3",
8+
"camel-ai[eigent]==0.2.80",
99
"fastapi>=0.115.12",
1010
"fastapi-babel>=1.0.0",
1111
"uvicorn[standard]>=0.34.2",

backend/uv.lock

Lines changed: 823 additions & 823 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

electron/main/init.ts

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, isBinaryExists, runInstallScript } from "./utils/process";
1+
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, getUvEnv, isBinaryExists, runInstallScript, killProcessByName } from "./utils/process";
22
import { spawn, exec } from 'child_process'
33
import log from 'electron-log'
44
import fs from 'fs'
@@ -21,16 +21,16 @@ export function getMainWindow(): BrowserWindow | null {
2121
export async function checkToolInstalled() {
2222
return new Promise<PromiseReturnType>(async (resolve, reject) => {
2323
if (!(await isBinaryExists('uv'))) {
24-
resolve({success: false, message: "uv doesn't exist"})
24+
resolve({ success: false, message: "uv doesn't exist" })
2525
return
2626
}
2727

2828
if (!(await isBinaryExists('bun'))) {
29-
resolve({success: false, message: "Bun doesn't exist"})
29+
resolve({ success: false, message: "Bun doesn't exist" })
3030
return
3131
}
3232

33-
resolve({success: true, message: "Tools exist already"})
33+
resolve({ success: true, message: "Tools exist already" })
3434
})
3535

3636
}
@@ -159,12 +159,13 @@ export async function startBackend(setPort?: (port: number) => void): Promise<an
159159
fs.mkdirSync(npmCacheDir, { recursive: true });
160160
}
161161

162+
const uvEnv = getUvEnv(currentVersion);
162163
const env = {
163164
...process.env,
165+
...uvEnv,
164166
SERVER_URL: "https://dev.eigent.ai/api",
165167
PYTHONIOENCODING: 'utf-8',
166168
PYTHONUNBUFFERED: '1',
167-
UV_PROJECT_ENVIRONMENT: venvPath,
168169
npm_config_cache: npmCacheDir,
169170
}
170171

@@ -202,10 +203,79 @@ export async function startBackend(setPort?: (port: number) => void): Promise<an
202203
{ cwd: backendPath, env: env }
203204
);
204205
log.info(`Python test output: ${pythonTest.trim()}`);
205-
} catch (testErr) {
206-
log.error(`Pre-flight check failed: ${testErr}`);
207-
reject(new Error(`Backend environment check failed: ${testErr}`));
208-
return;
206+
} catch (testErr: any) {
207+
log.warn(`Pre-flight check failed, attempting repair: ${testErr}`);
208+
209+
try {
210+
// Attempt to repair the environment
211+
log.info("Attempting to repair environment...");
212+
213+
// Cleanup stale processes and locks
214+
log.info("Cleaning up stale processes and locks...");
215+
await killProcessByName('uv');
216+
await killProcessByName('python');
217+
218+
// Try to remove the lock file explicitly if it exists
219+
try {
220+
const lockFile = path.join(getCachePath('uv_python'), '.lock');
221+
if (fs.existsSync(lockFile)) {
222+
fs.unlinkSync(lockFile);
223+
}
224+
} catch (e) {
225+
log.warn(`Failed to remove lock file: ${e}`);
226+
}
227+
228+
// Cleanup corrupted python cache
229+
try {
230+
const pythonCacheDir = getCachePath('uv_python');
231+
if (fs.existsSync(pythonCacheDir)) {
232+
log.info(`Removing potentially corrupted Python cache: ${pythonCacheDir}`);
233+
fs.rmSync(pythonCacheDir, { recursive: true, force: true });
234+
}
235+
} catch (e) {
236+
log.warn(`Failed to remove Python cache: ${e}`);
237+
}
238+
239+
// Cleanup corrupted venv (pyvenv.cfg may reference non-existent Python version)
240+
try {
241+
if (fs.existsSync(venvPath)) {
242+
log.info(`Removing potentially corrupted venv: ${venvPath}`);
243+
fs.rmSync(venvPath, { recursive: true, force: true });
244+
}
245+
} catch (e) {
246+
log.warn(`Failed to remove venv: ${e}`);
247+
}
248+
249+
// Use proxy if in China (simple check based on timezone)
250+
// Add official PyPI as fallback for packages not available on mirror
251+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
252+
const proxyArgs = timezone === 'Asia/Shanghai'
253+
? [
254+
'--default-index', 'https://mirrors.aliyun.com/pypi/simple/',
255+
'--index', 'https://pypi.org/simple/'
256+
]
257+
: [];
258+
259+
// Step 1: Ensure Python is installed (fixes corrupted/missing Python)
260+
log.info("Step 1: Ensuring Python is installed...");
261+
await execAsync(`${uv_path} python install 3.10`, { cwd: backendPath, env: env });
262+
263+
// Step 2: Sync dependencies
264+
log.info("Step 2: Syncing dependencies...");
265+
const syncArgs = ['sync', '--no-dev', ...proxyArgs];
266+
await execAsync(`${uv_path} ${syncArgs.join(' ')}`, { cwd: backendPath, env: env });
267+
268+
// Retry the check
269+
const { stdout: pythonTest } = await execAsync(
270+
`${uv_path} run python -c "print('Python OK')"`,
271+
{ cwd: backendPath, env: env }
272+
);
273+
log.info(`Python test output after repair: ${pythonTest.trim()}`);
274+
} catch (repairErr) {
275+
log.error(`Repair failed: ${repairErr}`);
276+
reject(new Error(`Backend environment check failed: ${testErr}\nRepair failed: ${repairErr}`));
277+
return;
278+
}
209279
}
210280

211281
const node_process = spawn(
@@ -265,7 +335,7 @@ export async function startBackend(setPort?: (port: number) => void): Promise<an
265335
setTimeout(() => {
266336
try {
267337
process.kill(-proc.pid, 'SIGKILL');
268-
} catch (e) {}
338+
} catch (e) { }
269339
}, 1000);
270340
} catch (e) {
271341
log.error(`Failed to kill process group: ${e}`);

electron/main/install-deps.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import path from 'node:path'
33
import log from 'electron-log'
44
import { getMainWindow } from './init'
55
import fs from 'node:fs'
6-
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, cleanupOldVenvs, isBinaryExists, runInstallScript } from './utils/process'
6+
import { getBackendPath, getBinaryPath, getCachePath, getVenvPath, getUvEnv, cleanupOldVenvs, isBinaryExists, runInstallScript } from './utils/process'
77
import { spawn } from 'child_process'
88
import { safeMainWindowSend } from './utils/safeWebContentsSend'
99
import os from 'node:os'
@@ -242,7 +242,6 @@ class InstallLogs {
242242

243243
constructor(extraArgs:string[], version: string) {
244244
console.log('start install dependencies', extraArgs, 'version:', version)
245-
const venvPath = getVenvPath(version);
246245
this.version = version;
247246

248247
this.node_process = spawn(uv_path, [
@@ -253,10 +252,7 @@ class InstallLogs {
253252
cwd: backendPath,
254253
env: {
255254
...process.env,
256-
UV_TOOL_DIR: getCachePath('uv_tool'),
257-
UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'),
258-
UV_PROJECT_ENVIRONMENT: venvPath,
259-
UV_HTTP_TIMEOUT: '180', // 3 minutes timeout
255+
...getUvEnv(version),
260256
}
261257
})
262258
}

electron/main/utils/process.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,49 @@ export async function isBinaryExists(name: string): Promise<boolean> {
142142

143143
return await fs.existsSync(cmd)
144144
}
145+
146+
/**
147+
* Get unified UV environment variables for consistent Python environment management.
148+
* This ensures both installation and runtime use the same paths.
149+
* @param version - The app version for venv path
150+
* @returns Environment variables for UV commands
151+
*/
152+
export function getUvEnv(version: string): Record<string, string> {
153+
return {
154+
UV_PYTHON_INSTALL_DIR: getCachePath('uv_python'),
155+
UV_TOOL_DIR: getCachePath('uv_tool'),
156+
UV_PROJECT_ENVIRONMENT: getVenvPath(version),
157+
UV_HTTP_TIMEOUT: '180',
158+
}
159+
}
160+
161+
export async function killProcessByName(name: string): Promise<void> {
162+
const platform = process.platform
163+
try {
164+
if (platform === 'win32') {
165+
await new Promise<void>((resolve, reject) => {
166+
// /F = force, /IM = image name
167+
const cmd = spawn('taskkill', ['/F', '/IM', `${name}.exe`])
168+
cmd.on('close', (code) => {
169+
// code 0 = success, code 128 = process not found (which is fine)
170+
if (code === 0 || code === 128) resolve()
171+
else reject(new Error(`taskkill exited with code ${code}`))
172+
})
173+
cmd.on('error', reject)
174+
})
175+
} else {
176+
await new Promise<void>((resolve, reject) => {
177+
const cmd = spawn('pkill', ['-9', name])
178+
cmd.on('close', (code) => {
179+
// code 0 = success, code 1 = no process found (which is fine)
180+
if (code === 0 || code === 1) resolve()
181+
else reject(new Error(`pkill exited with code ${code}`))
182+
})
183+
cmd.on('error', reject)
184+
})
185+
}
186+
} catch (err) {
187+
// Ignore errors, just best effort
188+
log.warn(`Failed to kill process ${name}:`, err)
189+
}
190+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eigent",
3-
"version": "0.0.72",
3+
"version": "0.0.73",
44
"main": "dist-electron/main/index.js",
55
"description": "Eigent",
66
"author": "Eigent.AI",

0 commit comments

Comments
 (0)