mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-27 11:22:03 +00:00
fix(desktop): give the gateway reconnect loop an escape hatch
When a remote gateway dropped after a healthy boot (internet loss, sleep/wake, VPS restart), use-gateway-boot retried with backoff forever and never surfaced an error. The renderer sat behind the fullscreen CONNECTING overlay with gatewayState non-open and boot.error null — no way to reach Settings, sign in again, or switch to a local gateway. To the user the app was simply broken on connection loss. Raise a recoverable boot error once the reconnect loop crosses RECONNECT_ESCALATE_AFTER (6 attempts, ≈45s), so the BootFailureOverlay (Retry / Sign in / Use local gateway) replaces the dead-end CONNECTING screen. The loop keeps retrying underneath; the next successful reconnect (or a manual/wake-driven one) clears the error and dismisses the overlay. This implements the contract already specified — but never wired up — in use-gateway-boot.test.tsx (desktop vitest isn't in CI, so the failing "FIX:" specs went unnoticed). All 4 hook tests + the 3 connecting-overlay tests pass.
This commit is contained in:
parent
41b9b7e719
commit
2a75c4a8cb
6 changed files with 23 additions and 0 deletions
|
|
@ -40,6 +40,13 @@ import {
|
|||
} from '@/store/session'
|
||||
import type { RpcEvent } from '@/types/hermes'
|
||||
|
||||
// After this many consecutive failed reconnects (≈45s with the 1→15s backoff)
|
||||
// raise a recoverable boot error. Otherwise a dropped remote gateway loops the
|
||||
// backoff forever behind the fullscreen CONNECTING overlay with no way to reach
|
||||
// Settings / sign in / switch to local — the "lost connection breaks the app"
|
||||
// dead end. The next successful reconnect clears it.
|
||||
const RECONNECT_ESCALATE_AFTER = 6
|
||||
|
||||
interface GatewayBootOptions {
|
||||
handleGatewayEvent: (event: RpcEvent) => void
|
||||
onConnectionReady: (
|
||||
|
|
@ -105,6 +112,10 @@ export function useGatewayBoot({
|
|||
// tick — a stale OAuth ticket fails every attempt and would otherwise stack
|
||||
// identical error toasts (and their haptics). Reset on the next clean open.
|
||||
let reauthNotified = false
|
||||
// Raised once the reconnect loop crosses RECONNECT_ESCALATE_AFTER so the
|
||||
// recovery overlay replaces the dead-end CONNECTING screen. Reset on a clean
|
||||
// open or a manual/wake-driven reconnect.
|
||||
let escalated = false
|
||||
|
||||
// Wrap the live getter in a call so TS control-flow analysis doesn't narrow
|
||||
// `connectionState` to a constant across the early-return guards (the state
|
||||
|
|
@ -171,6 +182,11 @@ export function useGatewayBoot({
|
|||
reconnecting = false
|
||||
|
||||
if (!cancelled && !gatewayOpen()) {
|
||||
if (reconnectAttempt >= RECONNECT_ESCALATE_AFTER && !escalated) {
|
||||
escalated = true
|
||||
failDesktopBoot(translateNow('boot.errors.gatewayConnectionLost'))
|
||||
}
|
||||
|
||||
scheduleReconnect()
|
||||
}
|
||||
}
|
||||
|
|
@ -197,6 +213,7 @@ export function useGatewayBoot({
|
|||
|
||||
clearReconnectTimer()
|
||||
reconnectAttempt = 0
|
||||
escalated = false
|
||||
reconnectSecondaryGateways()
|
||||
|
||||
if (!gatewayOpen()) {
|
||||
|
|
@ -230,6 +247,7 @@ export function useGatewayBoot({
|
|||
if (st === 'open') {
|
||||
reconnectAttempt = 0
|
||||
reauthNotified = false
|
||||
escalated = false
|
||||
clearReconnectTimer()
|
||||
|
||||
// A revalidate-driven reconnect can rebuild the backend in place when the
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export const en: Translations = {
|
|||
backgroundExitedDuringStartup: 'Hermes background process exited during startup.',
|
||||
backendStopped: 'Backend stopped',
|
||||
desktopBootFailed: 'Desktop boot failed',
|
||||
gatewayConnectionLost: 'Lost connection to the gateway',
|
||||
gatewaySignInRequired: 'Gateway sign-in required',
|
||||
ipcBridgeUnavailable: 'Desktop IPC bridge is unavailable.'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export const ja = defineLocale({
|
|||
backgroundExitedDuringStartup: '起動中に Hermes バックグラウンドプロセスが終了しました。',
|
||||
backendStopped: 'バックエンドが停止しました',
|
||||
desktopBootFailed: 'デスクトップの起動に失敗しました',
|
||||
gatewayConnectionLost: 'ゲートウェイへの接続が切断されました',
|
||||
gatewaySignInRequired: 'ゲートウェイへのサインインが必要です',
|
||||
ipcBridgeUnavailable: 'デスクトップ IPC ブリッジが利用できません。'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ export interface Translations {
|
|||
backgroundExitedDuringStartup: string
|
||||
backendStopped: string
|
||||
desktopBootFailed: string
|
||||
gatewayConnectionLost: string
|
||||
gatewaySignInRequired: string
|
||||
ipcBridgeUnavailable: string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export const zhHant = defineLocale({
|
|||
backgroundExitedDuringStartup: 'Hermes 背景程序在啟動期間結束。',
|
||||
backendStopped: '後端已停止',
|
||||
desktopBootFailed: '桌面啟動失敗',
|
||||
gatewayConnectionLost: '與閘道的連線已中斷',
|
||||
gatewaySignInRequired: '需要閘道登入',
|
||||
ipcBridgeUnavailable: '桌面 IPC 橋接器不可用。'
|
||||
},
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export const zh: Translations = {
|
|||
backgroundExitedDuringStartup: 'Hermes 后台进程在启动期间退出。',
|
||||
backendStopped: '后端已停止',
|
||||
desktopBootFailed: '桌面启动失败',
|
||||
gatewayConnectionLost: '与网关的连接已断开',
|
||||
gatewaySignInRequired: '需要登录网关',
|
||||
ipcBridgeUnavailable: '桌面 IPC 桥不可用。'
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue