fix(desktop): resolve v2 registry SSH terminals from the bootstrap pool key
The terminal pane's registry-scoped lookup keys sshConnections with backendScopeKey(connectionId, profile), but the pool's single writer publishes every tunnel under its per-profile bootstrap key with the registry connection stamped on the entry. The lookup can never hit, so a v2 registry SSH terminal pane stays 'pending' forever (#97345). Fall back to the entry stamped with the window's connection id - the same identity match managedSshScopeRole applies to pool entries - and return the writer's actual scope so downstream getSshConnectionState and teardown keep working.
This commit is contained in:
@@ -411,7 +411,11 @@ import { isHermesOwnedVenvDaemon } from './venv-holder-select'
|
||||
import { fetchMarketplaceThemes, searchMarketplaceThemes } from './vscode-marketplace'
|
||||
import { createWakeIndicatorWindowController } from './wake-indicator-window'
|
||||
import { enumerateWindowsFrontToBack, enumerationFailed, readWindowBelow } from './window-below'
|
||||
import { registrySshScopeForWindowRoute, WindowConnectionRouteRegistry } from './window-connection-route'
|
||||
import {
|
||||
registrySshPoolScopeByConnectionId,
|
||||
registrySshScopeForWindowRoute,
|
||||
WindowConnectionRouteRegistry
|
||||
} from './window-connection-route'
|
||||
import { createWindowOpenHandler } from './window-open-policy'
|
||||
import { installWindowRendererLifecycle } from './window-renderer-lifecycle'
|
||||
import { createWindowRevealController } from './window-reveal'
|
||||
@@ -10315,7 +10319,18 @@ function activeSshTerminalTarget(webContentsId?: number) {
|
||||
|
||||
const state = sshConnections.get(scope)
|
||||
|
||||
return state && state.ssh ? { ssh: state.ssh, scope } : 'pending'
|
||||
if (state && state.ssh) {
|
||||
return { ssh: state.ssh, scope }
|
||||
}
|
||||
|
||||
// The pool's single writer publishes under the per-profile bootstrap key
|
||||
// while stamping the entry with its registry connection id (#97345), so a
|
||||
// composite-key miss must still resolve the live tunnel by that identity
|
||||
// instead of reporting 'pending' forever.
|
||||
const pooledScope = registrySshPoolScopeByConnectionId(sshConnections, windowRoute.connectionId)
|
||||
const pooledState = pooledScope === null ? null : sshConnections.get(pooledScope)
|
||||
|
||||
return pooledState && pooledState.ssh ? { ssh: pooledState.ssh, scope: pooledScope } : 'pending'
|
||||
}
|
||||
|
||||
const profile = windowRoute?.profile ?? primaryProfileKey()
|
||||
|
||||
@@ -4,6 +4,7 @@ import { test } from 'vitest'
|
||||
|
||||
import {
|
||||
normalizeWindowConnectionRoute,
|
||||
registrySshPoolScopeByConnectionId,
|
||||
registrySshScopeForWindowRoute,
|
||||
WindowConnectionRouteRegistry
|
||||
} from './window-connection-route'
|
||||
@@ -120,3 +121,20 @@ test('uses the canonical default profile scope when a registry SSH route has no
|
||||
'conn:source-b::default'
|
||||
)
|
||||
})
|
||||
|
||||
test('recovers the bootstrap pool key a registry SSH tunnel was published under', () => {
|
||||
const pool = new Map<string, any>([['', { registryConnectionId: 'source-b', ssh: { alive: true } }]])
|
||||
|
||||
assert.equal(registrySshPoolScopeByConnectionId(pool, 'source-b'), '')
|
||||
})
|
||||
|
||||
test('does not match another connection, an unlabelled entry, or a torn-down tunnel', () => {
|
||||
const pool = new Map<string, any>([
|
||||
['research', { registryConnectionId: 'source-a', ssh: { alive: true } }],
|
||||
['', { registryConnectionId: '', ssh: { alive: true } }],
|
||||
['worker', { registryConnectionId: 'source-b' }]
|
||||
])
|
||||
|
||||
assert.equal(registrySshPoolScopeByConnectionId(pool, 'source-b'), null)
|
||||
assert.equal(registrySshPoolScopeByConnectionId(pool, 'source-c'), null)
|
||||
})
|
||||
|
||||
@@ -40,6 +40,30 @@ export function registrySshScopeForWindowRoute(
|
||||
return backendScopeKey(route.connectionId, route.profile)
|
||||
}
|
||||
|
||||
export interface RegistrySshPoolEntry {
|
||||
registryConnectionId?: null | string
|
||||
ssh?: unknown
|
||||
}
|
||||
|
||||
// The sshConnections pool has a single writer that publishes every tunnel under
|
||||
// its per-profile bootstrap key while stamping the entry with the registry
|
||||
// connection that owns it. A registry-scoped lookup under the composite
|
||||
// backendScopeKey can therefore miss a live tunnel; this recovers the writer's
|
||||
// actual key from the stamped identity, the same match managedSshScopeRole
|
||||
// applies to pool entries.
|
||||
export function registrySshPoolScopeByConnectionId(
|
||||
entries: Iterable<readonly [string, RegistrySshPoolEntry | undefined]>,
|
||||
connectionId: string
|
||||
): null | string {
|
||||
for (const [scope, entry] of entries) {
|
||||
if (entry?.ssh && entry.registryConnectionId === connectionId) {
|
||||
return scope
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export class WindowConnectionRouteRegistry {
|
||||
private readonly routes = new Map<number, WindowConnectionRoute>()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user