fix(desktop): do not treat deferred local enumeration as a failure

'connect-on-demand' means local roster enumeration was intentionally
skipped to avoid spawning a local backend on a remote-only workspace,
not that it failed. The plugin-profile-routes IPC handler passed
Boolean(error) straight through, so that deferral was treated as a
genuine failure and Bot Mode re-synthesized cached local profile rows
even though local was never dialed.

Fixes #94648
This commit is contained in:
chelsealong
2026-08-25 10:35:10 +00:00
committed by Teknium
parent 4ca1f532be
commit c475484f63
3 changed files with 29 additions and 1 deletions
+7 -1
View File
@@ -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) {
@@ -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' }]
@@ -51,6 +51,13 @@ interface BuildOpaqueProfileRoutesOptions {
resolveSsh: (config: ProfileRouteConfig) => Promise<EffectiveSshRoute>
}
/** 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[],