mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-08 13:12:08 +00:00
fix(desktop): probe venv runtime health before trusting bootstrap marker
A broken/empty Windows launcher venv can see the source tree via PYTHONPATH but lack PyYAML, so 'import hermes_cli' succeeds while the first real CLI import dies — the desktop then trusts the bootstrap marker, spawns a dead backend, and loops on 'gateway offline' (#52378). - backend-probes.cjs: canImportHermesCli now runs 'import yaml; import hermes_cli.config' (extracted as hermesRuntimeImportProbe) and accepts an env override, so a dependency regression is caught without a real broken venv fixture. - main.cjs: isBootstrapComplete() routes through new isActiveRuntimeUsable(), which requires the venv python to pass the runtime import probe (with ACTIVE_HERMES_ROOT on PYTHONPATH) — not just exist on disk. Salvaged from PR #38179. The PR's install.ps1 reset/clean + autocrlf changes and their tests are dropped: current main already preserves dirty checkouts via stash (the data-loss-safe #38542 path) rather than the PR's older reset-based Repair-ManagedCheckoutBeforeUpdate approach.
This commit is contained in:
parent
7c9cdad9fd
commit
0229246ab8
3 changed files with 48 additions and 5 deletions
|
|
@ -37,7 +37,18 @@ const { execFileSync } = require('node:child_process')
|
|||
const PROBE_TIMEOUT_MS = 5000
|
||||
|
||||
/**
|
||||
* Return true iff `python -c "import hermes_cli"` exits 0.
|
||||
* Return the Python snippet used to verify Hermes can import far enough to
|
||||
* launch the CLI. Kept exported for tests so dependency regressions are
|
||||
* caught without needing a real broken venv fixture.
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
function hermesRuntimeImportProbe() {
|
||||
return 'import yaml; import hermes_cli.config'
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true iff the Hermes runtime import probe exits 0.
|
||||
*
|
||||
* Used to gate the "fallback to system Python with hermes_cli installed"
|
||||
* rung of resolveHermesBackend. Without this, a system Python 3.11-3.13
|
||||
|
|
@ -46,13 +57,20 @@ const PROBE_TIMEOUT_MS = 5000
|
|||
* site-packages -- and the resolver returns a backend that immediately
|
||||
* dies on spawn.
|
||||
*
|
||||
* The probe intentionally imports hermes_cli.config, not just the top-level
|
||||
* package: a broken/empty Windows launcher venv can still see the source tree
|
||||
* through PYTHONPATH but lack PyYAML, then die on the first real CLI import.
|
||||
*
|
||||
* @param {string} pythonPath - Absolute path to a python.exe / python.
|
||||
* @param {object} [opts]
|
||||
* @param {object} [opts.env] - Additional environment for the probe.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function canImportHermesCli(pythonPath) {
|
||||
function canImportHermesCli(pythonPath, opts = {}) {
|
||||
if (!pythonPath) return false
|
||||
try {
|
||||
execFileSync(pythonPath, ['-c', 'import hermes_cli'], {
|
||||
execFileSync(pythonPath, ['-c', hermesRuntimeImportProbe()], {
|
||||
env: { ...process.env, ...(opts.env || {}) },
|
||||
stdio: 'ignore',
|
||||
timeout: PROBE_TIMEOUT_MS,
|
||||
windowsHide: true
|
||||
|
|
@ -101,6 +119,7 @@ function verifyHermesCli(hermesCommand, opts = {}) {
|
|||
|
||||
module.exports = {
|
||||
canImportHermesCli,
|
||||
hermesRuntimeImportProbe,
|
||||
verifyHermesCli,
|
||||
PROBE_TIMEOUT_MS
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ const fs = require('node:fs')
|
|||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
|
||||
const { canImportHermesCli, verifyHermesCli } = require('./backend-probes.cjs')
|
||||
const { canImportHermesCli, hermesRuntimeImportProbe, verifyHermesCli } = require('./backend-probes.cjs')
|
||||
|
||||
// Resolve the host's own Node binary -- guaranteed to be on disk and
|
||||
// runnable. We use it as both a stand-in for "a python that doesn't
|
||||
|
|
@ -40,6 +40,12 @@ test('canImportHermesCli returns false when binary does not exist', () => {
|
|||
assert.equal(canImportHermesCli(ghost), false)
|
||||
})
|
||||
|
||||
test('hermes runtime import probe checks config dependencies', () => {
|
||||
const probe = hermesRuntimeImportProbe()
|
||||
assert.match(probe, /\bimport yaml\b/)
|
||||
assert.match(probe, /\bimport hermes_cli\.config\b/)
|
||||
})
|
||||
|
||||
test('verifyHermesCli returns false when command is falsy', () => {
|
||||
assert.equal(verifyHermesCli(''), false)
|
||||
assert.equal(verifyHermesCli(null), false)
|
||||
|
|
|
|||
|
|
@ -2608,6 +2608,24 @@ function readBootstrapMarker() {
|
|||
return readJson(BOOTSTRAP_COMPLETE_MARKER)
|
||||
}
|
||||
|
||||
// Marker-independent: is the canonical install at ACTIVE_HERMES_ROOT actually
|
||||
// runnable right now? A complete CLI install (`install.sh --include-desktop`)
|
||||
// or a DMG launch over a prior CLI install satisfies this WITHOUT the desktop
|
||||
// ever having written the bootstrap marker -- so we must be able to recognise
|
||||
// "already installed" off the filesystem alone, not just the marker.
|
||||
function isActiveRuntimeUsable() {
|
||||
const venvPython = getVenvPython(VENV_ROOT)
|
||||
return (
|
||||
isHermesSourceRoot(ACTIVE_HERMES_ROOT) &&
|
||||
fileExists(venvPython) &&
|
||||
canImportHermesCli(venvPython, {
|
||||
env: {
|
||||
PYTHONPATH: [ACTIVE_HERMES_ROOT, process.env.PYTHONPATH].filter(Boolean).join(path.delimiter)
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function isBootstrapComplete() {
|
||||
const marker = readBootstrapMarker()
|
||||
if (!marker || typeof marker !== 'object') return false
|
||||
|
|
@ -2620,7 +2638,7 @@ function isBootstrapComplete() {
|
|||
// a runnable venv: an interrupted or split-home install can leave the marker
|
||||
// + checkout without a venv, and trusting that spawns a dead backend
|
||||
// ("gateway offline") instead of re-running bootstrap to repair it.
|
||||
return isHermesSourceRoot(ACTIVE_HERMES_ROOT) && fileExists(getVenvPython(VENV_ROOT))
|
||||
return isActiveRuntimeUsable()
|
||||
}
|
||||
|
||||
function writeBootstrapMarker(payload) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue