Files
hermes-agent/apps/desktop/electron/hud-url.ts
T
Teknium 124aff0aa9 fix(desktop): open HUD mode on the focused conversation's profile (#82325)
* fix(desktop): open HUD mode on the focused conversation's profile

The HUD is a full app renderer that adopted the PRIMARY backend's
profile at boot, so toggling HUD mode from a conversation on any other
profile resolved the session id against the wrong backend — the lookup
missed and the HUD fell back to the default profile's last session
(#82285).

- openHud() resolves the target's owning profile (session's stamped
  owner, else the active gateway profile) and passes it through
  hermes:hud:open.
- hudUrl() carries the profile in the query string next to win=hud;
  the HUD renderer's gateway boot honors it as an override for both
  getConnection() and profile adoption, so the window dials and adopts
  the right backend from first paint.
- Retargeting a live HUD onto a session from a DIFFERENT profile
  respawns the window against that profile's backend (a renderer adopts
  its backend exactly once at boot; an in-place goto would repeat the
  wrong-backend lookup).

No profile in the URL means no override — ordinary windows and
single-profile users boot exactly as before.

* refactor(desktop): extract the HUD renderer URL so its contract is tested

hudUrl() built the query string inline in main.ts, where the part that
actually breaks — `?win=hud&profile=` must sit BEFORE the '#' or
HashRouter eats it as the route — had no coverage. Move it next to
buildSessionWindowUrl's split (pure piece out of the monolith, unit
tested) and pin the contract: flag order, profile encoding, trailing
slash on the dev server, empty profile omitted, packaged file URL.

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

* refactor(desktop): resolve the HUD's target profile through the existing ladder

openHud() had its own copy of "stamped owner, else active gateway, else
default" — the same ladder rememberedSessionProfile() already owns for
the remembered-navigation key, down to sessionMatchesStoredId and the
default fallback. One resolver per policy, so the two can't drift.

---------

Co-authored-by: Brooklyn Nicholson <brooklyn.bb.nicholson@gmail.com>
Co-authored-by: rainbowgore <rainbowgore@users.noreply.github.com>
2026-08-09 09:10:59 +00:00

40 lines
1.5 KiB
TypeScript

// HUD mode's renderer URL. The pure, Electron-free piece lives here so it can
// be unit-tested (same split as session-windows.ts, and for the same reason:
// the contract below is invisible until it breaks at runtime).
import { pathToFileURL } from 'node:url'
/**
* Build the renderer URL for the HUD window.
*
* Same query-before-hash contract as `buildSessionWindowUrl`: `?win=hud` and
* the optional `profile=` MUST sit in the search string before the '#', or
* HashRouter swallows them as part of the route.
*
* The profile is what the HUD renderer adopts at boot. Session ids are scoped
* per profile, so without it a HUD opened on a non-primary conversation
* resolves the id against the primary backend, misses, and falls back to that
* backend's last session (#82285). Absent/blank means no override — ordinary
* single-profile boots are unchanged.
*/
export function buildHudWindowUrl(
sessionId: null | string | undefined,
{
devServer,
profile,
rendererIndexPath
}: { devServer?: null | string; profile?: null | string; rendererIndexPath?: string } = {}
): string {
const profileKey = typeof profile === 'string' ? profile.trim() : ''
const query = `?win=hud${profileKey ? `&profile=${encodeURIComponent(profileKey)}` : ''}`
const route = sessionId ? `#/${encodeURIComponent(sessionId)}` : '#/'
if (devServer) {
const base = devServer.endsWith('/') ? devServer.slice(0, -1) : devServer
return `${base}/${query}${route}`
}
return `${pathToFileURL(rendererIndexPath!).toString()}${query}${route}`
}