mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
fix(desktop): treat connected-but-expired remote sessions as reauth
Add isRemoteReauthError so an auth-shaped boot error counts as a remote-reauth failure even when the session indicator still reads connected (a stale refresh cookie / failed ws-ticket mint). Wire the boot error into the overlay's reauth check so those sessions route to Sign in instead of the local-only recovery buttons. Co-authored-by: Tony Antunez <57689194+smtony@users.noreply.github.com>
This commit is contained in:
parent
c1c74d7518
commit
f3af066b85
3 changed files with 57 additions and 12 deletions
|
|
@ -67,7 +67,7 @@ export function BootFailureOverlay() {
|
|||
?.getRecentLogs()
|
||||
.then(res => setLogs(res.lines ?? []))
|
||||
.catch(() => undefined)
|
||||
}, [visible])
|
||||
}, [boot.error, visible])
|
||||
|
||||
// Resolve whether this boot failure is a remote-gateway reauth so we can
|
||||
// offer the actionable "Sign in" path instead of the local-only recovery
|
||||
|
|
@ -104,7 +104,7 @@ export function BootFailureOverlay() {
|
|||
|
||||
setRemoteFailure(isRemoteConfig(config))
|
||||
|
||||
if (!isRemoteReauthFailure(config)) {
|
||||
if (!isRemoteReauthFailure(config, boot.error)) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ export function BootFailureOverlay() {
|
|||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [visible])
|
||||
}, [boot.error, visible])
|
||||
|
||||
if (!visible || suppressed) {
|
||||
return null
|
||||
|
|
|
|||
|
|
@ -2,7 +2,13 @@ import { describe, expect, it } from 'vitest'
|
|||
|
||||
import type { DesktopConnectionConfig } from '@/global'
|
||||
|
||||
import { deriveProviderShape, isRemoteConfig, isRemoteReauthFailure, signInLabel } from './boot-failure-reauth'
|
||||
import {
|
||||
deriveProviderShape,
|
||||
isRemoteConfig,
|
||||
isRemoteReauthError,
|
||||
isRemoteReauthFailure,
|
||||
signInLabel
|
||||
} from './boot-failure-reauth'
|
||||
|
||||
function config(overrides: Partial<DesktopConnectionConfig> = {}): DesktopConnectionConfig {
|
||||
return {
|
||||
|
|
@ -37,8 +43,14 @@ describe('isRemoteReauthFailure', () => {
|
|||
expect(isRemoteReauthFailure(config())).toBe(true)
|
||||
})
|
||||
|
||||
it('false when the oauth session is still connected', () => {
|
||||
expect(isRemoteReauthFailure(config({ remoteOauthConnected: true }))).toBe(false)
|
||||
it('false when connected and the boot error is not auth-shaped', () => {
|
||||
expect(isRemoteReauthFailure(config({ remoteOauthConnected: true }), 'Python exploded')).toBe(false)
|
||||
})
|
||||
|
||||
it('true when the indicator reads connected but the boot error is auth-shaped (expired session)', () => {
|
||||
expect(
|
||||
isRemoteReauthFailure(config({ remoteOauthConnected: true }), 'Your remote gateway session has expired.')
|
||||
).toBe(true)
|
||||
})
|
||||
|
||||
it('false for a local gateway', () => {
|
||||
|
|
@ -69,6 +81,18 @@ describe('isRemoteReauthFailure', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('isRemoteReauthError', () => {
|
||||
it('recognizes auth-shaped boot errors', () => {
|
||||
expect(isRemoteReauthError('Your remote gateway session has expired.')).toBe(true)
|
||||
expect(isRemoteReauthError('OAuth: please sign in')).toBe(true)
|
||||
})
|
||||
|
||||
it('ignores non-auth boot errors and nullish', () => {
|
||||
expect(isRemoteReauthError('Hermes background process exited during startup.')).toBe(false)
|
||||
expect(isRemoteReauthError(null)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('deriveProviderShape', () => {
|
||||
it('generic copy when there are no providers', () => {
|
||||
expect(deriveProviderShape([])).toEqual({ isPassword: false, providerLabel: 'your identity provider' })
|
||||
|
|
|
|||
|
|
@ -34,12 +34,33 @@ export function isRemoteConfig(config: DesktopConnectionConfig | null | undefine
|
|||
return Boolean(config && (config.mode === 'remote' || config.mode === 'cloud') && config.remoteUrl)
|
||||
}
|
||||
|
||||
// A remote, gated (oauth-bucket), not-currently-connected gateway is a
|
||||
// remote-reauth boot failure: the access cookie lapsed and the local Retry/Repair
|
||||
// buttons can't fix it — only re-establishing the remote session can. 'cloud'
|
||||
// counts as remote (it resolves to a remote oauth backend).
|
||||
export function isRemoteReauthFailure(config: DesktopConnectionConfig | null | undefined): boolean {
|
||||
return isRemoteConfig(config) && config!.remoteAuthMode === 'oauth' && !config!.remoteOauthConnected
|
||||
// True when a boot error is auth-shaped — the refresh token was rejected or the
|
||||
// remote couldn't mint a websocket ticket. The Settings indicator can still read
|
||||
// "connected" (a stale RT cookie exists), so the error text is part of the
|
||||
// signal; without it a connected-but-expired session drops into the local-only
|
||||
// recovery buttons for a problem only reauth can fix.
|
||||
export function isRemoteReauthError(error: string | null | undefined): boolean {
|
||||
const text = String(error || '').toLowerCase()
|
||||
|
||||
return (
|
||||
text.includes('remote gateway session has expired') ||
|
||||
text.includes('gateway sign-in required') ||
|
||||
text.includes('needs oauth login') ||
|
||||
(text.includes('oauth') && (text.includes('not signed in') || text.includes('sign in')))
|
||||
)
|
||||
}
|
||||
|
||||
// A remote, gated (oauth-bucket) gateway is a remote-reauth boot failure when the
|
||||
// session isn't connected OR the boot error is auth-shaped (connected-but-expired
|
||||
// — see isRemoteReauthError). Only re-establishing the remote session fixes it;
|
||||
// the local Retry/Repair buttons can't. 'cloud' counts as remote (it resolves to
|
||||
// a remote oauth backend), so a lapsed cloud session is the same failure.
|
||||
export function isRemoteReauthFailure(config: DesktopConnectionConfig | null | undefined, error?: string | null): boolean {
|
||||
return (
|
||||
isRemoteConfig(config) &&
|
||||
config!.remoteAuthMode === 'oauth' &&
|
||||
(!config!.remoteOauthConnected || isRemoteReauthError(error))
|
||||
)
|
||||
}
|
||||
|
||||
// Derive the password flag + display label from the probed providers. A
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue