mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-30 19:09:28 +00:00
Merge pull request #74277 from NousResearch/bb/remembered-route-per-profile
fix(desktop): scope the remembered route per profile
This commit is contained in:
commit
b84389c625
3 changed files with 83 additions and 9 deletions
|
|
@ -71,15 +71,17 @@ export function useDesktopIntegrations({
|
|||
// lands where you were. Overlays (settings/command-center/…) aren't stored —
|
||||
// you don't want to boot into a modal.
|
||||
useEffect(() => {
|
||||
const routeProfile = rememberedSessionProfile($sessions.get(), routedSessionId, $activeGatewayProfile.get())
|
||||
|
||||
if (routedSessionId) {
|
||||
setRememberedSessionId(
|
||||
routedSessionId,
|
||||
rememberedSessionProfile($sessions.get(), routedSessionId, $activeGatewayProfile.get())
|
||||
)
|
||||
setRememberedSessionId(routedSessionId, routeProfile)
|
||||
}
|
||||
|
||||
if (!isOverlayView(appViewForPath(locationPathname))) {
|
||||
setRememberedRoute(locationPathname)
|
||||
// Keyed by the same owner as the id above: a session route embeds a
|
||||
// session id, so remembering it globally would restore another profile's
|
||||
// conversation on cold start.
|
||||
setRememberedRoute(locationPathname, routeProfile)
|
||||
}
|
||||
}, [locationPathname, routedSessionId])
|
||||
|
||||
|
|
@ -97,7 +99,8 @@ export function useDesktopIntegrations({
|
|||
}
|
||||
|
||||
restoredRef.current = true
|
||||
const route = getRememberedRoute()
|
||||
const activeProfile = $activeGatewayProfile.get()
|
||||
const route = getRememberedRoute(activeProfile)
|
||||
|
||||
if (route && route !== NEW_CHAT_ROUTE && !isOverlayView(appViewForPath(route))) {
|
||||
navigate(route, { replace: true })
|
||||
|
|
@ -105,7 +108,7 @@ export function useDesktopIntegrations({
|
|||
return
|
||||
}
|
||||
|
||||
const last = getRememberedSessionId($activeGatewayProfile.get())
|
||||
const last = getRememberedSessionId(activeProfile)
|
||||
|
||||
if (last) {
|
||||
navigate(sessionRoute(last), { replace: true })
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ import {
|
|||
$sessions,
|
||||
$unreadFinishedSessionIds,
|
||||
applyConfiguredDefaultProjectDir,
|
||||
getRememberedRoute,
|
||||
getRememberedSessionId,
|
||||
mergeSessionPage,
|
||||
rememberedSessionProfile,
|
||||
resolveComposerSessionKey,
|
||||
sessionPinId,
|
||||
setCurrentCwd,
|
||||
setRememberedRoute,
|
||||
setRememberedSessionId,
|
||||
setSelectedStoredSessionId,
|
||||
setSessions,
|
||||
|
|
@ -507,6 +509,59 @@ describe('remembered session id (per profile)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('remembered route (per profile)', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
localStorage.clear()
|
||||
})
|
||||
|
||||
it('scopes the remembered route by profile so one profile cannot restore another', () => {
|
||||
// A session route embeds a session id. Remembered globally, a cold start
|
||||
// under 'default' would navigate straight into ai-engineer's conversation.
|
||||
setRememberedRoute('/session/work-session', 'ai-engineer')
|
||||
setRememberedRoute('/session/personal-session', 'default')
|
||||
|
||||
expect(getRememberedRoute('ai-engineer')).toBe('/session/work-session')
|
||||
expect(getRememberedRoute('default')).toBe('/session/personal-session')
|
||||
expect(getRememberedRoute('research')).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps the default profile on the legacy unsuffixed key for back-compat', () => {
|
||||
localStorage.setItem('hermes.desktop.lastRoute', '/skills')
|
||||
|
||||
expect(getRememberedRoute('default')).toBe('/skills')
|
||||
expect(getRememberedRoute(undefined)).toBe('/skills')
|
||||
expect(getRememberedRoute('')).toBe('/skills')
|
||||
expect(getRememberedRoute(null)).toBe('/skills')
|
||||
})
|
||||
|
||||
it('clearing one profile leaves the others intact', () => {
|
||||
setRememberedRoute('/session/work-session', 'ai-engineer')
|
||||
setRememberedRoute('/session/personal-session', 'default')
|
||||
|
||||
setRememberedRoute(null, 'ai-engineer')
|
||||
|
||||
expect(getRememberedRoute('ai-engineer')).toBeNull()
|
||||
expect(getRememberedRoute('default')).toBe('/session/personal-session')
|
||||
})
|
||||
|
||||
it('route and session id agree on the owner, so restore cannot cross profiles', () => {
|
||||
// The cold-start restore prefers the route over the id, so the two keys
|
||||
// must be written under the same owner or the id scoping is bypassed.
|
||||
const owner = rememberedSessionProfile([session({ id: 'stored-1', profile: 'ai-engineer' })], 'stored-1', 'default')
|
||||
|
||||
setRememberedSessionId('stored-1', owner)
|
||||
setRememberedRoute('/session/stored-1', owner)
|
||||
|
||||
expect(getRememberedRoute('default')).toBeNull()
|
||||
expect(getRememberedSessionId('default')).toBeNull()
|
||||
expect(getRememberedRoute('ai-engineer')).toBe('/session/stored-1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('rememberedSessionProfile', () => {
|
||||
it('keys by the session row owning profile, not the active one', () => {
|
||||
const sessions = [session({ id: 'stored-1', profile: 'ai-engineer' })]
|
||||
|
|
|
|||
|
|
@ -72,10 +72,26 @@ export function rememberedSessionProfile(
|
|||
|
||||
// The last non-overlay route (a page like /skills, or a session route), so a
|
||||
// relaunch lands back where you were instead of a bare new-chat.
|
||||
//
|
||||
// Scoped per profile for the same reason the remembered session id is: a single
|
||||
// global key remembered ONE route across every profile, and a session route
|
||||
// carries a session id in its path. Restoring under profile B would navigate to
|
||||
// a session owned by profile A — the remembered-id scoping above is bypassed
|
||||
// entirely, because the route is preferred over the id on cold start
|
||||
// (#67603 family). The default profile keeps the original unsuffixed key so
|
||||
// existing installs' remembered route survives the upgrade.
|
||||
const LAST_ROUTE_KEY = 'hermes.desktop.lastRoute'
|
||||
|
||||
export const getRememberedRoute = (): null | string => storedString(LAST_ROUTE_KEY)
|
||||
export const setRememberedRoute = (path: null | string) => persistString(LAST_ROUTE_KEY, path)
|
||||
function rememberedRouteKey(profile?: null | string): string {
|
||||
const key = (profile ?? '').trim()
|
||||
|
||||
return !key || key === 'default' ? LAST_ROUTE_KEY : `${LAST_ROUTE_KEY}.${key}`
|
||||
}
|
||||
|
||||
export const getRememberedRoute = (profile?: null | string): null | string =>
|
||||
storedString(rememberedRouteKey(profile))
|
||||
export const setRememberedRoute = (path: null | string, profile?: null | string) =>
|
||||
persistString(rememberedRouteKey(profile), path)
|
||||
|
||||
let configuredDefaultProjectDir = ''
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue