mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-21 16:18:55 +00:00
fix(desktop): follow compression's stored-id rotation to prevent thread reload (#65984)
Auto-compression ends the SessionDB session and forks a continuation, rotating the stored session id. The gateway emits `session.info` with the new `stored_session_id`, and the desktop's cache entry was updated via `ensureSessionState` — but the URL route and `$selectedStoredSessionId` never followed the rotation. On the next send, `getRuntimeIdForStoredSession(oldStoredId)` returned null (the cache entry's `storedSessionId` no longer matched the old id), so `routedSessionNeedsResume` evaluated true, triggering a full `session.resume` + REST transcript prefetch — the whole thread reloaded. Fix: a new `$activeSessionStoredId` atom is set in `ensureSessionState` when the active session's stored id changes. A `useEffect` in `use-session-actions` subscribes to it and re-anchors the route + selection (`setSelectedStoredSessionId` + `navigate(replace: true)`), and cleans up the stale stored→runtime mapping. `replace: true` because it's the same conversation — compression is transparent to the user, so back-button stays correct.
This commit is contained in:
parent
75467998f9
commit
39a93dc633
3 changed files with 50 additions and 2 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import type { MutableRefObject } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import { type MutableRefObject, useCallback, useEffect, useRef } from 'react'
|
||||
import type { NavigateFunction } from 'react-router-dom'
|
||||
|
||||
import { revealTreePane } from '@/components/pane-shell/tree/store'
|
||||
|
|
@ -13,6 +13,7 @@ import { clearNotifications, notify, notifyError } from '@/store/notifications'
|
|||
import { $activeGatewayProfile, $newChatProfile, ensureGatewayProfile, normalizeProfileKey } from '@/store/profile'
|
||||
import { resolveNewSessionCwd, tombstoneSessions, untombstoneSessions } from '@/store/projects'
|
||||
import {
|
||||
$activeSessionStoredId,
|
||||
$currentCwd,
|
||||
$currentFastMode,
|
||||
$currentModel,
|
||||
|
|
@ -177,6 +178,33 @@ export function useSessionActions({
|
|||
const copy = t.desktop
|
||||
const resumeRequestRef = useRef(0)
|
||||
|
||||
// Follow auto-compression's stored-id rotation. When the active session's
|
||||
// stored id changes (compression ends the SessionDB session and forks a
|
||||
// continuation), re-anchor the URL route + selection to the new id so the
|
||||
// next send doesn't hit a stale stored→runtime mapping and trigger a full
|
||||
// thread reload. replace: true — it's the same conversation, not a new
|
||||
// history entry.
|
||||
const rotatedStoredId = useStore($activeSessionStoredId)
|
||||
|
||||
useEffect(() => {
|
||||
if (!rotatedStoredId || rotatedStoredId === selectedStoredSessionIdRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
const oldStoredId = selectedStoredSessionIdRef.current
|
||||
|
||||
setSelectedStoredSessionId(rotatedStoredId)
|
||||
selectedStoredSessionIdRef.current = rotatedStoredId
|
||||
navigate(sessionRoute(rotatedStoredId), { replace: true })
|
||||
|
||||
// Clean up the stale stored→runtime mapping so getRuntimeIdForStoredSession
|
||||
// can't resolve the old id to this runtime (it would fail the storedSessionId
|
||||
// check and return null, but leaving the stale key is sloppy).
|
||||
if (oldStoredId) {
|
||||
runtimeIdByStoredSessionIdRef.current.delete(oldStoredId)
|
||||
}
|
||||
}, [rotatedStoredId, navigate, runtimeIdByStoredSessionIdRef, selectedStoredSessionIdRef])
|
||||
|
||||
const startFreshSessionDraft = useCallback(
|
||||
(options: boolean | FreshSessionDraftOptions = false) => {
|
||||
const draftOptions = typeof options === 'boolean' ? { replaceRoute: options } : options
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import { preserveLocalAssistantErrors } from '@/lib/chat-messages'
|
|||
import { createClientSessionState } from '@/lib/chat-runtime'
|
||||
import { setMutableRef } from '@/lib/mutable-ref'
|
||||
import {
|
||||
$activeSessionId,
|
||||
$busy,
|
||||
$messages,
|
||||
noteSessionActivity,
|
||||
onSessionWatchdogClear,
|
||||
setActiveSessionStoredId,
|
||||
setCurrentFastMode,
|
||||
setCurrentModel,
|
||||
setCurrentPersonality,
|
||||
|
|
@ -115,6 +117,15 @@ export function useSessionStateCache({
|
|||
|
||||
if (previousStoredSessionId && previousStoredSessionId !== storedSessionId) {
|
||||
setSessionWorking(previousStoredSessionId, false)
|
||||
|
||||
// Auto-compression rotated the stored id on the active session. Signal
|
||||
// the route-following effect in use-session-actions so the URL + selection
|
||||
// re-anchor to the continuation id — otherwise the next send hits a stale
|
||||
// stored→runtime mapping (getRuntimeIdForStoredSession returns null) and
|
||||
// triggers a full thread reload via resumeStoredSession.
|
||||
if (sessionId === $activeSessionId.get()) {
|
||||
setActiveSessionStoredId(storedSessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -248,6 +248,13 @@ export const $sessionsLoading = atom(true)
|
|||
export const $workingSessionIds = atom<string[]>([])
|
||||
export const $activeSessionId = atom<string | null>(null)
|
||||
export const $selectedStoredSessionId = atom<string | null>(null)
|
||||
// Reactive signal for when the active session's stored id rotates (auto-
|
||||
// compression ends the SessionDB session and forks a continuation). The
|
||||
// route + selection must follow the rotation so the next send doesn't
|
||||
// trigger a full thread reload (getRuntimeIdForStoredSession would return
|
||||
// null for the old stored id, forcing resumeStoredSession). Set in
|
||||
// ensureSessionState when the cache entry's storedSessionId changes.
|
||||
export const $activeSessionStoredId = atom<string | null>(null)
|
||||
export const $messages = atom<ChatMessage[]>([])
|
||||
|
||||
// Streaming-stable derivations of $messages. During a token stream the array
|
||||
|
|
@ -321,6 +328,8 @@ export const setSessionProfileTotals = (next: Updater<Record<string, number>>) =
|
|||
export const setSessionsLoading = (next: Updater<boolean>) => updateAtom($sessionsLoading, next)
|
||||
export const setWorkingSessionIds = (next: Updater<string[]>) => updateAtom($workingSessionIds, next)
|
||||
export const setActiveSessionId = (next: Updater<string | null>) => updateAtom($activeSessionId, next)
|
||||
export const setActiveSessionStoredId = (next: Updater<string | null>) =>
|
||||
updateAtom($activeSessionStoredId, next)
|
||||
|
||||
export const setSelectedStoredSessionId = (next: Updater<string | null>) => {
|
||||
updateAtom($selectedStoredSessionId, next)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue