mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-27 17:58:07 +00:00
fix(desktop): give ⌘T session tabs a live gateway so slash completions load
`useGatewayRequest` only exposed the gateway through a ref that its own
subscription effect fills in, so a component reading it during render saw
null on mount. Session tiles — what ⌘T and the tab-strip "+" open — did
exactly that and passed `gateway={null}` down to ChatBar. The slash
adapter is disabled without a gateway, so a new tab had no completions at
all while ⌘N, which resolves its gateway through a state-driven memo,
worked fine. Nothing re-rendered the tile afterwards unless the connection
state happened to flip, so it never recovered.
Return the `$gateway` atom as a reactive value alongside the ref and use
it wherever the gateway is needed as a render-time value. The ref stays
for `requestGateway`, which wants a stable identity. The model picker,
model visibility, and settings overlays read the same ref during render,
so they're moved over too.
This commit is contained in:
parent
58d805302a
commit
bfc8e3b00d
4 changed files with 56 additions and 9 deletions
|
|
@ -113,7 +113,7 @@ function TileChat({
|
|||
storedSessionId: string
|
||||
view: SessionView
|
||||
}) {
|
||||
const { gatewayRef, requestGateway } = useGatewayRequest()
|
||||
const { gateway, requestGateway } = useGatewayRequest()
|
||||
const queryClient = useQueryClient()
|
||||
const { selectModel } = useModelControls({ queryClient, requestGateway })
|
||||
const activeGatewayProfile = useStore($activeGatewayProfile)
|
||||
|
|
@ -151,20 +151,20 @@ function TileChat({
|
|||
() =>
|
||||
gatewayOpen ? (
|
||||
<ModelMenuPanel
|
||||
gateway={gatewayRef.current || undefined}
|
||||
gateway={gateway || undefined}
|
||||
onSelectModel={selectModel}
|
||||
profile={activeGatewayProfile}
|
||||
requestGateway={requestGateway}
|
||||
/>
|
||||
) : null,
|
||||
[activeGatewayProfile, gatewayOpen, gatewayRef, requestGateway, selectModel]
|
||||
[activeGatewayProfile, gateway, gatewayOpen, requestGateway, selectModel]
|
||||
)
|
||||
|
||||
return (
|
||||
<SessionViewProvider value={view}>
|
||||
<ComposerScopeProvider value={scope}>
|
||||
<ChatView
|
||||
gateway={gatewayRef.current}
|
||||
gateway={gateway}
|
||||
modelMenuContent={modelMenuContent}
|
||||
onAddContextRef={composer.addContextRefAttachment}
|
||||
onAddUrl={url => composer.addContextRefAttachment(`@url:${formatRefValue(url)}`, url)}
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export function ContribWiring({ children }: { children: ReactNode }) {
|
|||
setMessages
|
||||
})
|
||||
|
||||
const { connectionRef, gatewayRef, requestGateway } = useGatewayRequest()
|
||||
const { connectionRef, gateway, gatewayRef, requestGateway } = useGatewayRequest()
|
||||
|
||||
const {
|
||||
loadMoreMessagingForPlatform,
|
||||
|
|
@ -943,13 +943,13 @@ export function ContribWiring({ children }: { children: ReactNode }) {
|
|||
/>
|
||||
)}
|
||||
<ModelPickerOverlay
|
||||
gateway={gatewayRef.current || undefined}
|
||||
gateway={gateway || undefined}
|
||||
onSelect={selectModel}
|
||||
profile={activeGatewayProfile}
|
||||
/>
|
||||
<SessionPickerOverlay onResume={resumeSession} />
|
||||
<ModelVisibilityOverlay
|
||||
gateway={gatewayRef.current || undefined}
|
||||
gateway={gateway || undefined}
|
||||
onOpenProviders={openProviderSettings}
|
||||
profile={activeGatewayProfile}
|
||||
/>
|
||||
|
|
@ -965,7 +965,7 @@ export function ContribWiring({ children }: { children: ReactNode }) {
|
|||
{settingsOpen && (
|
||||
<Suspense fallback={null}>
|
||||
<SettingsView
|
||||
gateway={gatewayRef.current}
|
||||
gateway={gateway}
|
||||
onClose={closeOverlayToPreviousRoute}
|
||||
onConfigSaved={() => {
|
||||
void refreshHermesConfig()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import { act, renderHook } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import type { HermesGateway } from '@/hermes'
|
||||
import { $gateway } from '@/store/gateway'
|
||||
|
||||
import { useGatewayRequest } from './use-gateway-request'
|
||||
|
||||
const fakeGateway = { connectionState: 'open' } as unknown as HermesGateway
|
||||
|
||||
afterEach(() => {
|
||||
$gateway.set(null)
|
||||
})
|
||||
|
||||
describe('useGatewayRequest', () => {
|
||||
// The composer's `/` completions only exist when ChatBar receives a non-null
|
||||
// gateway PROP. `gatewayRef` is populated by a subscription effect, so it is
|
||||
// still null on the first render — a surface that read the ref while
|
||||
// rendering (session tiles / ⌘T tabs) shipped `gateway={null}` and silently
|
||||
// lost slash completions. The returned `gateway` value must be live
|
||||
// immediately so that never happens again.
|
||||
it('exposes the live gateway on the first render, before effects run', () => {
|
||||
$gateway.set(fakeGateway)
|
||||
|
||||
const { result } = renderHook(() => useGatewayRequest())
|
||||
|
||||
expect(result.current.gateway).toBe(fakeGateway)
|
||||
})
|
||||
|
||||
it('tracks the gateway when the active socket changes', () => {
|
||||
const { result } = renderHook(() => useGatewayRequest())
|
||||
|
||||
expect(result.current.gateway).toBeNull()
|
||||
|
||||
act(() => $gateway.set(fakeGateway))
|
||||
|
||||
expect(result.current.gateway).toBe(fakeGateway)
|
||||
})
|
||||
})
|
||||
|
|
@ -9,6 +9,14 @@ import { $gatewayState, setConnection } from '@/store/session'
|
|||
|
||||
export function useGatewayRequest() {
|
||||
const gatewayState = useStore($gatewayState)
|
||||
// Reactive companion to `gatewayRef`. The ref exists so `requestGateway`
|
||||
// keeps a stable identity and always reaches the live socket, but it is only
|
||||
// populated by the subscription effect below — i.e. AFTER the first render.
|
||||
// A component that reads `gatewayRef.current` while rendering therefore sees
|
||||
// null on mount, and if the connection state doesn't happen to flip
|
||||
// afterwards it never re-renders to pick the instance up. Anything that needs
|
||||
// the gateway as a render-time VALUE (props, memo deps) must use this.
|
||||
const gateway = useStore($gateway) as HermesGateway | null
|
||||
const gatewayRef = useRef<HermesGateway | null>(null)
|
||||
|
||||
const connectionRef = useRef<Awaited<ReturnType<NonNullable<typeof window.hermesDesktop>['getConnection']>> | null>(
|
||||
|
|
@ -136,5 +144,5 @@ export function useGatewayRequest() {
|
|||
[ensureGatewayOpen]
|
||||
)
|
||||
|
||||
return { connectionRef, gatewayRef, requestGateway }
|
||||
return { connectionRef, gateway, gatewayRef, requestGateway }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue