fix false missing Hermes CLI detection after deployment
This commit is contained in:
@@ -4,7 +4,9 @@ import * as hermesCli from '../services/hermes/hermes-cli'
|
||||
import { getAgentBridgeManager } from '../services/hermes/agent-bridge/manager'
|
||||
import { redactAgentBridgeError } from '../services/hermes/agent-bridge/redact'
|
||||
import { isDockerContainer } from '../services/runtime-environment'
|
||||
import { isHermesCliAvailable, resolveHermesBin } from '../services/hermes/hermes-process'
|
||||
import { probeHermesCli, resolveHermesBin, type HermesCliProbeResult } from '../services/hermes/hermes-process'
|
||||
|
||||
import { logger } from '../services/logger'
|
||||
|
||||
declare const __APP_VERSION__: string
|
||||
|
||||
@@ -209,19 +211,27 @@ export async function healthCheck(ctx: any) {
|
||||
}
|
||||
}
|
||||
|
||||
let cachedCliStatus: { value: boolean; expiresAt: number } | null = null
|
||||
let cachedCliStatus: { bin: string; value: HermesCliProbeResult; expiresAt: number } | null = null
|
||||
let pendingCliStatus: { bin: string; promise: Promise<HermesCliProbeResult> } | null = null
|
||||
const CLI_STATUS_CACHE_TTL_MS = 30_000
|
||||
const CLI_FAILURE_CACHE_TTL_MS = 5_000
|
||||
|
||||
/**
|
||||
* Whether the user-installed hermes CLI can be resolved and executed.
|
||||
* hermes-ui no longer bundles the CLI, so the web UI probes this
|
||||
* (public, pre-login) to show an install guide when it is missing.
|
||||
*/
|
||||
/** Public installation probe. Slow startup is distinct from a missing executable. */
|
||||
export async function hermesCliStatus(ctx: any) {
|
||||
const now = Date.now()
|
||||
if (!cachedCliStatus || cachedCliStatus.expiresAt <= now) {
|
||||
const available = await isHermesCliAvailable()
|
||||
cachedCliStatus = { value: available, expiresAt: now + CLI_STATUS_CACHE_TTL_MS }
|
||||
const bin = resolveHermesBin()
|
||||
if (ctx.query?.refresh !== '1' && cachedCliStatus?.bin === bin && cachedCliStatus.expiresAt > Date.now()) {
|
||||
ctx.body = { ...cachedCliStatus.value, bin }
|
||||
return
|
||||
}
|
||||
ctx.body = { available: cachedCliStatus.value, bin: resolveHermesBin() }
|
||||
if (!pendingCliStatus || pendingCliStatus.bin !== bin) {
|
||||
const promise = probeHermesCli(bin).then(value => {
|
||||
cachedCliStatus = { bin, value, expiresAt: Date.now() + (value.available ? CLI_STATUS_CACHE_TTL_MS : CLI_FAILURE_CACHE_TTL_MS) }
|
||||
if (!value.available) logger.warn({ bin, reason: value.reason }, 'Hermes CLI availability probe failed')
|
||||
return value
|
||||
}).finally(() => {
|
||||
if (pendingCliStatus?.promise === promise) pendingCliStatus = null
|
||||
})
|
||||
pendingCliStatus = { bin, promise }
|
||||
}
|
||||
ctx.body = { ...await pendingCliStatus.promise, bin }
|
||||
}
|
||||
|
||||
@@ -70,20 +70,37 @@ export function execHermes(args: readonly string[], options?: ExecFileOptions) {
|
||||
return execHermesWithBin(resolveHermesBin(), args, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe whether the user-installed hermes CLI is reachable.
|
||||
* hermes-ui no longer bundles the CLI, so startup/UI can guide the
|
||||
* user to install it instead of failing on every spawn.
|
||||
*/
|
||||
export async function isHermesCliAvailable(hermesBin = resolveHermesBin()): Promise<boolean> {
|
||||
export type HermesCliProbeReason = 'ready' | 'missing' | 'permission_denied' | 'timeout' | 'failed'
|
||||
|
||||
export interface HermesCliProbeResult {
|
||||
available: boolean
|
||||
reason: HermesCliProbeReason
|
||||
}
|
||||
|
||||
/** Use local help: --version may synchronously check upstream updates. */
|
||||
export async function probeHermesCli(hermesBin = resolveHermesBin()): Promise<HermesCliProbeResult> {
|
||||
try {
|
||||
await execHermesWithBin(hermesBin, ['--version'], { timeout: 5000 })
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
await execHermesWithBin(hermesBin, ['--help'], { timeout: 15_000 })
|
||||
return { available: true, reason: 'ready' }
|
||||
} catch (error: any) {
|
||||
const code = error?.code
|
||||
if (code === 'ENOENT' || code === 'ENOTDIR' || code === 127) {
|
||||
return { available: false, reason: 'missing' }
|
||||
}
|
||||
if (code === 'EACCES' || code === 'EPERM' || code === 126) {
|
||||
return { available: false, reason: 'permission_denied' }
|
||||
}
|
||||
if (code === 'ETIMEDOUT' || (error?.killed && error?.signal === 'SIGTERM')) {
|
||||
return { available: false, reason: 'timeout' }
|
||||
}
|
||||
return { available: false, reason: 'failed' }
|
||||
}
|
||||
}
|
||||
|
||||
export async function isHermesCliAvailable(hermesBin = resolveHermesBin()): Promise<boolean> {
|
||||
return (await probeHermesCli(hermesBin)).available
|
||||
}
|
||||
|
||||
export function spawnHermesWithBin(
|
||||
hermesBin: string,
|
||||
args: readonly string[],
|
||||
|
||||
Reference in New Issue
Block a user