mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-21 10:22:18 +00:00
fix(desktop): resume stored session id on notification click
Native notifications (approval / sudo / secret / clarify) are tagged with the gateway *runtime* session id — the key under which the session lives in the gateway's in-memory `_sessions` map and the id every event carries (`tui_gateway/server.py` `_emit(event, sid, ...)`). The chat route, however, is keyed by the *stored* session id (`stored_session_id`), which is a different value: a new chat gets its runtime id immediately but its stored id only once the first turn persists. `onFocusSession` navigated straight to `sessionRoute(<runtime id>)`, so clicking a notification (e.g. an approval prompt) sent the route-resume path a runtime id where it expects a stored id. `useRouteResume` then resumed it as a stored session -> REST `/api/sessions/<runtime id>` 404 "session not found", and the running session was navigated away, which the user experiences as the session being destroyed. Translate runtime -> stored before navigating via the existing `runtimeIdByStoredSessionId` map (new `storedSessionIdForNotification` helper), falling back to the id as-is when no mapping is known. The Approve/Reject notification button path is untouched: `approval.respond` is routed by the runtime id (`_sess()` -> `_sessions[session_id]`), so it must keep carrying the runtime id.
This commit is contained in:
parent
ce0ac9bb4d
commit
f9ffe0bc3f
2 changed files with 34 additions and 3 deletions
|
|
@ -20,6 +20,7 @@ import {
|
|||
MESSAGING_SESSION_SOURCE_IDS,
|
||||
normalizeSessionSource
|
||||
} from '../lib/session-source'
|
||||
import { storedSessionIdForNotification } from '../lib/session-ids'
|
||||
import { latestSessionTodos } from '../lib/todos'
|
||||
import { setCronFocusJobId, setCronJobs } from '../store/cron'
|
||||
import {
|
||||
|
|
@ -276,16 +277,20 @@ export function DesktopController() {
|
|||
}
|
||||
}, [])
|
||||
|
||||
// Notification click: the main process already focused the window; jump to its session.
|
||||
// Notification click: the main process already focused the window; jump to its
|
||||
// session. Notifications are tagged with the gateway *runtime* session id, but
|
||||
// the chat route is keyed by the *stored* id — navigating with the runtime id
|
||||
// resumes a non-existent stored session ("session not found") and strands the
|
||||
// user. Translate runtime -> stored before navigating.
|
||||
useEffect(() => {
|
||||
const unsubscribe = window.hermesDesktop?.onFocusSession?.(sessionId => {
|
||||
if (sessionId) {
|
||||
navigate(sessionRoute(sessionId))
|
||||
navigate(sessionRoute(storedSessionIdForNotification(sessionId, runtimeIdByStoredSessionIdRef.current)))
|
||||
}
|
||||
})
|
||||
|
||||
return () => unsubscribe?.()
|
||||
}, [navigate])
|
||||
}, [navigate, runtimeIdByStoredSessionIdRef])
|
||||
|
||||
// Notification action button (Approve/Reject) — resolve in place, no navigation.
|
||||
useEffect(() => {
|
||||
|
|
|
|||
26
apps/desktop/src/lib/session-ids.ts
Normal file
26
apps/desktop/src/lib/session-ids.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// The gateway tags every event — and therefore every native notification —
|
||||
// with the *runtime* session id (the key under which the session lives in the
|
||||
// gateway's in-memory `_sessions` map). The chat route, however, is keyed by
|
||||
// the *stored* session id (`stored_session_id`), which is a different value:
|
||||
// a brand-new chat gets a runtime id immediately but its stored id is assigned
|
||||
// when the first turn persists. Navigating to a runtime id therefore tries to
|
||||
// resume a stored session that does not exist ("session not found") and
|
||||
// strands the user, who experiences it as the running session being destroyed.
|
||||
//
|
||||
// `runtimeIdByStoredSessionId` maps stored -> runtime; this resolves the
|
||||
// reverse so notification-click navigation lands on the real route. The id is
|
||||
// returned unchanged when no mapping is known — it may already be a stored id
|
||||
// (e.g. a notification for a session this window never opened), in which case
|
||||
// the normal resume/REST lookup handles it.
|
||||
export function storedSessionIdForNotification(
|
||||
id: string,
|
||||
runtimeIdByStoredSessionId: ReadonlyMap<string, string>
|
||||
): string {
|
||||
for (const [storedId, runtimeId] of runtimeIdByStoredSessionId) {
|
||||
if (runtimeId === id) {
|
||||
return storedId
|
||||
}
|
||||
}
|
||||
|
||||
return id
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue