fix(desktop): reuse primary WS for named profiles on attached shared remote (#96493)

Bot Mode group turns on a Windows Desktop attached to a remote gateway
dialed a registry secondary per member profile. That second WebSocket
accept/closed in ~30ms and never ran session.create. When the window
primary is already that one-host-many-profiles source (sharedRemote),
request/retain/open/ensure now reuse the primary socket and scope RPCs
with profile=. Isolated SSH/pooled backends are unchanged.
This commit is contained in:
Paula Rossi
2026-08-27 13:37:58 -04:00
committed by Teknium
parent 5cc3da6827
commit 3efb514b9e
3 changed files with 145 additions and 0 deletions

View File

@@ -564,3 +564,75 @@ describe('retainGatewayForAgent (#93602)', () => {
expect(secondaryGateways[0].close).toHaveBeenCalledOnce()
})
})
describe('attached shared-remote group turns (#96493)', () => {
function installAttachedSharedRemote() {
;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
getConnection: vi.fn(async (profile: null | string) => ({ port: 4242, profile, token: 't' })),
getConnectionFor: vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
connectionId,
port: 9119,
profile,
sharedRemote: true
})),
getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
ok: true as const,
wsUrl: `ws://${connectionId}/${profile}`
})),
touchBackend: vi.fn(async () => undefined)
}
}
it('reuses the primary socket for a named profile on the attached shared remote', async () => {
const primary = makePrimary()
setPrimaryGateway(primary as never, 'default')
setPrimaryGatewayConnection({ connectionId: 'homelab' })
installAttachedSharedRemote()
await ensureGatewayForProfile('default')
const release = await retainGatewayForAgent('homelab', 'voter')
await requestGatewayForAgent('homelab', 'voter', 'session.create', { title: 'Group: room' })
await requestGatewayForAgent('homelab', 'voter', 'prompt.submit', { session_id: 'rt-1', text: 'hi' })
expect(secondaryGateways).toHaveLength(0)
expect(primary.request).toHaveBeenCalledTimes(2)
expect(primary.request).toHaveBeenNthCalledWith(1, 'session.create', {
title: 'Group: room',
profile: 'voter'
})
expect(primary.request).toHaveBeenNthCalledWith(2, 'prompt.submit', {
session_id: 'rt-1',
text: 'hi',
profile: 'voter'
})
release()
expect(secondaryGateways).toHaveLength(0)
})
it('still dials a secondary when the attached source is not a shared remote', async () => {
const primary = makePrimary()
setPrimaryGateway(primary as never, 'default')
setPrimaryGatewayConnection({ connectionId: 'homelab' })
;(window as unknown as { hermesDesktop: unknown }).hermesDesktop = {
getConnection: vi.fn(async (profile: null | string) => ({ port: 4242, profile, token: 't' })),
getConnectionFor: vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
connectionId,
port: 5151,
profile,
sharedRemote: false
})),
getGatewayWsUrlFor: vi.fn(async ({ connectionId, profile }: { connectionId: string; profile: string }) => ({
ok: true as const,
wsUrl: `ws://${connectionId}/${profile}`
})),
touchBackend: vi.fn(async () => undefined)
}
await ensureGatewayForProfile('default')
await requestGatewayForAgent('homelab', 'voter', 'session.create', { title: 'g' })
expect(secondaryGateways).toHaveLength(1)
expect(primary.request).not.toHaveBeenCalled()
})
})

View File

@@ -296,6 +296,60 @@ function isPrimaryRegistryRoute(connectionId: null | string, profile: string): b
)
}
/** True when `connectionId` is the window's already-attached source AND that
* source is a one-host-many-profiles remote (`sharedRemote`). Named member
* profiles on that host must reuse the primary socket — a registry secondary
* dials a second WebSocket at the same Tailscale URL, which accept/closes in
* ~30ms (`messages=1`) and never runs `session.create` (#96493). Isolated
* SSH/pooled backends (`sharedRemote: false`) still get their own secondary. */
async function isAttachedSharedRemote(connectionId: null | string, profile: string): Promise<boolean> {
const id = String(connectionId ?? '').trim()
const key = normKey(profile)
if (!id || !g.primaryConnectionId || id !== g.primaryConnectionId) {
return false
}
if (isPrimaryRegistryRoute(id, key)) {
return false
}
const desktop = window.hermesDesktop
if (!desktop?.getConnectionFor) {
return false
}
try {
const conn = await withTimeout(
desktop.getConnectionFor({ connectionId: id, profile: key }),
RECONNECT_ATTEMPT_TIMEOUT_MS,
`Timed out resolving shared-remote route for "${key}"`
)
return Boolean(conn && typeof conn === 'object' && (conn as { sharedRemote?: boolean }).sharedRemote === true)
} catch {
return false
}
}
async function requestOnPrimaryGateway<T>(
method: string,
params: Record<string, unknown>,
timeoutMs?: number,
signal?: AbortSignal
): Promise<T> {
const gateway = g.primaryGateway
if (!gateway) {
throw new Error('Hermes gateway unavailable')
}
return timeoutMs === undefined && signal === undefined
? gateway.request<T>(method, params)
: gateway.request<T>(method, params, timeoutMs, signal)
}
export function isActivePrimary(): boolean {
return g.activeKey === g.primaryProfile
}
@@ -870,6 +924,10 @@ export async function requestGatewayForAgent<T>(
return requestGatewayForProfile<T>(key, method, params, timeoutMs, signal)
}
if (await isAttachedSharedRemote(connectionId, key)) {
return requestOnPrimaryGateway<T>(method, { ...params, profile: key }, timeoutMs, signal)
}
if (!window.hermesDesktop?.getConnectionFor) {
throw new Error('This Desktop build cannot dial registry connections. Update Hermes Desktop.')
}
@@ -1054,6 +1112,11 @@ export async function retainGatewayForAgent(connectionId: null | string, profile
return route.release
}
if (isPrimaryRegistryRoute(connectionId, key) || (await isAttachedSharedRemote(connectionId, key))) {
// Primary socket stays open for the window lifetime — no secondary to hold.
return () => undefined
}
if (!window.hermesDesktop?.getConnectionFor) {
// No registry dialing in this build — nothing to hold; the request path
// will throw its own actionable error.
@@ -1268,6 +1331,10 @@ export async function openGatewayForAgent(
return openGatewayForProfile(profile)
}
if (await isAttachedSharedRemote(connectionId, profile)) {
return
}
if (!window.hermesDesktop?.getConnectionFor) {
throw new Error('This Desktop build cannot dial registry connections. Update Hermes Desktop.')
}
@@ -1314,6 +1381,10 @@ export async function ensureGatewayForAgent(
return !signal?.aborted
}
if (await isAttachedSharedRemote(connectionId, profile)) {
return !signal?.aborted
}
if (!window.hermesDesktop?.getConnectionFor) {
throw new Error('This Desktop build cannot dial registry connections. Update Hermes Desktop.')
}

View File

@@ -0,0 +1,2 @@
smfworks
# SMF Works contributor