Skip to content

fix(desktop): preserve profile when opening session routes - #49619

Closed
d31tcjg wants to merge 1 commit into
NousResearch:mainfrom
d31tcjg:fix/desktop-profile-routed-session-resume
Closed

d31tcjg wants to merge 1 commit into
NousResearch:mainfrom
d31tcjg:fix/desktop-profile-routed-session-resume

Conversation

@d31tcjg

@d31tcjg d31tcjg commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Preserve each Desktop session row's owning profile when opening sessions from sidebar and messaging sections, cron run lists, command center, command palette, keyboard/session pickers, and pop-out windows.
  • Encode the optional profile hint in session routes, parse it during route resume, and carry it through cached and uncached session ownership resolution.
  • Route getSession, message fallback, and session.resume through the same profile hint while preserving legacy fallback behavior when the hint is absent or stale.
  • Carry the profile through the renderer window bridge, Electron IPC, secondary-window URL construction, and existing-window focus/reload paths.

Motivation

Desktop aggregates sessions from multiple profiles. A row owned by a non-active profile could previously navigate or open a secondary window using only its session ID. Resume then probed the active/default profile, leaving Desktop stuck loading or reporting session not found even though the session existed in its owning profile.

This update covers the complete current Desktop path rather than only the original sidebar/resume implementation. It also adapts the change to the split use-session-actions/index.ts and utils.ts layout now on main.

Compatibility

  • Profile hints remain optional for existing callers.
  • Blank profile values normalize to no hint.
  • Resume retains fallback discovery when an explicit hint is stale or unavailable.
  • No voice/audio changes are included in this PR.

Testing

  • npm run test:ui --workspace apps/desktop -- src/store/windows.test.ts src/app/chat/sidebar/sessions-section.test.tsx src/app/routes.test.ts src/app/session/hooks/use-route-resume.test.tsx src/app/session/hooks/use-session-actions.test.tsx
    • 49 tests passed
  • npm run test:desktop:platforms --workspace apps/desktop -- electron/session-windows.test.ts
    • 15 tests passed
  • npm run typecheck --workspace apps/desktop
  • Scoped ESLint over all 28 changed Desktop files; zero errors
  • git diff --check origin/main...HEAD
  • Added-line security/scope scan across 304 added lines; no findings and no voice/audio files

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/gateway Gateway runner, session dispatch, delivery labels Jun 20, 2026
@d31tcjg
d31tcjg force-pushed the fix/desktop-profile-routed-session-resume branch 2 times, most recently from 4df4e9d to 90f60ab Compare June 25, 2026 23:22
@alt-glitch alt-glitch added comp/desktop Electron desktop app (apps/desktop/*) and removed comp/gateway Gateway runner, session dispatch, delivery comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jun 26, 2026
@d31tcjg
d31tcjg force-pushed the fix/desktop-profile-routed-session-resume branch from 90f60ab to 50183f1 Compare July 14, 2026 13:41
@teknium1

Copy link
Copy Markdown
Collaborator

Thanks for tracing the profile across the Desktop session-entry surfaces. The premise is confirmed on current main: a fresh secondary renderer starts with an empty $sessions store (apps/desktop/src/store/session.ts:200), while resolveStoredSession() first probes the active backend and then only the current $profiles cache (apps/desktop/src/app/session/hooks/use-session-actions/utils.ts:208-250). Gateway boot opens the socket before refreshSessions() completes (apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts:450-476), so an unhinted cross-profile route can enter that lookup before ownership is available.

The proposed optional route hint directly addresses that cold path and preserves the existing fallback discovery behavior. The focused changes align with Electron's existing profile-aware API routing (apps/desktop/electron/main.ts:7903-7922).

This is an automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 14, 2026
@d31tcjg
d31tcjg force-pushed the fix/desktop-profile-routed-session-resume branch from 50183f1 to 65c201f Compare July 16, 2026 00:40
@teknium1 teknium1 added area/sessions Session lifecycle, resume, persistence, history area/profiles Multi-profile isolation, HERMES_HOME scoping labels Jul 19, 2026
@d31tcjg
d31tcjg force-pushed the fix/desktop-profile-routed-session-resume branch from 65c201f to cfd038e Compare July 28, 2026 13:07
@OutThisLife

Copy link
Copy Markdown
Contributor

Thanks for tracing this across every Desktop session-entry surface — the map you built of where ownership gets dropped was accurate, and it's what made the residual gap easy to find.

We ended up fixing it from the other side. #74033 makes GET /api/sessions/{id} and GET /api/sessions stamp the owning profile unconditionally, rather than only when the request carries ?profile=. That was the actual asymmetry: the default profile is the one reachable without the param, so its rows were the only ones circulating unowned. With the server holding up its end, resolveStoredSession gets the right answer on its own and the route hint has nothing left to carry for this motivation.

One piece of your PR wasn't covered by that, though: hermes.desktop.lastRoute was still a single global key while lastSessionId had already been scoped per profile, and cold-start restore prefers the route. Since a session route embeds a session id, that bypassed the id scoping entirely. Salvaged as #74277 with credit to you.

Closing as superseded — the remaining diff here is a 149-file rebase against a fix that's already landed.

pull Bot pushed a commit to manmuqingshan/hermes-agent that referenced this pull request Jul 29, 2026
`hermes.desktop.lastSessionId` is keyed by owning profile (143942d), but
`hermes.desktop.lastRoute` stayed global -- and cold-start restore prefers the
route over the id. A session route embeds a session id in its path, so
relaunching under profile B navigated straight into a session owned by profile
A, bypassing the id scoping entirely (NousResearch#67603 family).

Key the remembered route by the same owner the id already uses
(rememberedSessionProfile), read it back for the active profile on restore, and
keep the default profile on the original unsuffixed key so existing installs'
remembered route survives the upgrade.

Salvaged from the profile-hint work in NousResearch#49619, which threaded an explicit
profile through every route/IPC/window surface to reach the same end. The server
now stamps session ownership unconditionally (NousResearch#74033), so the resolver already
gets the right answer and only this persisted key was left crossing profiles.

Co-authored-by: d31tcjg <d31tcjg@users.noreply.github.com>
@d31tcjg

d31tcjg commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

Thank you for the clear explanation, and for salvaging the remembered-route fix with credit. I’m glad the investigation and session-entry mapping helped improve Hermes, even though the final solution landed through a different path. Happy to have contributed. 🙂

randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
`hermes.desktop.lastSessionId` is keyed by owning profile (9c3ff21), but
`hermes.desktop.lastRoute` stayed global -- and cold-start restore prefers the
route over the id. A session route embeds a session id in its path, so
relaunching under profile B navigated straight into a session owned by profile
A, bypassing the id scoping entirely (NousResearch#67603 family).

Key the remembered route by the same owner the id already uses
(rememberedSessionProfile), read it back for the active profile on restore, and
keep the default profile on the original unsuffixed key so existing installs'
remembered route survives the upgrade.

Salvaged from the profile-hint work in NousResearch#49619, which threaded an explicit
profile through every route/IPC/window surface to reach the same end. The server
now stamps session ownership unconditionally (NousResearch#74033), so the resolver already
gets the right answer and only this persisted key was left crossing profiles.

Co-authored-by: d31tcjg <d31tcjg@users.noreply.github.com>
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
`hermes.desktop.lastSessionId` is keyed by owning profile (143942d), but
`hermes.desktop.lastRoute` stayed global -- and cold-start restore prefers the
route over the id. A session route embeds a session id in its path, so
relaunching under profile B navigated straight into a session owned by profile
A, bypassing the id scoping entirely (NousResearch#67603 family).

Key the remembered route by the same owner the id already uses
(rememberedSessionProfile), read it back for the active profile on restore, and
keep the default profile on the original unsuffixed key so existing installs'
remembered route survives the upgrade.

Salvaged from the profile-hint work in NousResearch#49619, which threaded an explicit
profile through every route/IPC/window surface to reach the same end. The server
now stamps session ownership unconditionally (NousResearch#74033), so the resolver already
gets the right answer and only this persisted key was left crossing profiles.

Co-authored-by: d31tcjg <d31tcjg@users.noreply.github.com>
prmartinow pushed a commit to prmartinow/hermes-agent that referenced this pull request Aug 26, 2026
`hermes.desktop.lastSessionId` is keyed by owning profile (7790df2), but
`hermes.desktop.lastRoute` stayed global -- and cold-start restore prefers the
route over the id. A session route embeds a session id in its path, so
relaunching under profile B navigated straight into a session owned by profile
A, bypassing the id scoping entirely (NousResearch#67603 family).

Key the remembered route by the same owner the id already uses
(rememberedSessionProfile), read it back for the active profile on restore, and
keep the default profile on the original unsuffixed key so existing installs'
remembered route survives the upgrade.

Salvaged from the profile-hint work in NousResearch#49619, which threaded an explicit
profile through every route/IPC/window surface to reach the same end. The server
now stamps session ownership unconditionally (NousResearch#74033), so the resolver already
gets the right answer and only this persisted key was left crossing profiles.

Co-authored-by: d31tcjg <d31tcjg@users.noreply.github.com>
melon-xf added a commit to melon-xf/hermes-agent that referenced this pull request Sep 3, 2026
`hermes.desktop.lastSessionId` is keyed by owning profile (143942d), but
`hermes.desktop.lastRoute` stayed global -- and cold-start restore prefers the
route over the id. A session route embeds a session id in its path, so
relaunching under profile B navigated straight into a session owned by profile
A, bypassing the id scoping entirely (NousResearch#67603 family).

Key the remembered route by the same owner the id already uses
(rememberedSessionProfile), read it back for the active profile on restore, and
keep the default profile on the original unsuffixed key so existing installs'
remembered route survives the upgrade.

Salvaged from the profile-hint work in NousResearch#49619, which threaded an explicit
profile through every route/IPC/window surface to reach the same end. The server
now stamps session ownership unconditionally (NousResearch#74033), so the resolver already
gets the right answer and only this persisted key was left crossing profiles.

Co-authored-by: d31tcjg <d31tcjg@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping area/sessions Session lifecycle, resume, persistence, history comp/desktop Electron desktop app (apps/desktop/*) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants