mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-18 09:51:59 +00:00
A GUI app launched from Explorer inherits the environment block captured at login, so a HERMES_HOME set via 'setx' AFTER login is invisible in process.env even though the CLI (a fresh shell) sees it. The desktop then silently fell back to %LOCALAPPDATA%\hermes and reported 'No inference provider configured' despite a valid configured home (#45471). resolveHermesHome() now consults the live HKCU\Environment registry value on Windows before the LOCALAPPDATA default. New windows-user-env.cjs helper parses 'reg query' output, expands %VAR% refs, and fails safe (returns null off-Windows, on spawn error, or empty value). The registry value is normalized through the same normalizeHermesHomeRoot() path as the env var for consistency. Co-authored-by: jeffrobodie-glitch <jeffrobodie@gmail.com>
76 lines
2.8 KiB
JavaScript
76 lines
2.8 KiB
JavaScript
// windows-user-env.cjs
|
|
//
|
|
// Read a User-scoped environment variable straight from the Windows registry
|
|
// (HKCU\Environment).
|
|
//
|
|
// A GUI app launched from Explorer inherits the environment block captured at
|
|
// login, so a variable set via `setx` AFTER login is invisible in process.env
|
|
// even though a fresh shell — and the Hermes CLI — sees it immediately. The
|
|
// desktop's HERMES_HOME resolution relies on process.env, so that stale-snapshot
|
|
// gap silently sends the backend to the default %LOCALAPPDATA%\hermes. Reading
|
|
// the live registry value closes the gap. See #45471.
|
|
|
|
const { execFileSync } = require('node:child_process')
|
|
|
|
// Parse the output of `reg query HKCU\Environment /v <name>`, which looks like:
|
|
//
|
|
// HKEY_CURRENT_USER\Environment
|
|
// HERMES_HOME REG_SZ F:\Hermes\data
|
|
//
|
|
// Returns the raw value string (spaces inside the value preserved), or null when
|
|
// the requested value line isn't present.
|
|
function parseRegQueryValue(stdout, name) {
|
|
if (!stdout || !name) return null
|
|
const typePattern =
|
|
/^(\S+)\s+(?:REG_SZ|REG_EXPAND_SZ|REG_MULTI_SZ|REG_DWORD|REG_QWORD|REG_BINARY|REG_NONE)\s+(.*)$/
|
|
for (const rawLine of String(stdout).split(/\r?\n/)) {
|
|
const line = rawLine.trim()
|
|
const match = line.match(typePattern)
|
|
if (match && match[1].toLowerCase() === name.toLowerCase()) {
|
|
return match[2]
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
// Expand %VAR% references against an env map. REG_EXPAND_SZ values store
|
|
// unexpanded references; plain REG_SZ paths have none, so this is a no-op for
|
|
// the common F:\... case. Unknown references are left verbatim.
|
|
function expandWindowsEnvRefs(value, env = process.env) {
|
|
if (!value) return value
|
|
return value.replace(/%([^%]+)%/g, (whole, name) => {
|
|
const key = Object.keys(env).find(k => k.toUpperCase() === String(name).toUpperCase())
|
|
return key != null && env[key] != null ? env[key] : whole
|
|
})
|
|
}
|
|
|
|
// Read a User-scoped env var from HKCU\Environment. Windows-only: returns null
|
|
// off-Windows (without spawning), on any spawn error, when `reg` exits non-zero
|
|
// (the value doesn't exist), or when the value is empty.
|
|
function readWindowsUserEnvVar(
|
|
name,
|
|
{ platform = process.platform, env = process.env, exec = execFileSync } = {}
|
|
) {
|
|
if (platform !== 'win32' || !name) return null
|
|
let stdout
|
|
try {
|
|
stdout = exec('reg', ['query', 'HKCU\\Environment', '/v', name], {
|
|
encoding: 'utf8',
|
|
windowsHide: true,
|
|
timeout: 5000
|
|
})
|
|
} catch {
|
|
// `reg` missing, or value absent (reg exits 1) — caller falls back.
|
|
return null
|
|
}
|
|
const raw = parseRegQueryValue(stdout, name)
|
|
if (raw == null) return null
|
|
const expanded = expandWindowsEnvRefs(raw, env).trim()
|
|
return expanded || null
|
|
}
|
|
|
|
module.exports = {
|
|
expandWindowsEnvRefs,
|
|
parseRegQueryValue,
|
|
readWindowsUserEnvVar
|
|
}
|