Files
hermes-agent/apps/desktop/electron/source-backend.ts
ethernet cff2cfd41d fix(desktop): put the checkout on the source backend's PYTHONPATH
A developer/E2E interpreter override need not have the checkout installed,
and the backend runs in the user's workspace cwd, so `-m hermes_cli.main`
only resolved from an editable .venv. The Desktop core E2E on the PM test
environment died with "No module named hermes_cli" on every local boot.
Name this checkout explicitly; the scrub of an inherited value stays.
2026-09-24 12:48:04 -04:00

109 lines
2.9 KiB
TypeScript

import { existsSync } from 'node:fs'
import path from 'node:path'
import { buildDesktopBackendEnv } from './backend-env'
import { execProbe, PROBE_TIMEOUT_MS } from './backend-probes'
import { resolveInstallationLauncher } from './updater-process'
export interface SourceBackend {
kind: 'command' | 'python'
label: string
command: string
args: string[]
env: NodeJS.ProcessEnv
root: string
bootstrap: false
shell: boolean
local: 'installed'
}
interface SourceOptions {
isWindows?: boolean
env?: NodeJS.ProcessEnv
}
/** Keep the validated command. PM owns interpreter and generation selection. */
export async function resolveSourceInstallationBackend(
root: string,
args: string[],
options: SourceOptions & { hermesHome?: string } = {}
): Promise<SourceBackend | null> {
if (!existsSync(path.join(root, 'hermes_cli', 'main.py'))) {
return null
}
const isWindows: boolean = options.isWindows ?? process.platform === 'win32'
const launcher: string | null = resolveInstallationLauncher(root, isWindows, options.hermesHome)
if (!launcher) {
return null
}
const shell: boolean = isWindows && /\.(cmd|bat)$/i.test(launcher)
const command: string = shell ? `"${launcher}"` : launcher
const env: NodeJS.ProcessEnv = buildDesktopBackendEnv({ currentEnv: options.env ?? process.env })
try {
await execProbe(command, ['--version'], {
cwd: root,
env: { ...process.env, ...options.env, ...env },
shell,
stdio: 'ignore',
timeout: PROBE_TIMEOUT_MS,
windowsHide: true
})
} catch {
return null
}
return {
kind: 'command',
label: `Hermes at ${root}`,
command,
args: [...args],
env,
root,
bootstrap: false,
shell,
local: 'installed'
}
}
/** Developer overrides retain their interpreter, even outside the checkout. */
export function createSourcePythonBackend(
root: string,
python: string | null,
args: string[],
options: SourceOptions = {}
): SourceBackend | null {
if (!python) {
return null
}
let command: string = python
if ((options.isWindows ?? process.platform === 'win32') && /[\\/]pythonw\.exe$/i.test(python)) {
// Use only the console interpreter beside the selected windowless one.
const consolePython: string = python.replace(/pythonw\.exe$/i, 'python.exe')
if (existsSync(consolePython)) {
command = consolePython
}
}
return {
kind: 'python',
label: `Hermes source at ${root}`,
command,
args: ['-m', 'hermes_cli.main', ...args],
// The backend runs in the user's workspace cwd, and the selected
// interpreter need not have this checkout installed: name it explicitly.
// (The scrubbed inherited value could point at another checkout.)
env: { ...buildDesktopBackendEnv({ currentEnv: options.env ?? process.env }), PYTHONPATH: root },
root,
bootstrap: false,
shell: false,
local: 'installed'
}
}