fix(cli): preserve unknown SSH connection state

This commit is contained in:
Neil
2026-09-05 14:28:06 -07:00
parent de6e3ac244
commit fa5d21be41
4 changed files with 51 additions and 2 deletions
+3
View File
@@ -245,6 +245,9 @@ function formatHostConnection(host: HostListEntry): string {
if (host.kind !== 'ssh') {
return ''
}
if (host.connected === undefined) {
return `connection unknown${host.connectionStatus ? ` (${host.connectionStatus})` : ''}`
}
return host.connected
? `connected${host.connectionStatus ? ` (${host.connectionStatus})` : ''}`
: `not connected${host.connectionStatus ? ` (${host.connectionStatus})` : ''}`
+1 -1
View File
@@ -51,7 +51,7 @@ export const ENVIRONMENT_HANDLERS: Record<string, CommandHandler> = {
name: target.label,
id: target.id,
selector: `--host ssh:${target.id}`,
connected: target.connected ?? false,
...(target.connected === undefined ? {} : { connected: target.connected }),
...(target.connectionStatus ? { connectionStatus: target.connectionStatus } : {}),
...(target.remotePlatform ? { platform: target.remotePlatform } : {})
}))
@@ -118,6 +118,24 @@ describe('listSshTargets', () => {
expect(call).toHaveBeenCalledWith('ssh.listTargets')
})
it('enriches legacy target rows from host-owned connection state', async () => {
const { RuntimeClientError } = await import('./runtime/types.js')
const call = vi.fn(async (method: string) => {
if (method === 'ssh.listTargetSummaries') {
throw new RuntimeClientError('method_not_found', 'Unknown method')
}
if (method === 'ssh.getState') {
return { result: { state: { status: 'connected', remotePlatform: 'win32' } } }
}
return { result: { targets: SSH_TARGETS } }
})
await expect(listSshTargets({ call } as unknown as RuntimeClient)).resolves.toEqual([
{ ...SSH_TARGETS[0], connected: true, connectionStatus: 'connected', remotePlatform: 'win32' }
])
expect(call).toHaveBeenCalledWith('ssh.getState', { targetId: SSH_TARGETS[0].id })
})
// Why: this only ever runs to enrich an error we are already reporting; a failure here must
// not replace that error with a confusing one about SSH enumeration.
it('returns nothing rather than masking the error it was enriching', async () => {
+29 -1
View File
@@ -112,7 +112,7 @@ export async function listSshTargets(client: RuntimeClient): Promise<SshTargetSu
if (error instanceof Error && 'code' in error && error.code === 'method_not_found') {
try {
const legacy = await client.call<{ targets: SshTargetSummary[] }>('ssh.listTargets')
return legacy.result.targets
return await enrichLegacySshTargetStates(client, legacy.result.targets)
} catch {
return []
}
@@ -121,6 +121,34 @@ export async function listSshTargets(client: RuntimeClient): Promise<SshTargetSu
}
}
async function enrichLegacySshTargetStates(
client: RuntimeClient,
targets: SshTargetSummary[]
): Promise<SshTargetSummary[]> {
return Promise.all(
targets.map(async (target) => {
try {
const response = await client.call<{
state: {
status?: string
remotePlatform?: 'linux' | 'darwin' | 'win32'
} | null
}>('ssh.getState', { targetId: target.id })
const state = response.result.state
return {
...target,
...(state?.status === undefined
? {}
: { connected: state.status === 'connected', connectionStatus: state.status }),
...(state?.remotePlatform === undefined ? {} : { remotePlatform: state.remotePlatform })
}
} catch {
return target
}
})
)
}
// Why: `--host ssh:<id>` was never validated, so an unknown target answered ok:true with an
// empty list — the same silent wrong-machine answer that `runtime:` ids used to give. And since
// target ids are machine-generated (`ssh-<timestamp>-<random>`), the label a caller actually