From fbabf438a17cc16a768e567eea832e39a9e0a40b Mon Sep 17 00:00:00 2001 From: xxxigm Date: Mon, 15 Jun 2026 20:31:36 +0700 Subject: [PATCH] fix(desktop): sync $connection on profile switch so remote profiles attach images as bytes The renderer's $connection seeds from the PRIMARY (window) backend at boot and otherwise only refreshes on a sleep/wake reconnect. Activating a background profile (ensureGatewayProfile) pointed the live gateway + REST at that profile's backend but never updated $connection, so its `mode` stayed stuck on the primary. With a local primary and a remote pool profile active, every code path that branches on local-vs-remote misfired: image attachments went out via the path-based `image.attach` instead of `image.attach_bytes`, handing the remote gateway a client-only Windows path it can't resolve ("image not found: C:\..."), and the /api/fs/* file browser and /api/media fetches targeted the wrong machine. Resync $connection from the now-active profile's descriptor right after the gateway swap, so the remote-aware paths follow the live backend. Best-effort: a failed descriptor fetch leaves the prior connection intact for boot/reconnect to resync. Single-profile users are unaffected (the same-profile fast path never runs the swap). Fixes #46651 --- apps/desktop/src/store/profile.ts | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/apps/desktop/src/store/profile.ts b/apps/desktop/src/store/profile.ts index 67b708fb219..4c8ffc3540f 100644 --- a/apps/desktop/src/store/profile.ts +++ b/apps/desktop/src/store/profile.ts @@ -12,6 +12,7 @@ import { storedStringRecord } from '@/lib/storage' import { $gateway, ensureGatewayForProfile } from '@/store/gateway' +import { setConnection } from '@/store/session' import type { ProfileInfo } from '@/types/hermes' // Canonical key for a profile: trimmed, empty → "default". Used everywhere we @@ -178,6 +179,32 @@ export const $gatewaySwapTarget = atom(null) let gatewaySwitch: Promise | null = null +// Keep the renderer's $connection (mode / baseUrl / profile) in lockstep with +// the profile the live gateway is now on. $connection seeds from the PRIMARY +// (window) backend at boot and otherwise only refreshes on a sleep/wake +// reconnect — so activating a *background* profile left $connection describing +// the primary, with the wrong `mode` for everything that branches on +// local-vs-remote. Headline symptom: with a local primary and a remote pool +// profile active, image attachments went out via the path-based `image.attach` +// instead of `image.attach_bytes`, handing the remote gateway a client-only +// path it can't resolve ("image not found: C:\…"), while the /api/fs/* file +// browser and /api/media fetches targeted the wrong machine (#46651). +// Best-effort: a failed descriptor fetch leaves the prior connection intact for +// boot/reconnect to resync. +async function syncConnectionToActiveProfile(profile: string): Promise { + const getConnection = window.hermesDesktop?.getConnection + + if (!getConnection) { + return + } + + try { + setConnection(await getConnection(profile)) + } catch { + // Leave the prior connection in place; boot/reconnect resyncs it later. + } +} + // Make `profile`'s backend the active gateway, lazily opening its socket if it // isn't live yet. Unlike the old single-socket swap, background profiles keep // their sockets — so their sessions keep streaming concurrently. A null/empty @@ -218,6 +245,9 @@ export async function ensureGatewayProfile(profile: string | null | undefined): // the active gateway at it — without closing the profile you came from. await ensureGatewayForProfile(target) $activeGatewayProfile.set(target) + // The active backend just changed; resync $connection so remote-aware + // paths (image.attach_bytes vs image.attach, /api/fs/*, /api/media) follow. + await syncConnectionToActiveProfile(target) })() try {