mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(desktop): latch a confirmed remote reauth failure so the overlay stays clickable
shouldLatchBackendStartFailure deliberately never latches a remote failure: remote faults are usually transient and must stay retryable without an app restart. A confirmed reauth rejection is the exception — it cannot self-heal, because nothing changes until the user signs in again. Worse, not latching actively prevents the recovery it was protecting. A non-latching remote boot failure re-runs startHermes on every getConnection/api call, re-emits running: true, and the boot-failure overlay (visible = Boolean(boot.error) && !boot.running) hides itself — so the "Sign in" button flickers out from under the user before it can be clicked. Add shouldLatchRemoteReauthFailure as a separate predicate rather than changing the existing one, so transient remote failures keep self-healing. The two latches are complementary and never fire for the same failure.
This commit is contained in:
parent
cf4fb8993f
commit
36e2228a4c
2 changed files with 61 additions and 1 deletions
|
|
@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
|||
|
||||
import { test } from 'vitest'
|
||||
|
||||
import { shouldLatchBackendStartFailure } from './backend-start-failure'
|
||||
import { shouldLatchBackendStartFailure, shouldLatchRemoteReauthFailure } from './backend-start-failure'
|
||||
|
||||
test('latches a LOCAL backend failure so the install-retry loop is broken', () => {
|
||||
assert.equal(shouldLatchBackendStartFailure({ attemptedRemote: false }), true)
|
||||
|
|
@ -21,3 +21,32 @@ test('the two branches are mutually exclusive (a failure either latches or stays
|
|||
assert.equal(latched, !attemptedRemote)
|
||||
}
|
||||
})
|
||||
|
||||
test('latches a CONFIRMED remote reauth failure so the overlay stays clickable', () => {
|
||||
// Without this the non-latching remote path re-runs startHermes on every
|
||||
// getConnection/api call, re-emits running:true, and the overlay hides
|
||||
// itself — the "Sign in" button flickers away before it can be clicked.
|
||||
assert.equal(shouldLatchRemoteReauthFailure({ attemptedRemote: true, isReauth: true }), true)
|
||||
})
|
||||
|
||||
test('does not latch a transient remote failure as reauth', () => {
|
||||
// A mint timeout or a host unreachable across sleep must still self-heal.
|
||||
assert.equal(shouldLatchRemoteReauthFailure({ attemptedRemote: true, isReauth: false }), false)
|
||||
})
|
||||
|
||||
test('never latches a LOCAL failure as reauth (that is backendStartFailure job)', () => {
|
||||
assert.equal(shouldLatchRemoteReauthFailure({ attemptedRemote: false, isReauth: true }), false)
|
||||
assert.equal(shouldLatchRemoteReauthFailure({ attemptedRemote: false, isReauth: false }), false)
|
||||
})
|
||||
|
||||
test('the two latches never fire for the same failure', () => {
|
||||
// They are complementary, not overlapping: local failures latch via
|
||||
// backendStartFailure, confirmed remote reauth latches via its own flag.
|
||||
for (const attemptedRemote of [true, false]) {
|
||||
for (const isReauth of [true, false]) {
|
||||
const start = shouldLatchBackendStartFailure({ attemptedRemote })
|
||||
const reauth = shouldLatchRemoteReauthFailure({ attemptedRemote, isReauth })
|
||||
assert.ok(!(start && reauth), `both latched for remote=${attemptedRemote} reauth=${isReauth}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -39,3 +39,34 @@ export interface BackendStartFailureContext {
|
|||
export function shouldLatchBackendStartFailure(context: BackendStartFailureContext): boolean {
|
||||
return !context.attemptedRemote
|
||||
}
|
||||
|
||||
export interface RemoteReauthFailureContext {
|
||||
/** True when the boot that just failed was dialing a REMOTE (or cloud) backend. */
|
||||
attemptedRemote: boolean
|
||||
/**
|
||||
* True when the failure was a CONFIRMED auth rejection (a credentialed
|
||||
* probe got 401/403), not a transient connectivity fault.
|
||||
*/
|
||||
isReauth: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a failed remote boot should latch as a reauth failure.
|
||||
*
|
||||
* This is the deliberate counterpart to `shouldLatchBackendStartFailure`,
|
||||
* which never latches a remote failure because remote faults are usually
|
||||
* transient and must stay retryable. A *confirmed* reauth rejection is the
|
||||
* exception: it cannot self-heal, because nothing will change until the user
|
||||
* signs in again.
|
||||
*
|
||||
* Without a latch, the non-latching remote path actively prevents recovery.
|
||||
* Every subsequent `getConnection`/`api` call re-runs `startHermes`, re-emits
|
||||
* `running: true`, and the boot-failure overlay (`visible = Boolean(boot.error)
|
||||
* && !boot.running`) hides itself — so the "Sign in" button flickers out from
|
||||
* under the user before they can click it. Latching holds the overlay still
|
||||
* and clickable. Cleared on every recovery path (reset, repair, apply-config,
|
||||
* and a confirmed sign-in) so a fresh session boots normally.
|
||||
*/
|
||||
export function shouldLatchRemoteReauthFailure(context: RemoteReauthFailureContext): boolean {
|
||||
return context.attemptedRemote && context.isReauth
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue