Files

2024 lines
70 KiB
TypeScript

/**
* Tests for electron/connection-registry.ts — the v2 multi-connection
* registry: label rules (required, unique, @handle disambiguation), input
* validation, registry normalization from disk, the v1→v2 migration, and the
* pure upsert/remove/set-primary operations.
*/
import assert from 'node:assert/strict'
import { test } from 'vitest'
import type { ConnectionRegistry } from './connection-registry'
import {
agentHandle,
backendScopeKey,
backendScopePrefix,
buildAgentRoster,
connectionDialFieldsChanged,
connectionIdForLabel,
labelKey,
labelSlug,
LOCAL_CONNECTION_ID,
mergeConnectionInput,
migrateV1ToRegistry,
normalizeConnectionInput,
normalizeRegistry,
parseRemoteProfileListing,
reconcileAppliedGlobalConnection,
reconcileRegistryDrift,
REGISTRY_VERSION,
registrySourceOwnsPrimaryBackend,
rememberSshEnumeration,
removeConnection,
resolvedConnectionId,
resolveRegistryLocalRoute,
reuseMatchingPrimarySshBackend,
setConnectionLaunchMode,
setLastUsedConnection,
setPrimaryConnection,
shouldDeferLocalEnumeration,
shouldRetrySshInventory,
uniqueLabel,
updateEligibility,
upsertConnection
} from './connection-registry'
function emptyRegistry(): ConnectionRegistry {
return normalizeRegistry(null)
}
// --- labels, slugs, handles ---
test('labelKey is case-insensitive and trimmed', () => {
assert.equal(labelKey(' Homelab '), 'homelab')
assert.equal(labelKey('HOMELAB'), labelKey('homelab'))
})
test('labelSlug kebab-cases and never returns empty for non-empty input', () => {
assert.equal(labelSlug('Work Laptop'), 'work-laptop')
assert.equal(labelSlug('Spark Box #2'), 'spark-box-2')
assert.equal(labelSlug('!!!'), 'connection')
})
test('registry SSH fingerprint failures name the connection and ssh -G step', async () => {
const registry = migrateV1ToRegistry({
mode: 'ssh',
remote: { mode: 'ssh', host: 'build-host', user: 'alice' },
profiles: {}
})
const source = registry.connections.find(connection => connection.id === registry.primary)!
const cause = new Error('spawn ssh ENOENT')
source.label = 'Build box'
await assert.rejects(
reuseMatchingPrimarySshBackend({
connectionId: registry.primary,
effectiveFingerprint: async () => {
throw cause
},
ensurePrimary: async () => ({ mode: 'remote', remoteKind: 'ssh' }),
profile: 'default',
registry,
source
}),
error => {
assert.equal(
(error as Error).message,
`Could not resolve effective SSH config for connection "Build box" (${source.id}) via ssh -G: spawn ssh ENOENT`
)
assert.equal((error as Error).cause, cause)
return true
}
)
})
test('matching primary/default SSH route reuses the existing descriptor once', async () => {
const registry = migrateV1ToRegistry({
mode: 'ssh',
remote: { mode: 'ssh', host: 'build-host', user: 'alice' },
profiles: {}
})
const source = registry.connections.find(connection => connection.id === registry.primary)
const descriptor = {
mode: 'remote' as const,
remoteKind: 'ssh' as const,
ssh: {
effectiveConfigFingerprint: 'same-effective-config',
host: 'build-host',
keyPath: '~/.ssh/id_ed25519',
remoteProfile: 'default',
user: 'alice'
}
}
let ensureCalls = 0
let fingerprintCalls = 0
assert.equal(source?.kind, 'ssh')
assert.equal(
await reuseMatchingPrimarySshBackend({
connectionId: registry.primary,
effectiveFingerprint: async () => {
fingerprintCalls += 1
return 'same-effective-config'
},
ensurePrimary: async () => {
ensureCalls += 1
return descriptor
},
profile: 'default',
registry,
source: source!
}),
descriptor
)
assert.equal(ensureCalls, 1)
assert.equal(fingerprintCalls, 1)
})
test('non-default or non-primary SSH routes do not resolve the primary backend', async () => {
const registry = migrateV1ToRegistry({
mode: 'ssh',
remote: { mode: 'ssh', host: 'build-host', user: 'alice' },
profiles: {}
})
const source = registry.connections.find(connection => connection.id === registry.primary)!
let ensureCalls = 0
const opts = {
effectiveFingerprint: async () => 'same',
ensurePrimary: async () => {
ensureCalls += 1
return { mode: 'remote' as const, remoteKind: 'ssh' as const }
},
registry,
source
}
assert.equal(
await reuseMatchingPrimarySshBackend({ ...opts, connectionId: registry.primary, profile: 'researcher' }),
null
)
assert.equal(
await reuseMatchingPrimarySshBackend({ ...opts, connectionId: LOCAL_CONNECTION_ID, profile: 'default' }),
null
)
assert.equal(ensureCalls, 0)
})
test('primary SSH reuse rejects a descriptor with different effective dialing config', async () => {
const registry = migrateV1ToRegistry({
mode: 'ssh',
remote: { mode: 'ssh', host: 'build-host', user: 'alice' },
profiles: {}
})
const source = registry.connections.find(connection => connection.id === registry.primary)!
assert.equal(
await reuseMatchingPrimarySshBackend({
connectionId: registry.primary,
effectiveFingerprint: async () => 'registry-config',
ensurePrimary: async () => ({
mode: 'remote',
remoteKind: 'ssh',
ssh: {
effectiveConfigFingerprint: 'active-config',
host: 'other-host',
remoteProfile: ''
}
}),
profile: 'default',
registry,
source
}),
null
)
})
test('primary SSH reuse rejects a descriptor with a different remote Hermes path', async () => {
const registry = migrateV1ToRegistry({
mode: 'ssh',
remote: { mode: 'ssh', host: 'build-host', remoteHermesPath: '/srv/hermes', user: 'alice' },
profiles: {}
})
const source = registry.connections.find(connection => connection.id === registry.primary)!
assert.equal(
await reuseMatchingPrimarySshBackend({
connectionId: registry.primary,
effectiveFingerprint: async () => 'same-effective-config',
ensurePrimary: async () => ({
mode: 'remote',
remoteKind: 'ssh',
ssh: {
effectiveConfigFingerprint: 'same-effective-config',
host: 'build-host',
remoteHermesPath: '/opt/hermes',
remoteProfile: '',
user: 'alice'
}
}),
profile: 'default',
registry,
source
}),
null
)
})
test('registry primary reuses a matching primary backend descriptor', () => {
const registry = normalizeRegistry({
version: REGISTRY_VERSION,
primary: 'hermes-vps',
launchMode: 'primary',
lastUsed: 'hermes-vps',
connections: [
{ id: LOCAL_CONNECTION_ID, kind: 'local', label: 'This device' },
{ id: 'hermes-vps', kind: 'ssh', label: 'Hermes VPS', host: 'hermes-vps' }
]
})
const descriptor = {
connectionId: 'hermes-vps',
mode: 'remote' as const,
remoteKind: 'ssh' as const,
ssh: { host: 'hermes-vps' }
}
assert.equal(registrySourceOwnsPrimaryBackend(registry, 'hermes-vps', descriptor), true)
assert.equal(registrySourceOwnsPrimaryBackend(registry, LOCAL_CONNECTION_ID, descriptor), false)
})
test('resolvedConnectionId identifies local and migrated remote descriptors', () => {
const registry = migrateV1ToRegistry({
mode: 'local',
profiles: {
personal: { mode: 'remote', url: 'https://personal.example:9443/', authMode: 'token' },
work: { mode: 'ssh', host: 'work-host', user: 'root' }
}
})
const personal = registry.connections.find(connection => connection.kind === 'remote')
const work = registry.connections.find(connection => connection.kind === 'ssh')
assert.equal(resolvedConnectionId(registry, { mode: 'local' }), LOCAL_CONNECTION_ID)
assert.equal(
resolvedConnectionId(registry, {
baseUrl: 'https://personal.example:9443',
mode: 'remote',
remoteKind: 'url'
}),
personal?.id
)
assert.equal(
resolvedConnectionId(registry, {
baseUrl: 'http://127.0.0.1:49152',
mode: 'remote',
remoteHost: 'ROOT@WORK-HOST',
remoteKind: 'ssh'
}),
work?.id
)
const ambiguousLocal: ConnectionRegistry = {
...registry,
connections: [...registry.connections, { id: 'local-copy', kind: 'local', label: 'Local copy' }]
}
assert.equal(resolvedConnectionId(ambiguousLocal, { mode: 'local' }), null)
})
test('resolvedConnectionId does not guess an unregistered remote', () => {
assert.equal(
resolvedConnectionId(emptyRegistry(), {
baseUrl: 'https://unknown.example',
mode: 'remote',
remoteKind: 'url'
}),
null
)
})
test('agentHandle bare when unique, @name-device shape when duplicated', () => {
assert.equal(agentHandle('research', 'Homelab', false), 'research')
assert.equal(agentHandle('research', 'Homelab', true), 'research-homelab')
assert.equal(agentHandle('research', 'Work Laptop', true), 'research-work-laptop')
assert.equal(agentHandle('', 'Homelab', false), 'default')
})
test('resolvedConnectionId accepts only a current exact descriptor id and never falls back', () => {
const registry: ConnectionRegistry = {
version: REGISTRY_VERSION,
primary: 'remote-a',
launchMode: 'primary',
lastUsed: 'remote-a',
connections: [
{ id: LOCAL_CONNECTION_ID, kind: 'local', label: 'This device' },
{
id: 'remote-a',
kind: 'remote',
label: 'Remote A',
url: 'https://shared.example',
authMode: 'token',
token: { encoding: 'safeStorage', value: 'token-a' },
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-a' } }
},
{
id: 'remote-b',
kind: 'remote',
label: 'Remote B',
url: 'https://shared.example',
authMode: 'oauth',
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-b' } }
}
]
}
assert.equal(
resolvedConnectionId(registry, {
authMode: 'token',
baseUrl: 'https://shared.example',
connectionId: 'remote-b',
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-a' } },
mode: 'remote',
remoteKind: 'url',
token: { encoding: 'safeStorage', value: 'token-a' }
}),
'remote-b'
)
const inferableRemoteA = {
authMode: 'token',
baseUrl: 'https://shared.example',
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-a' } },
mode: 'remote' as const,
remoteKind: 'url' as const,
token: { encoding: 'safeStorage', value: 'token-a' }
}
// Only true absence enters compatibility inference. Every explicitly
// present invalid value remains unresolved even though the remaining
// envelope uniquely identifies remote-a.
assert.equal(resolvedConnectionId(registry, inferableRemoteA), 'remote-a')
for (const connectionId of ['', ' ', null, undefined, 42, {}, 'unknown-source', 'retired-source']) {
assert.equal(resolvedConnectionId(registry, { ...inferableRemoteA, connectionId }), null)
}
// Registry order is never authority for either an exact current id or a
// rejected explicit claim.
const reordered = { ...registry, connections: [...registry.connections].reverse() }
assert.equal(
resolvedConnectionId(reordered, {
...inferableRemoteA,
connectionId: 'remote-b'
}),
'remote-b'
)
assert.equal(resolvedConnectionId(reordered, { ...inferableRemoteA, connectionId: 'retired-source' }), null)
})
test('resolvedConnectionId reuses the exact URL envelope and rejects weak or duplicate matches', () => {
const sharedUrl = 'https://shared.example/gateway'
const registry: ConnectionRegistry = {
version: REGISTRY_VERSION,
primary: 'remote-token',
launchMode: 'primary',
lastUsed: 'remote-token',
connections: [
{ id: LOCAL_CONNECTION_ID, kind: 'local', label: 'This device' },
{
id: 'remote-token',
kind: 'remote',
label: 'Token remote',
url: sharedUrl,
authMode: 'token',
token: { encoding: 'safeStorage', value: 'token-a' },
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-a' } }
},
{
id: 'remote-oauth',
kind: 'remote',
label: 'OAuth remote',
url: `${sharedUrl}/`,
authMode: 'oauth',
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-b' } }
},
{
id: 'cloud-nous',
kind: 'cloud',
label: 'Nous cloud',
url: sharedUrl,
authMode: 'oauth',
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-cloud' } },
org: 'nous'
},
{
id: 'cloud-labs',
kind: 'cloud',
label: 'Labs cloud',
url: sharedUrl,
authMode: 'oauth',
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-cloud' } },
org: 'labs'
}
]
}
assert.equal(
resolvedConnectionId(registry, {
authMode: 'token',
baseUrl: sharedUrl,
headers: { 'cf-access-client-id': { encoding: 'safeStorage', value: 'header-a' } },
mode: 'remote',
remoteKind: 'url',
token: { encoding: 'safeStorage', value: 'token-a' }
}),
'remote-token'
)
assert.equal(
resolvedConnectionId(registry, {
authMode: 'oauth',
baseUrl: sharedUrl,
headers: { 'CF-ACCESS-CLIENT-ID': { encoding: 'safeStorage', value: 'header-b' } },
mode: 'remote',
remoteKind: 'url'
}),
'remote-oauth'
)
assert.equal(
resolvedConnectionId(registry, {
authMode: 'oauth',
baseUrl: sharedUrl,
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-cloud' } },
mode: 'remote',
org: 'nous',
remoteKind: 'cloud'
}),
'cloud-nous'
)
assert.equal(
resolvedConnectionId(registry, {
authMode: 'oauth',
baseUrl: sharedUrl,
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-cloud' } },
mode: 'remote',
org: 'labs',
remoteKind: 'cloud'
}),
'cloud-labs'
)
// Post-dial URL-only shapes do not contain enough proof to choose a source.
assert.equal(resolvedConnectionId(registry, { baseUrl: sharedUrl, mode: 'remote', remoteKind: 'url' }), null)
assert.equal(resolvedConnectionId(registry, { baseUrl: sharedUrl, mode: 'remote', remoteKind: 'cloud' }), null)
// Even a complete envelope fails closed when two registrations are exact twins.
const duplicate: ConnectionRegistry = {
...registry,
connections: [
...registry.connections,
{ ...registry.connections.find(connection => connection.id === 'remote-token')!, id: 'remote-token-copy' }
]
}
assert.equal(
resolvedConnectionId(duplicate, {
authMode: 'token',
baseUrl: sharedUrl,
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-a' } },
mode: 'remote',
remoteKind: 'url',
token: { encoding: 'safeStorage', value: 'token-a' }
}),
null
)
assert.equal(
resolvedConnectionId(
{ ...duplicate, connections: [...duplicate.connections].reverse() },
{
authMode: 'token',
baseUrl: sharedUrl,
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'header-a' } },
mode: 'remote',
remoteKind: 'url',
token: { encoding: 'safeStorage', value: 'token-a' }
}
),
null
)
})
test('resolvedConnectionId keeps same-host SSH routes distinct by port, key, path, and profile', () => {
const base = {
host: 'work-host',
keyPath: '/keys/a',
kind: 'ssh' as const,
remoteHermesPath: '/srv/hermes',
remoteProfile: 'alpha',
user: 'root'
}
const registry: ConnectionRegistry = {
version: REGISTRY_VERSION,
primary: 'ssh-base',
launchMode: 'primary',
lastUsed: 'ssh-base',
connections: [
{ id: LOCAL_CONNECTION_ID, kind: 'local', label: 'This device' },
{ ...base, id: 'ssh-base', label: 'SSH base' },
{ ...base, id: 'ssh-port', label: 'SSH port', port: 2222 },
{ ...base, id: 'ssh-key', keyPath: '/keys/b', label: 'SSH key' },
{ ...base, id: 'ssh-path', label: 'SSH path', remoteHermesPath: '/opt/hermes' },
{ ...base, id: 'ssh-profile', label: 'SSH profile', remoteProfile: 'beta' }
]
}
const resolve = (ssh: NonNullable<Parameters<typeof resolvedConnectionId>[1]['ssh']>) =>
resolvedConnectionId(registry, { mode: 'remote', remoteKind: 'ssh', ssh })
assert.equal(resolve(base), 'ssh-base')
assert.equal(resolve({ ...base, port: 2222 }), 'ssh-port')
assert.equal(resolve({ ...base, keyPath: '/keys/b' }), 'ssh-key')
assert.equal(resolve({ ...base, remoteHermesPath: '/opt/hermes' }), 'ssh-path')
assert.equal(resolve({ ...base, remoteProfile: 'beta' }), 'ssh-profile')
assert.equal(
resolvedConnectionId(registry, {
connectionId: 'ssh-port',
mode: 'remote',
remoteKind: 'ssh',
ssh: { ...base, port: 9999 }
}),
'ssh-port'
)
// user@host is a transport hint, not a registry identity, when variants coexist.
assert.equal(
resolvedConnectionId(registry, {
mode: 'remote',
remoteHost: 'ROOT@WORK-HOST',
remoteKind: 'ssh'
}),
null
)
assert.equal(
resolvedConnectionId(registry, {
mode: 'remote',
remoteHost: 'ROOT@WORK-HOST',
remoteKind: 'ssh',
ssh: undefined
}),
null
)
const duplicate: ConnectionRegistry = {
...registry,
connections: [...registry.connections, { ...base, id: 'ssh-base-copy', label: 'SSH base copy' }]
}
assert.equal(resolvedConnectionId(duplicate, { mode: 'remote', remoteKind: 'ssh', ssh: base }), null)
assert.equal(
resolvedConnectionId(
{ ...duplicate, connections: [...duplicate.connections].reverse() },
{ mode: 'remote', remoteKind: 'ssh', ssh: base }
),
null
)
})
test('connectionIdForLabel suffixes on collision and never mints "local"', () => {
assert.equal(connectionIdForLabel('Homelab', []), 'homelab')
assert.equal(connectionIdForLabel('Homelab', ['homelab']), 'homelab-2')
assert.equal(connectionIdForLabel('Homelab', ['homelab', 'homelab-2']), 'homelab-3')
assert.equal(connectionIdForLabel('Local', []), 'local-2')
})
test('uniqueLabel counts up (never "X 2 2") and clamps long candidates', () => {
assert.equal(uniqueLabel('Homelab', []), 'Homelab')
assert.equal(uniqueLabel('Homelab', ['Homelab']), 'Homelab 2')
assert.equal(uniqueLabel('Homelab', ['Homelab', 'Homelab 2']), 'Homelab 3')
// Case-insensitive collision detection.
assert.equal(uniqueLabel('homelab', ['HOMELAB']), 'homelab 2')
const long = 'x'.repeat(300)
assert.ok(uniqueLabel(long, []).length <= 64)
assert.ok(uniqueLabel(long, [uniqueLabel(long, [])]).length <= 64)
})
// --- backendScopeKey (composite pool keys) ---
// The electron and @hermes/shared implementations MUST stay byte-identical —
// the renderer keys its socket registry with the shared copy while the main
// process keys the backend pool with this one. This contract test is the
// enforcement (see the NOTE on backendScopeKey).
test('backendScopeKey: electron and shared implementations agree everywhere', async () => {
// Non-literal specifier on purpose: tsconfig.electron.json's project
// boundary excludes apps/shared sources, but vitest resolves the workspace
// package fine at runtime — which is exactly what this test needs.
const shared = (await import(String('@hermes/shared'))) as {
backendScopeKey: typeof backendScopeKey
backendScopePrefix: typeof backendScopePrefix
LOCAL_CONNECTION_ID: string
}
const cases: [null | string | undefined, null | string | undefined][] = [
[null, null],
[undefined, undefined],
['', ''],
['local', 'research'],
['homelab', 'research'],
['homelab', ''],
[' homelab ', ' research '],
['spark-2', 'default']
]
for (const [conn, profile] of cases) {
assert.equal(backendScopeKey(conn, profile), shared.backendScopeKey(conn, profile))
}
assert.equal(backendScopePrefix('homelab'), shared.backendScopePrefix('homelab'))
assert.equal(LOCAL_CONNECTION_ID, shared.LOCAL_CONNECTION_ID)
})
test('backendScopeKey: local/empty connection keeps the bare profile key', () => {
assert.equal(backendScopeKey(null, 'research'), 'research')
assert.equal(backendScopeKey('', 'research'), 'research')
assert.equal(backendScopeKey(LOCAL_CONNECTION_ID, 'research'), 'research')
assert.equal(backendScopeKey('local', ''), 'default')
assert.equal(backendScopeKey(undefined, undefined), 'default')
})
test('backendScopeKey: non-local connections get an unambiguous composite', () => {
assert.equal(backendScopeKey('homelab', 'research'), 'conn:homelab::research')
assert.equal(backendScopeKey('homelab', ''), 'conn:homelab::default')
// Composite keys can never collide with a plain profile name, and the
// prefix helper matches exactly the keys the connection owns.
assert.ok(backendScopeKey('homelab', 'research').startsWith(backendScopePrefix('homelab')))
assert.ok(!backendScopeKey('homelab-2', 'research').startsWith(backendScopePrefix('homelab')))
assert.ok(!'research'.startsWith(backendScopePrefix('homelab')))
})
// --- resolveRegistryLocalRoute (registry 'local' entry vs the v1 route) ---
test('registry local route: delegates to the legacy path when v1 is local (single-source users byte-identical)', () => {
assert.deepEqual(resolveRegistryLocalRoute('research', {}), { delegate: true, poolKey: 'research' })
assert.deepEqual(resolveRegistryLocalRoute('', {}), { delegate: true, poolKey: 'default' })
assert.deepEqual(resolveRegistryLocalRoute(null, { globalRemote: false }), { delegate: true, poolKey: 'default' })
})
test('registry local route: v1 REMOTE global mode forces a genuinely-local backend (migration scenario)', () => {
// The migration keeps the mandatory 'local' entry AND makes the v1 remote
// the registry primary. If 'local' delegated to the v1 route here, the
// roster's "This device" rows would enumerate + dial the REMOTE primary —
// every profile duplicated and local agents talking to the remote box.
const route = resolveRegistryLocalRoute('default', { globalRemote: true })
assert.equal(route.delegate, false)
// The forced-local child must NOT pool under the bare profile key: that
// slot is where the v1 route caches the REMOTE descriptor. The composite
// form is prefix-owned by the local connection and collision-free.
assert.equal(route.poolKey, 'conn:local::default')
assert.ok(route.poolKey.startsWith(backendScopePrefix(LOCAL_CONNECTION_ID)))
assert.notEqual(route.poolKey, backendScopeKey(LOCAL_CONNECTION_ID, 'default'))
})
test('registry local route: a per-profile remote override delegates to the override (#90477)', () => {
// The per-profile SSH/remote override is the authoritative route for that
// profile. Forcing local here made the roster list the profile via its
// override but open the thread in a local child — which fails when the
// profile exists only on the remote. The override must win.
const route = resolveRegistryLocalRoute('research', { profileRemoteOverride: true })
assert.deepEqual(route, { delegate: true, poolKey: 'research' })
})
test('registry local route: per-profile override wins when global remote is also active', () => {
const route = resolveRegistryLocalRoute('research', {
globalRemote: true,
profileRemoteOverride: true
})
assert.deepEqual(route, { delegate: true, poolKey: 'research' })
})
// --- shouldDeferLocalEnumeration (roster's connect-on-demand for 'local') ---
test('local enumeration: delegate route (local-primary desktop) always enumerates', () => {
const route = resolveRegistryLocalRoute('default', {})
assert.equal(shouldDeferLocalEnumeration(route, []), false)
assert.equal(shouldDeferLocalEnumeration(route, ['conn:local::default']), false)
})
test('local enumeration: forced-local route defers until a local child exists (remote-primary desktop)', () => {
// Remote-gateway-only desktops: enumerating "This device" here would SPAWN
// a local backend the user never asked for — a phantom `default` agent
// that duplicates their real one and forces -device handles onto it.
const route = resolveRegistryLocalRoute('default', { globalRemote: true })
assert.equal(shouldDeferLocalEnumeration(route, []), true)
// The v1 remote descriptor cached at the BARE profile key is not a local child.
assert.equal(shouldDeferLocalEnumeration(route, ['default', 'research']), true)
// Once the user has genuinely opened a forced-local child, it enumerates.
assert.equal(shouldDeferLocalEnumeration(route, ['conn:local::default']), false)
})
// --- buildAgentRoster (union roster + @name-device rule) ---
test('roster: unique profiles keep bare handles; duplicates get @name-device', () => {
const local = { id: 'local', kind: 'local' as const, label: 'This device' }
const homelab = { id: 'homelab', kind: 'remote' as const, label: 'Homelab', url: 'http://h:1' }
const roster = buildAgentRoster([
{ connection: local, profiles: ['default', 'research'] },
{ connection: homelab, profiles: ['research', 'coder'] }
])
const byKey = new Map(roster.map(a => [`${a.connectionId}/${a.profile}`, a.handle]))
// research exists on both sources → both disambiguate.
assert.equal(byKey.get('local/research'), 'research-this-device')
assert.equal(byKey.get('homelab/research'), 'research-homelab')
// default and coder are unique → bare names.
assert.equal(byKey.get('local/default'), 'default')
assert.equal(byKey.get('homelab/coder'), 'coder')
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']
})
assert.deepEqual(
rememberSshEnumeration({ profiles: null, error: 'connect-on-demand' }, ['bob', 'kai', 'rook'], 'ssh'),
{ profiles: ['bob', 'kai', 'rook'], error: 'connect-on-demand' }
)
assert.deepEqual(rememberSshEnumeration({ profiles: null, error: 'connect-on-demand' }, null, 'ssh'), {
profiles: ['default'],
error: 'connect-on-demand'
})
assert.deepEqual(rememberSshEnumeration({ profiles: null, error: 'connect-on-demand' }, null, 'remote'), {
profiles: null,
error: 'connect-on-demand'
})
})
test('rememberSshEnumeration: a bounced remote source keeps its last-known roster (4-bots-show-as-2)', () => {
// A VPS restart makes the remote source unreachable for a few polls. The
// last successful enumeration must keep painting so the roster does not
// silently drop that source's bots mid-outage.
assert.deepEqual(
rememberSshEnumeration({ profiles: null, error: 'unreachable' }, ['default', 'ceo', 'accounter'], 'remote'),
{ profiles: ['default', 'ceo', 'accounter'], error: 'unreachable' }
)
// Never-seen remote source: no seed — an unreachable URL is not evidence a
// backend exists there.
assert.deepEqual(rememberSshEnumeration({ profiles: null, error: 'unreachable' }, null, 'remote'), {
profiles: null,
error: 'unreachable'
})
// Local enumeration failures never reuse a cache (the local runtime answers
// authoritatively or not at all).
assert.deepEqual(rememberSshEnumeration({ profiles: null, error: 'boom' }, ['default'], 'local'), {
profiles: null,
error: 'boom'
})
})
test('shouldRetrySshInventory: first try, cooldown, then retry; cache never retries', () => {
assert.equal(shouldRetrySshInventory(false, null, 1_000), true)
assert.equal(shouldRetrySshInventory(false, 1_000, 30_000, 60_000), false)
assert.equal(shouldRetrySshInventory(false, 1_000, 61_000, 60_000), true)
assert.equal(shouldRetrySshInventory(true, 1_000, 120_000, 60_000), false)
})
test('parseRemoteProfileListing: Mini/Spark dirs become roster names and drop rollbacks', () => {
const listed = parseRemoteProfileListing(
['bob', 'dixie', 'goose', 'rambo', 'bob.rollback-old', '.hidden', '', 'not a name'].join('\n')
)
assert.deepEqual(listed, ['default', 'bob', 'dixie', 'goose', 'rambo'])
})
test('parseRemoteProfileListing: empty listing is still the default agent', () => {
assert.deepEqual(parseRemoteProfileListing(''), ['default'])
})
test('roster: unreachable sources contribute no rows and cannot fake duplicates', () => {
const local = { id: 'local', kind: 'local' as const, label: 'This device' }
const dead = { id: 'dead', kind: 'remote' as const, label: 'Dead box', url: 'http://d:1' }
const roster = buildAgentRoster([
{ connection: local, profiles: ['research'] },
{ connection: dead, profiles: null, error: 'unreachable' }
])
assert.equal(roster.length, 1)
// Only one live source has research → bare handle, no phantom duplicate.
assert.equal(roster[0].handle, 'research')
})
test('roster: duplicate profiles from one connection remain one routable agent', () => {
const local = { id: 'local', kind: 'local' as const, label: 'This device' }
const homelab = { id: 'homelab', kind: 'remote' as const, label: 'Homelab', url: 'http://h:1' }
const roster = buildAgentRoster([
{ connection: local, profiles: ['default', 'research', 'default'] },
// A duplicate registry enumeration must not make local/research a second
// bot identity either.
{ connection: local, profiles: ['research'] },
{ connection: homelab, profiles: ['research', 'research'] }
])
assert.deepEqual(
roster.map(agent => `${agent.connectionId}/${agent.profile}`),
['local/default', 'local/research', 'homelab/research']
)
assert.equal(
roster.find(agent => agent.connectionId === 'local' && agent.profile === 'research')?.handle,
'research-this-device'
)
assert.equal(
roster.find(agent => agent.connectionId === 'homelab' && agent.profile === 'research')?.handle,
'research-homelab'
)
})
// --- buildAgentRoster: same-backend (install_id) collapse ---
test('roster: two connections with the same install_id collapse to one row per profile', () => {
const hostname = { id: 'spark', kind: 'remote' as const, label: 'Spark', url: 'http://spark:8642' }
const tailscale = { id: 'spark-ts', kind: 'remote' as const, label: 'Spark TS', url: 'http://100.1.2.3:8642' }
const roster = buildAgentRoster([
{ connection: hostname, profiles: ['default', 'research'], installId: 'aaa' },
{ connection: tailscale, profiles: ['default', 'research'], installId: 'aaa' }
])
// One row per (install, profile) — no duplicate bots for the same box.
assert.deepEqual(roster.map(agent => `${agent.connectionId}/${agent.profile}`).sort(), [
'spark/default',
'spark/research'
])
// Handle disambiguation runs AFTER the collapse: no more suffixed names.
assert.deepEqual(roster.map(agent => agent.handle).sort(), ['default', 'research'])
})
test('roster: collapse prefers the active (primary) connection', () => {
const hostname = { id: 'spark', kind: 'remote' as const, label: 'Spark', url: 'http://spark:8642' }
const tailscale = { id: 'spark-ts', kind: 'remote' as const, label: 'Spark TS', url: 'http://100.1.2.3:8642' }
const roster = buildAgentRoster(
[
{ connection: hostname, profiles: ['default'], installId: 'aaa' },
{ connection: tailscale, profiles: ['default'], installId: 'aaa' }
],
{ primaryConnectionId: 'spark-ts' }
)
assert.equal(roster.length, 1)
assert.equal(roster[0].connectionId, 'spark-ts')
})
test('roster: collapse pick order is local > ssh > remote > cloud, then registration order', () => {
const local = { id: 'local', kind: 'local' as const, label: 'This device' }
const remote = { id: 'loop', kind: 'remote' as const, label: 'Loopback', url: 'http://127.0.0.1:8642' }
const cloud = { id: 'cl', kind: 'cloud' as const, label: 'Cloud twin', url: 'http://cl:1' }
const ssh = { id: 'tun', kind: 'ssh' as const, label: 'Tunnel', host: 'box' }
// Same box registered four ways; primary is NOT one of them (unset).
const roster = buildAgentRoster([
{ connection: cloud, profiles: ['default'], installId: 'aaa' },
{ connection: remote, profiles: ['default'], installId: 'aaa' },
{ connection: ssh, profiles: ['default'], installId: 'aaa' },
{ connection: local, profiles: ['default'], installId: 'aaa' }
])
assert.equal(roster.length, 1)
assert.equal(roster[0].connectionId, 'local')
// Without the local candidate, ssh wins over remote/cloud.
const noLocal = buildAgentRoster([
{ connection: cloud, profiles: ['default'], installId: 'aaa' },
{ connection: remote, profiles: ['default'], installId: 'aaa' },
{ connection: ssh, profiles: ['default'], installId: 'aaa' }
])
assert.equal(noLocal[0].connectionId, 'tun')
// Same kind → earliest-registered (enumeration order) wins.
const twin = { id: 'loop2', kind: 'remote' as const, label: 'Loopback 2', url: 'http://[::1]:8642' }
const sameKind = buildAgentRoster([
{ connection: remote, profiles: ['default'], installId: 'aaa' },
{ connection: twin, profiles: ['default'], installId: 'aaa' }
])
assert.equal(sameKind[0].connectionId, 'loop')
})
test('roster: missing install_id bypasses the collapse (older backends keep current behavior)', () => {
const hostname = { id: 'spark', kind: 'remote' as const, label: 'Spark', url: 'http://spark:8642' }
const tailscale = { id: 'spark-ts', kind: 'remote' as const, label: 'Spark TS', url: 'http://100.1.2.3:8642' }
// Neither reports an id → both rows survive, handles disambiguate.
const roster = buildAgentRoster([
{ connection: hostname, profiles: ['default'] },
{ connection: tailscale, profiles: ['default'] }
])
assert.equal(roster.length, 2)
assert.deepEqual(roster.map(a => a.handle).sort(), ['default-spark', 'default-spark-ts'])
// One id + one missing must NOT collapse either (undefined never matches).
const mixed = buildAgentRoster([
{ connection: hostname, profiles: ['default'], installId: 'aaa' },
{ connection: tailscale, profiles: ['default'] }
])
assert.equal(mixed.length, 2)
})
test('roster: different install_ids stay separate rows with disambiguated handles', () => {
const spark = { id: 'spark', kind: 'remote' as const, label: 'Spark', url: 'http://spark:8642' }
const mini = { id: 'mini', kind: 'remote' as const, label: 'Mini', url: 'http://mini:8642' }
const roster = buildAgentRoster([
{ connection: spark, profiles: ['default'], installId: 'aaa' },
{ connection: mini, profiles: ['default'], installId: 'bbb' }
])
assert.equal(roster.length, 2)
assert.deepEqual(roster.map(a => a.handle).sort(), ['default-mini', 'default-spark'])
})
test('roster: collapse also folds a third same-box connection from a per-profile v1 override import', () => {
// The reporter's "profile with cron appears as another duplicate": the v1
// migration imports per-profile override blocks as EXTRA connections, so a
// cron profile pinned to the same box via a third URL becomes a third
// registry entry. Same install_id → still one row per profile.
const hostname = { id: 'spark', kind: 'remote' as const, label: 'Spark', url: 'http://spark:8642' }
const tailscale = { id: 'spark-ts', kind: 'remote' as const, label: 'Spark TS', url: 'http://100.1.2.3:8642' }
const override = { id: 'spark-lan', kind: 'remote' as const, label: 'Spark LAN', url: 'http://192.168.1.5:8642' }
const roster = buildAgentRoster([
{ connection: hostname, profiles: ['default', 'cron-bot'], installId: 'aaa' },
{ connection: tailscale, profiles: ['default', 'cron-bot'], installId: 'aaa' },
{ connection: override, profiles: ['default', 'cron-bot'], installId: 'aaa' }
])
assert.deepEqual(roster.map(agent => `${agent.profile}:${agent.handle}`).sort(), [
'cron-bot:cron-bot',
'default:default'
])
})
// --- updateEligibility ---
test('update fan-out: cloud is platform-managed, everything else eligible', () => {
assert.deepEqual(updateEligibility({ id: 'c', kind: 'cloud', label: 'Cloud' }), {
eligible: false,
reason: 'cloud-managed'
})
assert.equal(updateEligibility({ id: 'local', kind: 'local', label: 'x' }).eligible, true)
assert.equal(updateEligibility({ id: 'r', kind: 'remote', label: 'x' }).eligible, true)
assert.equal(updateEligibility({ id: 's', kind: 'ssh', label: 'x' }).eligible, true)
})
// --- normalizeConnectionInput ---
test('save rejects the reserved "local" id on non-local kinds', () => {
assert.throws(
() =>
normalizeConnectionInput({ id: 'local', kind: 'remote', label: 'Sneaky', url: 'http://x:1' }, emptyRegistry()),
/reserved/
)
})
test('token only persists on token-auth remotes; oauth/cloud drop it', () => {
const registry = emptyRegistry()
const tokenAuth = normalizeConnectionInput(
{ kind: 'remote', label: 'A', url: 'http://a:1', authMode: 'token', token: { enc: 'x' } },
registry
)
assert.deepEqual(tokenAuth.token, { enc: 'x' })
const oauth = normalizeConnectionInput(
{ kind: 'remote', label: 'B', url: 'http://b:1', authMode: 'oauth', token: { enc: 'x' } },
registry
)
assert.equal(oauth.token, undefined)
const cloud = normalizeConnectionInput(
{ kind: 'cloud', label: 'C', url: 'https://c.hermes.cloud', authMode: 'oauth', token: { enc: 'x' } },
registry
)
assert.equal(cloud.token, undefined)
})
// --- mergeConnectionInput (edit inheritance) ---
test('merge preserves fields the editor does not carry (org, ssh extras)', () => {
const cloud = {
authMode: 'oauth' as const,
id: 'c',
kind: 'cloud' as const,
label: 'Cloud',
org: 'nous',
url: 'https://a.cloud'
}
const renamed = mergeConnectionInput({ id: 'c', kind: 'cloud', label: 'Renamed', url: 'https://a.cloud' }, cloud)
assert.equal(renamed.org, 'nous')
const ssh = {
host: 'homelab.lan',
id: 's',
keyPath: '/k/id',
kind: 'ssh' as const,
label: 'Box',
port: 2222,
remoteHermesPath: '/opt/hermes',
remoteProfile: 'research',
user: 'k'
}
const labelOnly = mergeConnectionInput({ id: 's', kind: 'ssh', label: 'Renamed box' }, ssh)
assert.equal(labelOnly.remoteHermesPath, '/opt/hermes')
assert.equal(labelOnly.remoteProfile, 'research')
assert.equal(labelOnly.host, 'homelab.lan')
assert.equal(labelOnly.user, 'k')
assert.equal(labelOnly.port, 2222)
})
test('merge: a supplied ssh host string beats stored user/port', () => {
const ssh = { host: 'spark1', id: 's', kind: 'ssh' as const, label: 'Spark', port: 2222, user: 'tek' }
const merged = mergeConnectionInput({ host: 'admin@newbox:2200', id: 's', kind: 'ssh', label: 'Spark' }, ssh)
// Stored user/port must NOT ride along — the host string is authoritative.
assert.equal(merged.user, undefined)
assert.equal(merged.port, undefined)
const entry = normalizeConnectionInput(merged, emptyRegistry())
assert.equal(entry.host, 'newbox')
assert.equal(entry.user, 'admin')
assert.equal(entry.port, 2200)
})
test('save rejects a missing label with a device-name message', () => {
assert.throws(
() => normalizeConnectionInput({ kind: 'remote', label: ' ', url: 'http://10.0.0.5:9119' }, emptyRegistry()),
/device name/
)
})
test('save rejects a duplicate label case-insensitively', () => {
let registry = emptyRegistry()
registry = upsertConnection(
registry,
normalizeConnectionInput({ kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' }, registry)
)
assert.throws(
() => normalizeConnectionInput({ kind: 'remote', label: ' homelab ', url: 'http://10.0.0.9:9119' }, registry),
/must be unique/
)
})
test('editing an entry does not collide with its own label', () => {
let registry = emptyRegistry()
const entry = normalizeConnectionInput({ kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' }, registry)
registry = upsertConnection(registry, entry)
const edited = normalizeConnectionInput(
{ id: entry.id, kind: 'remote', label: 'Homelab', url: 'http://10.0.0.6:9119' },
registry
)
assert.equal(edited.id, entry.id)
assert.equal(edited.url, 'http://10.0.0.6:9119')
})
test('duplicate gateway URLs are rejected across remote and cloud kinds', () => {
let registry = emptyRegistry()
registry = upsertConnection(
registry,
normalizeConnectionInput({ kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' }, registry)
)
// Same URL modulo trailing slash → dupe, even as a different kind.
assert.throws(
() => normalizeConnectionInput({ kind: 'remote', label: 'Twin', url: 'http://10.0.0.5:9119/' }, registry),
/already exists/
)
assert.throws(
() => normalizeConnectionInput({ kind: 'cloud', label: 'Cloud twin', url: 'http://10.0.0.5:9119' }, registry),
/already exists/
)
// Editing the entry itself keeps its own URL without self-colliding.
const existing = registry.connections.find(c => c.kind === 'remote')!
const edited = normalizeConnectionInput(
{ id: existing.id, kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' },
registry
)
assert.equal(edited.id, existing.id)
})
test('duplicate ssh targets are rejected on user@host:port + remote profile', () => {
let registry = emptyRegistry()
registry = upsertConnection(
registry,
normalizeConnectionInput({ kind: 'ssh', label: 'Box', host: 'alice@box:22', remoteProfile: 'work' }, registry)
)
assert.throws(
() =>
normalizeConnectionInput(
{ kind: 'ssh', label: 'Box twin', host: 'alice@box:22', remoteProfile: 'work' },
registry
),
/already exists/
)
// A different remote profile on the same host is a distinct agent source.
const otherProfile = normalizeConnectionInput(
{ kind: 'ssh', label: 'Box other', host: 'alice@box:22', remoteProfile: 'other' },
registry
)
assert.equal(otherProfile.kind, 'ssh')
})
test('remote input normalizes URL and auth mode; cloud keeps org', () => {
const registry = emptyRegistry()
const remote = normalizeConnectionInput(
{ kind: 'remote', label: 'LAN box', url: '10.0.0.5:9119', authMode: 'weird' },
registry
)
assert.equal(remote.url, 'http://10.0.0.5:9119')
assert.equal(remote.authMode, 'token')
const cloud = normalizeConnectionInput(
{ kind: 'cloud', label: 'Cloud', url: 'https://foo.hermes.cloud', authMode: 'oauth', org: 'nous' },
registry
)
assert.equal(cloud.kind, 'cloud')
assert.equal(cloud.org, 'nous')
assert.equal(cloud.authMode, 'oauth')
})
test('ssh input requires a host; local input only carries the label', () => {
const registry = emptyRegistry()
assert.throws(() => normalizeConnectionInput({ kind: 'ssh', label: 'Spark', host: ' ' }, registry), /host/)
const ssh = normalizeConnectionInput({ kind: 'ssh', label: 'Spark', host: 'tek@spark1:2222' }, registry)
assert.equal(ssh.host, 'spark1')
assert.equal(ssh.user, 'tek')
assert.equal(ssh.port, 2222)
const local = normalizeConnectionInput({ kind: 'local', label: 'My MacBook' }, registry)
assert.equal(local.id, LOCAL_CONNECTION_ID)
assert.deepEqual(Object.keys(local).sort(), ['id', 'kind', 'label'])
})
// --- normalizeRegistry ---
test('normalizeRegistry degrades junk to a local-only registry', () => {
for (const junk of [null, undefined, 42, 'nope', { connections: 'zzz' }, { version: 99 }]) {
const registry = normalizeRegistry(junk)
assert.equal(registry.version, REGISTRY_VERSION)
assert.equal(registry.primary, LOCAL_CONNECTION_ID)
assert.equal(registry.launchMode, 'primary')
assert.equal(registry.lastUsed, LOCAL_CONNECTION_ID)
assert.equal(registry.connections.length, 1)
assert.equal(registry.connections[0].kind, 'local')
}
})
test('normalizeRegistry guarantees local, dedupes labels, fixes dangling primary', () => {
const registry = normalizeRegistry({
version: 2,
primary: 'ghost',
connections: [
{ id: 'a', kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' },
{ id: 'b', kind: 'remote', label: 'homelab', url: 'http://10.0.0.6:9119' },
{ id: 'c', kind: 'remote', label: 'No URL entry' },
{ kind: 'nonsense', label: 'x' }
]
})
assert.equal(registry.primary, LOCAL_CONNECTION_ID)
assert.ok(registry.connections.some(c => c.kind === 'local'))
const labels = registry.connections.map(c => labelKey(c.label))
assert.equal(new Set(labels).size, labels.length)
// The url-less remote entry is dropped, the junk kind is dropped.
assert.equal(registry.connections.filter(c => c.kind === 'remote').length, 2)
})
test('normalizeRegistry round-trips a valid registry unchanged in shape', () => {
const input = {
version: 2,
primary: 'homelab',
launchMode: 'last-used',
lastUsed: 'homelab',
connections: [
{ id: 'local', kind: 'local', label: 'This device' },
{
id: 'homelab',
kind: 'remote',
label: 'Homelab',
url: 'http://10.0.0.5:9119',
authMode: 'token',
token: { v: 1 }
},
{
id: 'cloud-1',
kind: 'cloud',
label: 'Hermes Cloud',
url: 'https://a.hermes.cloud',
authMode: 'oauth',
org: 'nous'
},
{ id: 'spark', kind: 'ssh', label: 'Spark', host: 'spark1', user: 'tek', port: 2222 }
]
}
const registry = normalizeRegistry(input)
assert.equal(registry.primary, 'homelab')
assert.equal(registry.launchMode, 'last-used')
assert.equal(registry.lastUsed, 'homelab')
assert.equal(registry.connections.length, 4)
assert.deepEqual(
registry.connections.map(c => c.id),
['local', 'homelab', 'cloud-1', 'spark']
)
assert.deepEqual(registry.connections[1].token, { v: 1 })
assert.equal(registry.connections[3].port, 2222)
})
test('normalizeRegistry falls back to Primary when the last-used source is missing', () => {
const registry = normalizeRegistry({
version: 2,
primary: 'homelab',
launchMode: 'last-used',
lastUsed: 'retired-host',
connections: [
{ id: 'local', kind: 'local', label: 'This device' },
{ id: 'homelab', kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' }
]
})
assert.equal(registry.launchMode, 'last-used')
assert.equal(registry.lastUsed, 'homelab')
})
// --- v1 → v2 migration ---
test('migrate: v1 local-only config → local-only registry', () => {
const registry = migrateV1ToRegistry({ mode: 'local', remote: {}, profiles: {} })
assert.equal(registry.primary, LOCAL_CONNECTION_ID)
assert.equal(registry.connections.length, 1)
})
test('migrate: v1 global remote becomes a labeled entry and the primary', () => {
const registry = migrateV1ToRegistry({
mode: 'remote',
remote: { url: 'http://homelab.lan:9119', authMode: 'token', token: { enc: 'x' } }
})
const remote = registry.connections.find(c => c.kind === 'remote')
assert.ok(remote)
assert.equal(registry.primary, remote.id)
assert.equal(remote.label, 'homelab.lan:9119')
assert.deepEqual(remote.token, { enc: 'x' })
})
test('migrate: v1 cloud keeps cloud provenance + org', () => {
const registry = migrateV1ToRegistry({
mode: 'cloud',
remote: { url: 'https://a.hermes.cloud', authMode: 'oauth', org: 'nous' }
})
const cloud = registry.connections.find(c => c.kind === 'cloud')
assert.ok(cloud)
assert.equal(registry.primary, cloud.id)
assert.equal(cloud.org, 'nous')
})
test('migrate: per-profile overrides become extra sources, deduped by URL', () => {
const registry = migrateV1ToRegistry({
mode: 'remote',
remote: { url: 'http://homelab.lan:9119', authMode: 'token', token: { enc: 'x' } },
profiles: {
research: { mode: 'remote', url: 'http://homelab.lan:9119', authMode: 'token', token: { enc: 'x' } },
coder: { mode: 'remote', url: 'http://other.lan:9119', authMode: 'token', token: { enc: 'y' } },
sparky: { mode: 'ssh', host: 'spark1', user: 'tek' },
plain: { mode: 'local', savedSsh: { mode: 'ssh', host: 'spark1', user: 'tek' } }
}
})
// homelab (global+research deduped), other.lan, spark ssh (override+savedSsh deduped), local
assert.equal(registry.connections.length, 4)
assert.equal(registry.connections.filter(c => c.kind === 'remote').length, 2)
assert.equal(registry.connections.filter(c => c.kind === 'ssh').length, 1)
})
test('migrate: v1 global ssh becomes the primary', () => {
const registry = migrateV1ToRegistry({
mode: 'ssh',
remote: { mode: 'ssh', host: 'spark1', user: 'tek', port: 2222 }
})
const ssh = registry.connections.find(c => c.kind === 'ssh')
assert.ok(ssh)
assert.equal(registry.primary, ssh.id)
assert.equal(ssh.label, 'spark1')
})
test('migrate: duplicate host labels are suffixed, not dropped', () => {
const registry = migrateV1ToRegistry({
mode: 'remote',
remote: { url: 'http://box.lan:9119', authMode: 'token', token: {} },
profiles: {
a: { mode: 'ssh', host: 'box.lan' }
}
})
const labels = registry.connections.map(c => labelKey(c.label))
assert.equal(new Set(labels).size, labels.length)
assert.equal(registry.connections.length, 3)
})
// --- registry operations ---
test('removeConnection: local refuses, primary and last-used retarget safely', () => {
let registry = emptyRegistry()
const entry = normalizeConnectionInput({ kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' }, registry)
registry = upsertConnection(registry, entry)
registry = setPrimaryConnection(registry, entry.id)
registry = setLastUsedConnection(registry, entry.id)
assert.throws(() => removeConnection(registry, LOCAL_CONNECTION_ID), /cannot be removed/)
const after = removeConnection(registry, entry.id)
assert.equal(after.primary, LOCAL_CONNECTION_ID)
assert.equal(after.lastUsed, LOCAL_CONNECTION_ID)
assert.equal(after.connections.length, 1)
// Removing an unknown id is a no-op, not an error.
assert.equal(removeConnection(after, 'ghost'), after)
})
test('setPrimaryConnection validates the target id', () => {
const registry = emptyRegistry()
assert.throws(() => setPrimaryConnection(registry, 'ghost'), /No connection/)
assert.equal(setPrimaryConnection(registry, LOCAL_CONNECTION_ID).primary, LOCAL_CONNECTION_ID)
})
test('last-used source and launch mode validate their persisted values', () => {
let registry = emptyRegistry()
const entry = normalizeConnectionInput({ kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' }, registry)
registry = upsertConnection(registry, entry)
assert.throws(() => setLastUsedConnection(registry, 'ghost'), /No connection/)
assert.equal(setLastUsedConnection(registry, entry.id).lastUsed, entry.id)
assert.equal(setConnectionLaunchMode(registry, 'last-used').launchMode, 'last-used')
assert.throws(() => setConnectionLaunchMode(registry, 'sometimes'), /Unknown connection launch mode/)
})
test('upsertConnection replaces by id and appends new ids', () => {
let registry = emptyRegistry()
const a = normalizeConnectionInput({ kind: 'remote', label: 'A', url: 'http://a:1' }, registry)
registry = upsertConnection(registry, a)
registry = upsertConnection(registry, { ...a, url: 'http://a:2' })
assert.equal(registry.connections.filter(c => c.id === a.id).length, 1)
assert.equal(registry.connections.find(c => c.id === a.id)?.url, 'http://a:2')
})
test('Apply remote inserts into an existing local-only registry and becomes primary/current', () => {
const registry = reconcileAppliedGlobalConnection(emptyRegistry(), {
mode: 'remote',
remote: { url: 'https://gateway.example.com/', authMode: 'oauth' }
})
const remote = registry.connections.find(connection => connection.kind === 'remote')
assert.ok(remote)
assert.equal(registry.primary, remote.id)
assert.equal(registry.lastUsed, remote.id)
assert.equal(
resolvedConnectionId(registry, {
authMode: 'oauth',
baseUrl: 'https://gateway.example.com',
headers: {},
mode: 'remote',
remoteKind: 'url'
}),
remote.id
)
})
test('Apply remote preserves an existing URL identity and label without duplicates', () => {
let registry = emptyRegistry()
registry = upsertConnection(registry, {
id: 'hermes-alex',
kind: 'remote',
label: 'Existing gateway',
url: 'https://gateway.example.com',
authMode: 'token',
token: { old: true }
})
const applied = reconcileAppliedGlobalConnection(registry, {
mode: 'remote',
remote: { url: 'https://GATEWAY.example.com/', authMode: 'oauth' }
})
const matches = applied.connections.filter(connection => connection.url === 'https://gateway.example.com')
assert.equal(matches.length, 1)
assert.equal(matches[0].id, 'hermes-alex')
assert.equal(matches[0].label, 'Existing gateway')
assert.equal(matches[0].authMode, 'oauth')
assert.equal(applied.primary, 'hermes-alex')
assert.equal(applied.lastUsed, 'hermes-alex')
})
test('Apply local moves primary/current to This device without deleting registered remotes', () => {
const remoteRegistry = reconcileAppliedGlobalConnection(emptyRegistry(), {
mode: 'remote',
remote: { url: 'https://one.example.com', authMode: 'oauth' }
})
const localRegistry = reconcileAppliedGlobalConnection(remoteRegistry, { mode: 'local', remote: {} })
assert.equal(localRegistry.primary, LOCAL_CONNECTION_ID)
assert.equal(localRegistry.lastUsed, LOCAL_CONNECTION_ID)
assert.equal(localRegistry.connections.filter(connection => connection.kind === 'remote').length, 1)
assert.equal(resolvedConnectionId(localRegistry, { mode: 'local' }), LOCAL_CONNECTION_ID)
})
test('Apply between two remotes keeps each real registration once and activates the latest', () => {
const first = reconcileAppliedGlobalConnection(emptyRegistry(), {
mode: 'remote',
remote: { url: 'https://one.example.com', authMode: 'oauth' }
})
const second = reconcileAppliedGlobalConnection(first, {
mode: 'remote',
remote: { url: 'https://two.example.com/', authMode: 'oauth' }
})
const remotes = second.connections.filter(connection => connection.kind === 'remote')
assert.deepEqual(remotes.map(connection => connection.url).sort(), [
'https://one.example.com',
'https://two.example.com'
])
assert.equal(new Set(remotes.map(connection => connection.id)).size, 2)
assert.equal(second.primary, remotes.find(connection => connection.url === 'https://two.example.com')?.id)
assert.equal(second.lastUsed, second.primary)
})
// --- reconcileRegistryDrift (v1 ↔ v2 healing) ---
test('drift heal registers a v1 remote the registry never learned about and makes it primary', () => {
// The exact shape users keep reporting: registry migrated while local-only,
// then Settings → Gateway pointed v1 at a remote. connections.json still
// says primary 'local', so every launch force-switches off the live remote.
const drifted = reconcileRegistryDrift(emptyRegistry(), {
mode: 'remote',
remote: { url: 'https://agent.example.com:4443', authMode: 'oauth' }
})
assert.equal(drifted.changed, true)
const remote = drifted.registry.connections.find(connection => connection.kind === 'remote')
assert.ok(remote)
assert.equal(drifted.registry.primary, remote.id)
assert.equal(drifted.registry.lastUsed, remote.id)
// The whole point: the live v1 descriptor can now be named, so the boot pick
// resolves to the remote instead of re-homing to 'local'. Descriptor shape
// matches what buildRemoteConnection emits for an oauth remote.
assert.equal(
resolvedConnectionId(drifted.registry, {
authMode: 'oauth',
baseUrl: 'https://agent.example.com:4443',
headers: {},
mode: 'remote',
remoteKind: 'url'
}),
remote.id
)
})
test('drift heal leaves a registry that already knows the v1 route untouched', () => {
const registered = reconcileAppliedGlobalConnection(emptyRegistry(), {
mode: 'remote',
remote: { url: 'https://agent.example.com', authMode: 'oauth' }
})
const drifted = reconcileRegistryDrift(registered, {
mode: 'remote',
remote: { url: 'https://AGENT.example.com/', authMode: 'oauth' }
})
assert.equal(drifted.changed, false)
assert.equal(drifted.registry, registered)
})
test('drift heal respects a deliberate primary pick on a registered route', () => {
// Route IS registered, but the user chose This device in the Connections
// panel. That is a choice, not drift — never override it.
let registry = reconcileAppliedGlobalConnection(emptyRegistry(), {
mode: 'remote',
remote: { url: 'https://agent.example.com', authMode: 'oauth' }
})
registry = setPrimaryConnection(registry, LOCAL_CONNECTION_ID)
const drifted = reconcileRegistryDrift(registry, {
mode: 'remote',
remote: { url: 'https://agent.example.com', authMode: 'oauth' }
})
assert.equal(drifted.changed, false)
assert.equal(drifted.registry.primary, LOCAL_CONNECTION_ID)
})
test('drift heal ignores local and unparseable v1 routes', () => {
const registry = emptyRegistry()
for (const v1 of [
{ mode: 'local', remote: {} },
{ mode: 'ssh', remote: {} },
{ mode: 'ssh', remote: { host: ' ' } },
{ mode: 'remote', remote: { url: 'not a url' } },
{ mode: 'remote', remote: {} },
null
]) {
const drifted = reconcileRegistryDrift(registry, v1)
assert.equal(drifted.changed, false, `expected no heal for ${JSON.stringify(v1)}`)
assert.equal(drifted.registry, registry)
}
})
test('drift heal registers a v1 SSH route the registry never learned about and makes it primary', () => {
// mgallmur-glitch's shape: registry migrated while local-only, then Settings
// pointed v1 at an SSH host (host, no url). The registry cannot name it, so
// primary stays 'local' and the files re-drift after every update relaunch.
const drifted = reconcileRegistryDrift(emptyRegistry(), {
mode: 'ssh',
remote: { host: 'devbox.example.com', user: 'omar', port: 2222 }
})
assert.equal(drifted.changed, true)
const ssh = drifted.registry.connections.find(connection => connection.kind === 'ssh')
assert.ok(ssh)
assert.equal(ssh.host, 'devbox.example.com')
assert.equal(ssh.user, 'omar')
assert.equal(ssh.port, 2222)
assert.equal(drifted.registry.primary, ssh.id)
assert.equal(drifted.registry.lastUsed, ssh.id)
// The whole point: the live v1 SSH descriptor can now be named.
assert.equal(
resolvedConnectionId(drifted.registry, {
mode: 'remote',
remoteKind: 'ssh',
ssh: { host: 'devbox.example.com', user: 'omar', port: 2222 }
}),
ssh.id
)
})
test('drift heal leaves a registry that already knows the v1 SSH route untouched', () => {
const first = reconcileRegistryDrift(emptyRegistry(), {
mode: 'ssh',
remote: { host: 'devbox.example.com', user: 'omar' }
})
assert.equal(first.changed, true)
const drifted = reconcileRegistryDrift(first.registry, {
mode: 'ssh',
remote: { host: 'DEVBOX.example.com', user: 'Omar' }
})
assert.equal(drifted.changed, false)
assert.equal(drifted.registry, first.registry)
})
test('drift heal respects a deliberate primary pick on a registered SSH route', () => {
let registry = reconcileRegistryDrift(emptyRegistry(), {
mode: 'ssh',
remote: { host: 'devbox.example.com' }
}).registry
registry = setPrimaryConnection(registry, LOCAL_CONNECTION_ID)
const drifted = reconcileRegistryDrift(registry, {
mode: 'ssh',
remote: { host: 'devbox.example.com' }
})
assert.equal(drifted.changed, false)
assert.equal(drifted.registry.primary, LOCAL_CONNECTION_ID)
})
test('drift heal adds the missing SSH source without disturbing other registered sources', () => {
let registry = emptyRegistry()
registry = upsertConnection(registry, {
id: 'homelab',
kind: 'remote',
label: 'Homelab',
url: 'https://homelab.example.com',
authMode: 'token',
token: { keep: true }
})
const drifted = reconcileRegistryDrift(registry, {
mode: 'ssh',
remote: { host: 'devbox.example.com' }
})
assert.equal(drifted.changed, true)
assert.ok(drifted.registry.connections.some(connection => connection.id === 'homelab'))
assert.ok(drifted.registry.connections.some(connection => connection.kind === 'ssh'))
})
test('drift heal adds the missing remote without disturbing other registered sources', () => {
let registry = emptyRegistry()
registry = upsertConnection(registry, {
id: 'homelab',
kind: 'remote',
label: 'Homelab',
url: 'https://homelab.example.com',
authMode: 'token',
token: { keep: true }
})
const drifted = reconcileRegistryDrift(registry, {
mode: 'remote',
remote: { url: 'https://agent.example.com:4443', authMode: 'oauth' }
})
assert.equal(drifted.changed, true)
assert.equal(drifted.registry.connections.filter(connection => connection.kind === 'remote').length, 2)
assert.ok(drifted.registry.connections.some(connection => connection.id === 'homelab'))
})
// --- connectionDialFieldsChanged (edit → recycle decision) ---
test('connectionDialFieldsChanged: label-only edits do not recycle', () => {
const before = {
id: 'homelab',
kind: 'remote',
label: 'Homelab',
url: 'http://10.0.0.5:9119',
authMode: 'token',
token: { encoding: 'safeStorage', value: 'abc' }
} as const
assert.equal(connectionDialFieldsChanged(before, { ...before, label: 'Home lab (renamed)' }), false)
// Identity edit is also a no-op.
assert.equal(connectionDialFieldsChanged(before, { ...before }), false)
})
test('connectionDialFieldsChanged: url / auth / token changes recycle', () => {
const before = {
id: 'homelab',
kind: 'remote',
label: 'Homelab',
url: 'http://10.0.0.5:9119',
authMode: 'token',
token: { encoding: 'safeStorage', value: 'abc' }
} as const
assert.equal(connectionDialFieldsChanged(before, { ...before, url: 'http://10.0.0.9:9119' }), true)
assert.equal(connectionDialFieldsChanged(before, { ...before, authMode: 'oauth', token: undefined }), true)
assert.equal(
connectionDialFieldsChanged(before, { ...before, token: { encoding: 'safeStorage', value: 'NEW' } }),
true
)
})
test('connectionDialFieldsChanged: ssh routing fields recycle, kind change recycles', () => {
const before = { id: 'box', kind: 'ssh', label: 'Box', host: 'box.lan', user: 'me', port: 22 } as const
assert.equal(connectionDialFieldsChanged(before, { ...before, label: 'Box 2' }), false)
assert.equal(connectionDialFieldsChanged(before, { ...before, host: 'other.lan' }), true)
assert.equal(connectionDialFieldsChanged(before, { ...before, port: 2222 }), true)
assert.equal(connectionDialFieldsChanged(before, { ...before, remoteProfile: 'work' }), true)
assert.equal(
connectionDialFieldsChanged(before, { id: 'box', kind: 'remote', label: 'Box', url: 'http://x:1' }),
true
)
})
// --- remote gateway headers (Cloudflare Access etc., #74466 / PR #74468) ---
test('normalizeConnectionInput keeps filtered headers on remote/cloud, drops them elsewhere', () => {
const registry = emptyRegistry()
const remote = normalizeConnectionInput(
{
kind: 'remote',
label: 'CF box',
url: 'https://hermes.example.com',
authMode: 'token',
token: { enc: 'x' },
headers: {
'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'id' },
Authorization: { encoding: 'plain', value: 'blocked' }
}
},
registry
)
assert.deepEqual(remote.headers, {
'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'id' }
})
const ssh = normalizeConnectionInput(
{
kind: 'ssh',
label: 'Box',
host: 'box.lan',
headers: { 'CF-Access-Client-Id': { encoding: 'plain', value: 'id' } }
} as any,
registry
)
assert.equal((ssh as any).headers, undefined)
})
test('mergeConnectionInput inherits stored headers when the editor payload omits them', () => {
const stored = {
id: 'cf',
kind: 'remote' as const,
label: 'CF box',
url: 'https://hermes.example.com',
authMode: 'token' as const,
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'id' } }
}
const renamed = mergeConnectionInput({ id: 'cf', kind: 'remote', label: 'Renamed' }, stored)
assert.deepEqual(renamed.headers, stored.headers)
// An explicit headers payload (even empty) is authoritative — clearing works.
const cleared = mergeConnectionInput({ id: 'cf', kind: 'remote', label: 'CF box', headers: {} }, stored)
assert.deepEqual(cleared.headers, {})
})
test('connectionDialFieldsChanged: a header change recycles live backends', () => {
const before = {
id: 'cf',
kind: 'remote',
label: 'CF box',
url: 'https://hermes.example.com',
authMode: 'token',
token: { enc: 'x' },
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'id' } }
} as const
assert.equal(connectionDialFieldsChanged(before, { ...before }), false)
assert.equal(
connectionDialFieldsChanged(before, {
...before,
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'OTHER' } }
}),
true
)
assert.equal(connectionDialFieldsChanged(before, { ...before, headers: undefined }), true)
})
test('normalizeRegistry preserves stored headers on remote entries (v2 additive field)', () => {
const registry = normalizeRegistry({
version: REGISTRY_VERSION,
primary: 'cf',
connections: [
{ id: 'local', kind: 'local', label: 'This device' },
{
id: 'cf',
kind: 'remote',
label: 'CF box',
url: 'https://hermes.example.com',
authMode: 'token',
token: { enc: 'x' },
headers: {
'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'id' },
Cookie: { encoding: 'plain', value: 'blocked' }
}
}
]
})
const remote = registry.connections.find(c => c.id === 'cf')
assert.ok(remote)
assert.deepEqual(remote.headers, {
'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'id' }
})
})
test('migrateV1ToRegistry carries v1 remote headers into the registry entry', () => {
const registry = migrateV1ToRegistry({
mode: 'remote',
remote: {
url: 'https://hermes.example.com',
authMode: 'token',
token: { enc: 'x' },
headers: { 'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'id' } }
}
})
const remote = registry.connections.find(c => c.kind === 'remote')
assert.ok(remote)
assert.deepEqual(remote.headers, {
'CF-Access-Client-Id': { encoding: 'safeStorage', value: 'id' }
})
})
// --- normalizeRegistry per-entry quarantine (#94246 remainder) ---
//
// One malformed entry must never cost the user the rest of the registry, and
// malformed entries are USER DATA: they are preserved under `quarantined`
// (with the raw entry verbatim) instead of being silently deleted on the next
// registry write. "Only deleting connections.json recovers" was the reported
// failure shape; the recovery must never be data loss.
test('normalizeRegistry quarantines malformed entries instead of silently dropping them', () => {
const registry = normalizeRegistry({
version: 2,
primary: 'a',
connections: [
{ id: 'local', kind: 'local', label: 'This device' },
{ id: 'a', kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' },
{ id: 'c', kind: 'remote', label: 'No URL entry' },
{ kind: 'nonsense', label: 'Mystery box', extra: 'still my data' },
{ id: 's', kind: 'ssh', label: 'No host ssh' }
]
})
// Healthy entries all load.
assert.deepEqual(
registry.connections.map(c => c.id),
['local', 'a']
)
assert.equal(registry.primary, 'a')
// The malformed ones are preserved verbatim, with reasons.
assert.equal((registry.quarantined || []).length, 3)
const reasons = registry.quarantined!.map(q => q.reason).sort()
assert.deepEqual(reasons, ['entry-missing-ssh-host', 'entry-missing-url', 'entry-unrecognized-kind'])
const mystery = registry.quarantined!.find(q => q.reason === 'entry-unrecognized-kind')
assert.deepEqual(mystery!.entry, { kind: 'nonsense', label: 'Mystery box', extra: 'still my data' })
})
test('normalizeRegistry preserves previously quarantined entries across round trips', () => {
const first = normalizeRegistry({
version: 2,
connections: [{ id: 'c', kind: 'remote', label: 'No URL entry' }]
})
assert.equal((first.quarantined || []).length, 1)
// Simulate write → read → normalize again (what every registry save does).
const second = normalizeRegistry(JSON.parse(JSON.stringify(first)))
assert.equal((second.quarantined || []).length, 1)
assert.deepEqual(second.quarantined![0].entry, { id: 'c', kind: 'remote', label: 'No URL entry' })
})
test('normalizeRegistry quarantines an entry that explodes during normalization (no whole-load abort)', () => {
const poisoned: any = { id: 'boom', kind: 'remote', url: 'http://10.0.0.9:9119' }
Object.defineProperty(poisoned, 'label', {
enumerable: true,
get() {
throw new Error('poisoned entry')
}
})
const registry = normalizeRegistry({
version: 2,
primary: 'a',
connections: [poisoned, { id: 'a', kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' }]
})
// The healthy entry still loads and keeps primary; the poisoned one is
// quarantined rather than aborting the whole registry load.
assert.deepEqual(
registry.connections.filter(c => c.kind === 'remote').map(c => c.id),
['a']
)
assert.equal(registry.primary, 'a')
assert.equal((registry.quarantined || []).length, 1)
assert.equal(registry.quarantined![0].reason, 'entry-normalization-failed')
})
test('normalizeRegistry keeps a clean registry free of the quarantined key and caps quarantine growth', () => {
const clean = normalizeRegistry({
version: 2,
connections: [{ id: 'a', kind: 'remote', label: 'Homelab', url: 'http://10.0.0.5:9119' }]
})
assert.equal('quarantined' in clean, false)
const flooded = normalizeRegistry({
version: 2,
connections: Array.from({ length: 100 }, (_, i) => ({ id: `q${i}`, kind: 'remote', label: `No URL ${i}` }))
})
assert.ok((flooded.quarantined || []).length <= 20)
})
test('normalizeRegistry quarantines non-object junk items that could still be user data', () => {
const registry = normalizeRegistry({
version: 2,
connections: ['{ mangled json fragment }', null, false, { id: 'a', kind: 'remote', label: 'A', url: 'http://x:1' }]
})
assert.deepEqual(
registry.connections.map(c => c.kind),
['local', 'remote']
)
// null/false carry no data and are dropped; the string is preserved.
assert.equal((registry.quarantined || []).length, 1)
assert.equal(registry.quarantined![0].entry, '{ mangled json fragment }')
})