mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
The boot-failure screenshot showed a progress bar because of two bugs:
1. waitForBootFailure matched on "Let's get you setup" (the onboarding
header that mounts from frame 1 during normal boot), so the screenshot
fired at ~86% progress while the Preparing component's progress bar was
still painted.
2. The Preparing component kept rendering the progress bar even after
boot.error was set — it just turned the bar red and appended the error
text below it.
Fixes:
- Preparing bails out (returns null) when boot.error is set, so
BootFailureOverlay (z-1400) owns the screen exclusively.
- applyDesktopBootProgress no longer clobbers a previously-set boot.error
when a late progress event arrives with error: null — failDesktopBoot is
terminal for the boot cycle.
- waitForBootFailure guards against progress bars being visible and matches
on actual failure signals (error toast, Retry/Repair buttons), not the
onboarding header.
- setupDeadBackend now accepts { fakeError: true } which injects
HERMES_DESKTOP_BOOT_FAKE_ERROR to trigger a real boot failure — the
previous dead-provider fixture never actually caused a boot failure
(hermes serve starts fine; the dead endpoint only matters at chat time).
- boot-failure.spec.ts updated to use { fakeError: true }.
Verified: e2e test passes with 0 progress bars in the DOM at screenshot
time (confirmed via DOM inspection), 16/16 vitest tests pass, typecheck
clean.
95 lines
2.4 KiB
TypeScript
95 lines
2.4 KiB
TypeScript
import { atom } from 'nanostores'
|
|
|
|
import type { DesktopBootProgress } from '@/global'
|
|
import { translateNow } from '@/i18n'
|
|
|
|
export interface DesktopBootState extends DesktopBootProgress {
|
|
visible: boolean
|
|
}
|
|
|
|
const INITIAL_BOOT_STATE: DesktopBootState = {
|
|
error: null,
|
|
fakeMode: false,
|
|
message: translateNow('boot.steps.startingHermesDesktop'),
|
|
phase: 'renderer.init',
|
|
progress: 2,
|
|
running: true,
|
|
timestamp: Date.now(),
|
|
visible: true
|
|
}
|
|
|
|
export const $desktopBoot = atom<DesktopBootState>(INITIAL_BOOT_STATE)
|
|
|
|
function clampProgress(value: number) {
|
|
if (!Number.isFinite(value)) {
|
|
return 0
|
|
}
|
|
|
|
return Math.max(0, Math.min(100, Math.round(value)))
|
|
}
|
|
|
|
export function applyDesktopBootProgress(progress: DesktopBootProgress) {
|
|
const current = $desktopBoot.get()
|
|
const nextProgress = clampProgress(progress.progress)
|
|
const mergedProgress = progress.running ? Math.max(current.progress, nextProgress) : nextProgress
|
|
|
|
// Don't let a late progress event (error: null) clobber a previously-set
|
|
// boot failure — failDesktopBoot is terminal for this boot cycle.
|
|
const error = progress.error ?? (current.running ? null : current.error)
|
|
|
|
$desktopBoot.set({
|
|
...current,
|
|
...progress,
|
|
error,
|
|
progress: mergedProgress,
|
|
visible: progress.running || mergedProgress < 100 || Boolean(error)
|
|
})
|
|
}
|
|
|
|
export function setDesktopBootStep(step: {
|
|
phase: string
|
|
message: string
|
|
progress: number
|
|
running?: boolean
|
|
fakeMode?: boolean
|
|
error?: string | null
|
|
}) {
|
|
const current = $desktopBoot.get()
|
|
applyDesktopBootProgress({
|
|
error: step.error ?? null,
|
|
fakeMode: step.fakeMode ?? current.fakeMode,
|
|
message: step.message,
|
|
phase: step.phase,
|
|
progress: step.progress,
|
|
running: step.running ?? true,
|
|
timestamp: Date.now()
|
|
})
|
|
}
|
|
|
|
export function completeDesktopBoot(message = translateNow('boot.ready')) {
|
|
const current = $desktopBoot.get()
|
|
$desktopBoot.set({
|
|
...current,
|
|
error: null,
|
|
message,
|
|
phase: 'renderer.ready',
|
|
progress: 100,
|
|
running: false,
|
|
timestamp: Date.now(),
|
|
visible: false
|
|
})
|
|
}
|
|
|
|
export function failDesktopBoot(message: string) {
|
|
const current = $desktopBoot.get()
|
|
$desktopBoot.set({
|
|
...current,
|
|
error: message,
|
|
message: translateNow('boot.desktopBootFailedWithMessage', message),
|
|
phase: 'renderer.error',
|
|
progress: clampProgress(current.progress),
|
|
running: false,
|
|
timestamp: Date.now(),
|
|
visible: true
|
|
})
|
|
}
|