diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index d8df31fa43..5f1a03aeca 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -244,6 +244,7 @@ import { createParentStartMarkerResolver, parentWatchdogEnv } from './parent-pro import { registerPetOverlayIpc } from './pet-overlay-ipc' import { buildRegistryProfileRoutes, + isLocalEnumerationFailure, localRouteFallbackProfiles, registryGatewayWsUrl, undialedSshRouteSeeds @@ -13367,7 +13368,12 @@ ipcMain.handle('hermes:plugin-profile-routes', async (_event, rawProfileNames) = : undefined const localFallbackProfiles = localSource - ? localRouteFallbackProfiles(agents, localSource.id, fallbackProfileNames, Boolean(localEnumeration?.error)) + ? localRouteFallbackProfiles( + agents, + localSource.id, + fallbackProfileNames, + isLocalEnumerationFailure(localEnumeration?.error) + ) : [] if (localSource && localFallbackProfiles.length > 0) { diff --git a/apps/desktop/electron/plugin-profile-routes.test.ts b/apps/desktop/electron/plugin-profile-routes.test.ts index 1b2ba0a1ec..bcd7fbbca8 100644 --- a/apps/desktop/electron/plugin-profile-routes.test.ts +++ b/apps/desktop/electron/plugin-profile-routes.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest' import { buildOpaqueProfileRoutes, buildRegistryProfileRoutes, + isLocalEnumerationFailure, localRouteFallbackProfiles, type ProfileRouteConfig, registryGatewayWsUrl, @@ -253,6 +254,20 @@ describe('buildRegistryProfileRoutes', () => { }) }) +describe('isLocalEnumerationFailure', () => { + it('does not treat an intentionally deferred local enumeration as a failure', () => { + expect(isLocalEnumerationFailure('connect-on-demand')).toBe(false) + }) + + it('treats any other enumeration error as a failure', () => { + expect(isLocalEnumerationFailure('ECONNREFUSED')).toBe(true) + }) + + it('treats a missing error as no failure', () => { + expect(isLocalEnumerationFailure(undefined)).toBe(false) + }) +}) + describe('localRouteFallbackProfiles', () => { it('restores failed local profiles when another source returned agents', () => { const agents = [{ connectionId: 'cloud-prod', profile: 'default' }] diff --git a/apps/desktop/electron/plugin-profile-routes.ts b/apps/desktop/electron/plugin-profile-routes.ts index 9f002fc5c2..576719aae6 100644 --- a/apps/desktop/electron/plugin-profile-routes.ts +++ b/apps/desktop/electron/plugin-profile-routes.ts @@ -51,6 +51,13 @@ interface BuildOpaqueProfileRoutesOptions { resolveSsh: (config: ProfileRouteConfig) => Promise } +/** A 'connect-on-demand' local enumeration was intentionally deferred, not + * failed — it must not be treated as a failure or Bot Mode will synthesize + * cached local rows on remote-only workspaces where local was never dialed. */ +export function isLocalEnumerationFailure(error?: string): boolean { + return Boolean(error) && error !== 'connect-on-demand' +} + /** Return cached local profile names only when the local roster read failed. */ export function localRouteFallbackProfiles( agents: RegistryProfileRouteAgent[],