Fixes#85834 (Electron REST intercept fall-through). The
/api/sessions/{id}[/messages] intercept in electron/main.ts required an
explicit ?profile= (or request.profile) to route a read to its remote owner;
callers without a hint fell straight through to the LOCAL backend and 404'd
on its state.db even though the session lives on a configured remote — while
the list endpoints happily showed the row (remoteSessionList tags s.profile).
When no explicit profile resolves, consult the same remote session lists the
list endpoints use to find the owning profile (matching id or lineage root
id), memoized for 30s so a transcript+messages burst costs one sweep. Only
when the id is genuinely unknown remotely does the request fall through to
local, exactly as before. Pure lookup lives in profile-session-routing.ts
with unit tests (owner hit, lineage-root match, null on miss/dead
remotes/no remotes).
Maintainer commit (cluster salvage).
The global SESSIONS sidebar only aggregated local profiles and v1 per-profile
remote overrides. Sessions living on v2 registry connections (remote/cloud/ssh
gateways) never appeared — the remote API returned the rows, but the renderer's
Sessions component received an empty array (#88880).
- electron/profile-session-routing.ts: fetchRegistrySessionRows reads each
CONNECTED registry gateway's session list (ssh backends natively, shared
remote/cloud hosts via one cross-profile aggregate with a legacy flat-list
fallback), tagging rows with connection_id + owning profile.
spliceRegistrySessionRows dedupes them into the unified list and extends
per-profile totals. Reads never pass include_hidden, so Bot Mode's hidden
canonical chats stay OUT of the global list, same as local sessions.
- electron/main.ts: mergeRemoteProfileSessions splices registry rows; the
/api/profiles/sessions[+/sidebar] intercepts also fire when registry
gateways are pooled (previously only v1 remote overrides). Only
already-pooled backends are read — a sidebar refresh never dials or spawns
a backend (the roster-respawn trap), and a dead gateway contributes nothing.
- types/hermes.ts + use-session-actions: SessionInfo carries connection_id;
resuming a registry-owned row activates its connection-scoped gateway
(ensureGatewayAgent) instead of a same-named local profile.
- store/gateway.ts: ensureActiveGatewayOpen rides out an in-flight secondary
activation (bounded 8s) instead of failing instantly — the Sessions "+"
during remote wake no longer errors "Hermes gateway is not connected".
Tests: 5 new registry-source/splice unit tests (tagging, shared-host
aggregate + legacy fallback, dead-gateway isolation, hidden-flag contract,
dedupe/totals) and a sabotage-verified activation-wait regression test.
Complete the sidebar profile-scope contract across remote Electron routing, older-backend fallbacks, standalone messaging refreshes, and pagination. Reject stale profile responses and keep the explicit all-profiles view unified.
Co-authored-by: 墨綠BG <s5460703@gmail.com>
Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>