mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-26 17:38:36 +00:00
Two gaps found auditing the decode-crash cluster: 1. suppress_platform_ver_console() only ran in hermes_cli.main processes; slash workers, tui_gateway/entry, run_agent, batch_runner, and cli.py import only hermes_bootstrap and were exposed to both the console flash and (on Python 3.11.0/3.11.1, which lack CPython's encoding='locale' fix) a UnicodeDecodeError inside platform.win32_ver() under PEP 540 — the crash #69413 reported. Move the stub into hermes_bootstrap so every entry point gets it; the _subprocess_compat copy stays for non-bootstrap callers. 2. The desktop Electron spawn built the backend env without PYTHONUTF8, so anything the Python child emitted before hermes_bootstrap ran (interpreter startup errors, pre-bootstrap tracebacks) decoded with the locale default. Re-port of PR #56499's env half (echoriver89) to backend-env.ts (original targeted the deleted backend-env.cjs); explicit user setting wins.
132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
import path from 'node:path'
|
|
|
|
// Match the POSIX fallback surface used by the Python terminal environment.
|
|
// macOS apps launched from Finder/Dock often inherit only /usr/bin:/bin:/usr/sbin:/sbin,
|
|
// which misses Apple Silicon Homebrew and user-installed CLI tools such as codex.
|
|
const POSIX_SANE_PATH_ENTRIES = Object.freeze([
|
|
'/opt/homebrew/bin',
|
|
'/opt/homebrew/sbin',
|
|
'/usr/local/sbin',
|
|
'/usr/local/bin',
|
|
'/usr/sbin',
|
|
'/usr/bin',
|
|
'/sbin',
|
|
'/bin'
|
|
])
|
|
|
|
function delimiterForPlatform(platform = process.platform) {
|
|
return platform === 'win32' ? ';' : ':'
|
|
}
|
|
|
|
function pathModuleForPlatform(platform = process.platform) {
|
|
return platform === 'win32' ? path.win32 : path.posix
|
|
}
|
|
|
|
function pathEnvKey(env = process.env, platform = process.platform) {
|
|
if (platform !== 'win32') {
|
|
return 'PATH'
|
|
}
|
|
|
|
return Object.keys(env || {}).find(key => key.toUpperCase() === 'PATH') || 'PATH'
|
|
}
|
|
|
|
function currentPathValue(env = process.env, platform = process.platform) {
|
|
const key = pathEnvKey(env, platform)
|
|
|
|
return env?.[key] || ''
|
|
}
|
|
|
|
function appendUniquePathEntries(entries, { delimiter = path.delimiter } = {}) {
|
|
const seen = new Set()
|
|
const ordered = []
|
|
|
|
for (const entry of entries) {
|
|
if (!entry) {
|
|
continue
|
|
}
|
|
|
|
const parts = Array.isArray(entry) ? entry : String(entry).split(delimiter)
|
|
|
|
for (const part of parts) {
|
|
if (!part || seen.has(part)) {
|
|
continue
|
|
}
|
|
|
|
seen.add(part)
|
|
ordered.push(part)
|
|
}
|
|
}
|
|
|
|
return ordered.join(delimiter)
|
|
}
|
|
|
|
function buildDesktopBackendPath({
|
|
hermesHome,
|
|
venvRoot,
|
|
currentPath = '',
|
|
platform = process.platform,
|
|
pathModule = pathModuleForPlatform(platform)
|
|
}: any = {}) {
|
|
const delimiter = delimiterForPlatform(platform)
|
|
const hermesNodeBin = hermesHome ? pathModule.join(hermesHome, 'node', 'bin') : null
|
|
const venvBin = venvRoot ? pathModule.join(venvRoot, platform === 'win32' ? 'Scripts' : 'bin') : null
|
|
const saneEntries = platform === 'win32' ? [] : POSIX_SANE_PATH_ENTRIES
|
|
|
|
return appendUniquePathEntries([hermesNodeBin, venvBin, currentPath, saneEntries], { delimiter })
|
|
}
|
|
|
|
function normalizeHermesHomeRoot(hermesHome, { pathModule = pathModuleForPlatform(process.platform) }: any = {}) {
|
|
if (!hermesHome) {
|
|
return hermesHome
|
|
}
|
|
|
|
const resolved = pathModule.resolve(String(hermesHome))
|
|
const parent = pathModule.dirname(resolved)
|
|
|
|
if (pathModule.basename(parent).toLowerCase() === 'profiles') {
|
|
return pathModule.dirname(parent)
|
|
}
|
|
|
|
return resolved
|
|
}
|
|
|
|
function buildDesktopBackendEnv({
|
|
hermesHome,
|
|
pythonPathEntries = [],
|
|
venvRoot,
|
|
currentEnv = process.env,
|
|
platform = process.platform,
|
|
pathModule = pathModuleForPlatform(platform)
|
|
}: any = {}) {
|
|
const delimiter = delimiterForPlatform(platform)
|
|
const currentPythonPath = currentEnv?.PYTHONPATH || ''
|
|
const key = pathEnvKey(currentEnv, platform)
|
|
|
|
return {
|
|
PYTHONPATH: appendUniquePathEntries([...pythonPathEntries, currentPythonPath], { delimiter }),
|
|
// Force PEP 540 UTF-8 mode in the spawned Python backend so its stdio and
|
|
// subprocess defaults are UTF-8 even on non-UTF-8 Windows locales (GBK,
|
|
// cp1252, ...). hermes_bootstrap sets this inside the child too, but only
|
|
// after import — anything emitted earlier (interpreter startup errors,
|
|
// pre-bootstrap tracebacks) still decodes with the locale default without
|
|
// this. User's explicit setting wins. Re-port of PR #56499 (echoriver89).
|
|
PYTHONUTF8: currentEnv?.PYTHONUTF8 ?? '1',
|
|
[key]: buildDesktopBackendPath({
|
|
hermesHome,
|
|
venvRoot,
|
|
currentPath: currentPathValue(currentEnv, platform),
|
|
platform,
|
|
pathModule
|
|
})
|
|
}
|
|
}
|
|
|
|
export {
|
|
appendUniquePathEntries,
|
|
buildDesktopBackendEnv,
|
|
buildDesktopBackendPath,
|
|
delimiterForPlatform,
|
|
normalizeHermesHomeRoot,
|
|
pathEnvKey,
|
|
POSIX_SANE_PATH_ENTRIES
|
|
}
|