diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs index 84c8c424b36..b321189c521 100644 --- a/apps/desktop/electron/main.cjs +++ b/apps/desktop/electron/main.cjs @@ -5124,7 +5124,16 @@ function coerceDesktopConnectionConfig(input = {}, existing = readDesktopConnect const remoteLike = modeIsRemoteLike(mode) // The block being edited: a per-profile entry or the global remote block. - const existingBlock = key ? existing.profiles?.[key] || {} : existing.remote || {} + const rawExistingBlock = key ? existing.profiles?.[key] || {} : existing.remote || {} + // Leaving a CLOUD connection unselects it: a cloud block's url/org/token + // describe a discovered Hermes Cloud instance, NOT a user-owned remote gateway, + // so switching to local or remote must NOT inherit them (otherwise the stale + // cloud URL lingers and re-selecting Cloud looks "already connected"). When the + // saved block was cloud and the new mode is not cloud, start from an empty + // block. (remote↔local toggles still preserve a real remote URL as before.) + const existingMode = key ? existing.profiles?.[key]?.mode : existing.mode + const leavingCloud = existingMode === 'cloud' && mode !== 'cloud' + const existingBlock = leavingCloud ? {} : rawExistingBlock const remoteUrl = String(input.remoteUrl ?? existingBlock.url ?? '').trim() // authMode: explicit input wins; otherwise inherit the saved value, default 'token'. const authMode = resolveAuthMode(input.remoteAuthMode, existingBlock.authMode) diff --git a/apps/desktop/src/app/settings/gateway-settings.tsx b/apps/desktop/src/app/settings/gateway-settings.tsx index 754987a7732..0ff4244a35b 100644 --- a/apps/desktop/src/app/settings/gateway-settings.tsx +++ b/apps/desktop/src/app/settings/gateway-settings.tsx @@ -128,7 +128,19 @@ export function GatewaySettings() { // list here and show a picker. `cloudOrg` is the chosen org slug/id (null = // not yet chosen / single-org user). const [cloudOrgs, setCloudOrgs] = useState([]) - const [cloudOrg, setCloudOrg] = useState(null) + const [cloudOrg, setCloudOrgState] = useState(null) + // Mirror the selected org into a ref so connect reads the CURRENT value, not a + // value captured in a stale render closure. discoverCloud() resolves the org + // asynchronously (from the NAS response) and a user can click Connect in the + // same render tick; without the ref, connectCloudAgent could persist a null + // org even though discovery just resolved one. Always set both together. + const cloudOrgRef = useRef(null) + + const setCloudOrg = (value: null | string) => { + cloudOrgRef.current = value + setCloudOrgState(value) + } + // Hermes Cloud is beta-gated: the selector ModeCard only appears when the main // process reports the BETA env flag is enabled. Default hidden until the async // check resolves, so it never flashes in for non-beta users. @@ -512,6 +524,16 @@ export function GatewaySettings() { void discoverCloud(ref) } + // "Change org": clear the selected org and re-discover with no org arg. A + // multi-org user gets NAS's 409 → the picker; a single-org user auto-resolves + // back to their one org. Also clear the agent list so the current org's + // agents don't linger under the picker while discovery re-runs. + const changeCloudOrg = () => { + setCloudOrg(null) + setCloudAgents([]) + void discoverCloud() + } + // On entering cloud mode (or scope change), read the portal session status and // auto-discover when already signed in, so the picker is populated on open. useEffect(() => { @@ -642,12 +664,14 @@ export function GatewaySettings() { // Persist a cloud-mode connection (remote-shaped, oauth) and reconnect. // Include the selected org so Settings reopens into the same org + instance. + // Read the REF (not the cloudOrg state) so a just-resolved org from + // discovery in this same render tick is captured, not a stale null. const next = await desktop.applyConnectionConfig({ mode: 'cloud', profile: scope ?? undefined, remoteAuthMode: 'oauth', remoteUrl: agent.dashboardUrl, - cloudOrg: cloudOrg ?? undefined + cloudOrg: cloudOrgRef.current ?? undefined }) setState(next) @@ -837,9 +861,15 @@ export function GatewaySettings() { {g.cloudAgentsTitle}
- {cloudOrgs.length > 1 ? ( - // Let a multi-org user switch back to the org picker. - ) : null}