fix(desktop): translate sidebar recents_profile through SSH aliases

Managed SSH maps a Desktop profile label onto a different remote name.
The sidebar filter lives in recents_profile, so rewriting only ?profile=
left those reads on the remote default and the Sessions list came back empty.

Co-authored-by: noah <loahnisk@gmail.com>
This commit is contained in:
Brooklyn Nicholson
2026-08-25 11:38:44 -05:00
committed by brooklyn!
parent cbd8de8ad6
commit 90ee4460cb
2 changed files with 50 additions and 8 deletions

View File

@@ -504,6 +504,14 @@ test('pathForRegistryBackendRequest uses the resolved registry backend scope', (
}),
'/api/fs/download?path=%2Fsrv%2Freport.pdf'
)
assert.equal(
pathForRegistryBackendRequest(
'/api/profiles/sessions/sidebar?recents_profile=research&recents_exclude=cron%2Cdesktop',
'research',
{ remoteProfile: 'remote-research' }
),
'/api/profiles/sessions/sidebar?recents_profile=remote-research&recents_exclude=cron%2Cdesktop'
)
})
// --- pathWithGlobalRemoteProfile ---
@@ -604,8 +612,23 @@ test('translateSelfProfileQuery rewrites the self-profile filter into the backen
)
})
test('translateSelfProfileQuery rewrites sidebar recents_profile aliases for managed SSH', () => {
assert.equal(
translateSelfProfileQuery(
'/api/profiles/sessions/sidebar?recents_profile=research&recents_limit=20&cron_limit=50&messaging_limit=100',
'research',
'remote-research'
),
'/api/profiles/sessions/sidebar?recents_profile=remote-research&recents_limit=20&cron_limit=50&messaging_limit=100'
)
})
test('translateSelfProfileQuery leaves cross-profile and unfiltered paths untouched', () => {
assert.equal(translateSelfProfileQuery('/api/cron/jobs?profile=all', 'mara', 'default'), '/api/cron/jobs?profile=all')
assert.equal(
translateSelfProfileQuery('/api/profiles/sessions/sidebar?recents_profile=all', 'mara', 'default'),
'/api/profiles/sessions/sidebar?recents_profile=all'
)
assert.equal(
translateSelfProfileQuery('/api/cron/jobs?profile=worker', 'mara', 'default'),
'/api/cron/jobs?profile=worker'

View File

@@ -735,14 +735,23 @@ function pathWithGlobalRemoteProfile(path, profile, opts: ProfileRouteOptions =
return pathWithProfileScope(path, profile)
}
/** Extra profile-valued query keys, beyond `profile`, that name the same
* self-scope on a given path. The sidebar batches recents/cron/messaging
* behind `recents_profile` instead of `profile`, so an SSH alias rewrite
* that only looks at `?profile=` leaves those reads on the remote default. */
const SELF_PROFILE_QUERY_KEYS_BY_PATH: Record<string, string[]> = {
'/api/profiles/sessions/sidebar': ['recents_profile']
}
/**
* Translate an explicit self-profile query from a Desktop routing alias to the
* backend's own profile namespace (a managed SSH `remoteProfile` can map local
* `mara` to remote `default`). Only a `?profile=` equal to the alias itself is
* rewritten; cross-profile selectors (`all`, another concrete profile) and
* unfiltered paths pass through untouched. Used by the v1 profile route above
* and by the registry SSH branch of the `hermes:api` handler — both routes
* reach a backend whose namespace is the remote profile, not the alias.
* `mara` to remote `default`). Only endpoint-declared profile-valued params
* equal to the alias itself are rewritten; cross-profile selectors (`all`,
* another concrete profile) and unfiltered paths pass through untouched. Used
* by the v1 profile route above and by the registry SSH branch of the
* `hermes:api` handler — both routes reach a backend whose namespace is the
* remote profile, not the alias.
*/
function translateSelfProfileQuery(path, profile, backendProfile) {
const scopedProfile = connectionScopeKey(profile)
@@ -766,11 +775,21 @@ function translateSelfProfileQuery(path, profile, backendProfile) {
return path
}
if (connectionScopeKey(parsed.searchParams.get('profile')) !== scopedProfile) {
return path
const profileQueryKeys = ['profile', ...(SELF_PROFILE_QUERY_KEYS_BY_PATH[parsed.pathname] || [])]
let changed = false
for (const key of profileQueryKeys) {
if (connectionScopeKey(parsed.searchParams.get(key)) !== scopedProfile) {
continue
}
parsed.searchParams.set(key, backend)
changed = true
}
parsed.searchParams.set('profile', backend)
if (!changed) {
return path
}
return `${parsed.pathname}${parsed.search}${parsed.hash}`
}