fix(desktop): thread eager profile metadata through registry enumeration

Never-interacted remote bots painted as bare handles because roster rows
carried only profile names: display_name/title/ui_meta/has_avatar were
fetched lazily on first interaction (#91365). Thread credential-free
profile metadata from the enumeration-time /api/profiles body through
enumerateRegistryAgentSources (main.ts) and buildAgentRoster
(connection-registry.ts), keeping it attached to the connection-qualified
row across the same-install collapse. The plugin.js botRosterMeta half of
the original PR is dropped — superseded by landed #92731.

Fixes #91365
Salvaged (partial) from #92708.
This commit is contained in:
Tom
2026-08-25 14:52:35 -07:00
committed by Teknium
parent 2952119bce
commit 2ed39365d6
4 changed files with 110 additions and 12 deletions
@@ -544,6 +544,24 @@ test('roster: unique profiles keep bare handles; duplicates get @name-device', (
assert.equal(roster.length, 4)
})
test('roster: source profile metadata follows the connection-qualified row', () => {
const local = { id: 'local', kind: 'local' as const, label: 'This device' }
const vps = { id: 'vps', kind: 'remote' as const, label: 'VPS', url: 'http://vps:8642' }
const vpsMeta = {
display_name: 'Emma',
ui_meta: { 'hermes-bots': { title: 'Emma', shape: 'blobatar::sun', color: '#8b5cf6' } },
has_avatar: true
}
const roster = buildAgentRoster([
{ connection: local, profiles: ['default'] },
{ connection: vps, profiles: ['default'], profileMetadata: { default: vpsMeta } }
])
assert.deepEqual(roster.find(agent => agent.connectionId === 'vps')?.profileMetadata, vpsMeta)
assert.equal(roster.find(agent => agent.connectionId === 'local')?.profileMetadata, undefined)
})
test('rememberSshEnumeration: live list wins, cache then seed default', () => {
assert.deepEqual(rememberSshEnumeration({ profiles: ['bob', 'kai'] }, ['stale'], 'ssh'), {
profiles: ['bob', 'kai']
+37 -9
View File
@@ -413,6 +413,9 @@ export interface ConnectionAgents {
/** Profile names enumerated from the connection, or null when unreachable /
* connect-on-demand (ssh not yet dialed). */
profiles: null | string[]
/** Credential-free profile metadata from the same connection. Kept separate
* from `profiles` so old enumerators can continue returning names only. */
profileMetadata?: Record<string, RosterProfileMetadata>
/** Present when profiles is null: why enumeration was skipped. */
error?: string
/** Stable backend identity from the connection's /api/status (`install_id`).
@@ -433,6 +436,15 @@ export interface RosterAgent {
/** Bare profile name, or `<profile>-<label-slug>` when the profile name
* exists on more than one registered source (the @name-device rule). */
handle: string
/** Rich metadata for this exact connection + profile, when enumerated. */
profileMetadata?: RosterProfileMetadata
}
export interface RosterProfileMetadata {
display_name?: string
title?: string
ui_meta?: Record<string, unknown>
has_avatar?: boolean
}
/**
@@ -529,18 +541,30 @@ export function buildAgentRoster(
// counting names for @name-device disambiguation.
const identities = new Map<
string,
{ connection: RegistryConnection; installId?: string; order: number; profile: string }
{
connection: RegistryConnection
installId?: string
order: number
profile: string
profileMetadata?: RosterProfileMetadata
}
>()
let order = 0
for (const { connection, installId, profiles } of enumerations) {
for (const { connection, installId, profiles, profileMetadata } of enumerations) {
for (const profile of profiles || []) {
const name = String(profile || '').trim() || 'default'
const key = `${connection.id}\0${name}`
if (!identities.has(key)) {
identities.set(key, { connection, installId, order, profile: name })
identities.set(key, {
connection,
installId,
order,
profile: name,
...(profileMetadata?.[name] ? { profileMetadata: profileMetadata[name] } : {})
})
}
}
@@ -551,16 +575,19 @@ export function buildAgentRoster(
// are the SAME physical install registered under two addresses, so their
// (install, profile) rows are one bot, not two. Connections without an id
// (older backends, undialed ssh) keep a per-connection key — no collapse.
const backends = new Map<string, { connection: RegistryConnection; order: number; profile: string }[]>()
const backends = new Map<
string,
{ connection: RegistryConnection; order: number; profile: string; profileMetadata?: RosterProfileMetadata }[]
>()
for (const { connection, installId, order: rank, profile } of identities.values()) {
for (const { connection, installId, order: rank, profile, profileMetadata } of identities.values()) {
const key = installId ? `id:${installId}\0${profile}` : `conn:${connection.id}\0${profile}`
const group = backends.get(key)
if (group) {
group.push({ connection, order: rank, profile })
group.push({ connection, order: rank, profile, profileMetadata })
} else {
backends.set(key, [{ connection, order: rank, profile }])
backends.set(key, [{ connection, order: rank, profile, profileMetadata }])
}
}
@@ -577,14 +604,15 @@ export function buildAgentRoster(
const roster: RosterAgent[] = []
for (const { connection, profile } of rows) {
for (const { connection, profile, profileMetadata } of rows) {
roster.push({
connectionId: connection.id,
connectionKind: connection.kind,
connectionLabel: connection.label,
profile,
targetProfile: connection.remoteProfile || profile,
handle: agentHandle(profile, connection.label, (counts.get(profile) || 0) > 1)
handle: agentHandle(profile, connection.label, (counts.get(profile) || 0) > 1),
...(profileMetadata ? { profileMetadata } : {})
})
}
+54 -3
View File
@@ -145,6 +145,7 @@ import {
updateEligibility,
upsertConnection
} from './connection-registry'
import type { RosterProfileMetadata } from './connection-registry'
import { describeCrashReason, installCrashForensics } from './crash-forensics'
import { adoptServedDashboardToken } from './dashboard-token'
import { loadOrCreateInstallationId, sshOwnershipId } from './desktop-installation'
@@ -13706,7 +13707,13 @@ async function enumerateRegistryAgentSources(registry = readDesktopConnectionsRe
return Promise.all(
registry.connections.map(async connection => {
let raw: { connection: typeof connection; error?: string; installId?: string; profiles: null | string[] }
let raw: {
connection: typeof connection
error?: string
installId?: string
profiles: null | string[]
profileMetadata?: Record<string, RosterProfileMetadata>
}
try {
// SSH roster listing must never spawn a dashboard. A stale
@@ -13751,13 +13758,52 @@ async function enumerateRegistryAgentSources(registry = readDesktopConnectionsRe
? body.profiles.map(p => String(p?.name || '').trim()).filter(Boolean)
: []
const profileMetadata = Array.isArray(body?.profiles)
? Object.fromEntries(
body.profiles
.map(profile => {
const name = String(profile?.name || '').trim()
if (!name) {
return null
}
const metadata: RosterProfileMetadata = {}
if (typeof profile?.display_name === 'string' && profile.display_name.trim()) {
metadata.display_name = profile.display_name.trim()
}
if (typeof profile?.title === 'string' && profile.title.trim()) {
metadata.title = profile.title.trim()
}
if (profile?.ui_meta && typeof profile.ui_meta === 'object') {
metadata.ui_meta = profile.ui_meta
}
if (typeof profile?.has_avatar === 'boolean') {
metadata.has_avatar = profile.has_avatar
}
return [name, metadata] as const
})
.filter((entry): entry is readonly [string, RosterProfileMetadata] => Boolean(entry))
)
: undefined
// The root HERMES_HOME is an agent too; enumerations that omit it
// (older backends list only named profiles) still get a default row.
if (!profiles.includes('default')) {
profiles.unshift('default')
}
raw = { connection, profiles, ...(installId ? { installId } : {}) }
raw = {
connection,
profiles,
...(installId ? { installId } : {}),
...(profileMetadata ? { profileMetadata } : {})
}
}
} catch (error: any) {
raw = { connection, profiles: null, error: String(error?.message || error) }
@@ -13769,7 +13815,12 @@ async function enumerateRegistryAgentSources(registry = readDesktopConnectionsRe
const remembered = rememberSshEnumeration(raw, sshRosterCache.get(connection.id), connection.kind)
return { connection, ...remembered, ...(raw.installId ? { installId: raw.installId } : {}) }
return {
connection,
...remembered,
...(raw.installId ? { installId: raw.installId } : {}),
...(raw.profileMetadata ? { profileMetadata: raw.profileMetadata } : {})
}
})
)
}
+1
View File
@@ -0,0 +1 @@
TomSpoct