Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
// `harness remote`: the picker on a fake tty, the loopback calls against a fake daemon socket, and the
// lines it says when something on the way refuses.
import { EventEmitter } from 'node:events'
import { PassThrough } from 'node:stream'
import { describe, expect, it, vi } from 'vitest'
import {
LoopbackClient, LoopbackError, handoffTerminal, openRemoteTerminal, orderForPicker, pickMachine, remoteCommand, remoteFailureLine,
type LoopbackSocket, type RemoteCommandDeps, type RemoteMachineChoice,
} from './remoteCommand.js'
const machines: RemoteMachineChoice[] = [
{ machineId: 'this-mac', label: 'MacbookPro.local', status: 'running', current: true },
{ machineId: 'box-1', label: 'machine-remote-1', status: 'offline', current: false },
{ machineId: 'box-2', label: 'machine-remote-2', status: 'running', current: false },
]
/** A tty for the picker: keys go in as the escape sequences a terminal sends, output is captured. */
function tty(): { input: RemoteCommandDeps['input']; output: RemoteCommandDeps['output']; written: () => string; press: (...keys: string[]) => void } {
const input = new PassThrough() as unknown as RemoteCommandDeps['input'] & { setRawMode: (raw: boolean) => void }
input.isTTY = true
input.setRawMode = vi.fn()
const chunks: string[] = []
const output = new PassThrough() as unknown as RemoteCommandDeps['output']
output.isTTY = true
output.on('data', (chunk: Buffer) => chunks.push(chunk.toString()))
const codes: Record<string, string> = { up: '\x1b[A', down: '\x1b[B', enter: '\r', esc: '\x1b', q: 'q', j: 'j', k: 'k', 'ctrl-c': '\x03' }
return {
input, output,
written: () => chunks.join(''),
press: (...keys) => { for (const key of keys) (input as unknown as PassThrough).write(codes[key] ?? key) },
}
}
/** The daemon's loopback end, as `harness remote` sees it: a socket per connection, scripted replies. */
class FakeDaemon {
readonly sent: Array<{ type: string; payload: Record<string, unknown> }> = []
constructor(private readonly answer: (socket: FakeSocket, frame: { type: string; payload: Record<string, unknown> }) => void) {}
connect = (url: string): LoopbackSocket => {
expect(url).toBe('ws://127.0.0.1:18473/api/local-ws')
const socket = new FakeSocket((frame) => { this.sent.push(frame); this.answer(socket, frame) })
setTimeout(() => socket.emit('open'), 0)
return socket
}
}
class FakeSocket extends EventEmitter implements LoopbackSocket {
closed = false
constructor(private readonly onSend: (frame: { type: string; payload: Record<string, unknown> }) => void) { super() }
send(data: string): void { this.onSend(JSON.parse(data) as { type: string; payload: Record<string, unknown> }) }
reply(type: string, payload: Record<string, unknown>): void { this.emit('message', JSON.stringify({ type, payload }), false) }
close(): void { this.closed = true; this.emit('close', 1000, '') }
}
/** The daemon as it behaves when everything works: selects, creates, hands off. */
function workingDaemon(windows = 1): FakeDaemon {
return new FakeDaemon((socket, frame) => {
if (frame.type === 'machine_select') socket.reply('connected', { machineId: frame.payload.machineId })
if (frame.type === 'agent_create') socket.reply('agent_create_result', { requestId: frame.payload.requestId, agent: { id: 'agent-remote-1', engine: frame.payload.engine } })
if (frame.type === 'remote_terminal_handoff') socket.reply('remote_terminal_handoff_result', { requestId: frame.payload.requestId, ok: true, fromAgentId: 'agent-local-7', windows })
})
}
function deps(overrides: Partial<RemoteCommandDeps> = {}): RemoteCommandDeps & { errors: string[]; io: ReturnType<typeof tty>; links: string[] } {
const io = tty()
const errors: string[] = []
const links: string[] = []
return {
tmuxPane: '%7',
localMachineId: 'this-mac',
port: 18473,
daemonRunning: () => true,
listMachines: async () => machines,
isLinked: () => true,
link: async (machineId, password) => { links.push(`${machineId}:${password}`); return { ok: true, fingerprint: 'AB:CD' } },
promptPassword: async () => 'hunter2',
input: io.input,
output: io.output,
error: (line) => errors.push(line),
connect: workingDaemon().connect,
...overrides,
errors,
io,
links,
}
}
describe('pickMachine', () => {
it('starts on the first online machine, says Online/Offline on every row, moves with the arrows, and takes Enter', async () => {
const io = tty()
const picked = pickMachine(machines.filter((m) => !m.current), io)
await new Promise((resolve) => setTimeout(resolve, 5))
expect(io.written()).toContain('❯ machine-remote-2 Online')
expect(io.written()).toContain(' machine-remote-1 Offline')
io.press('up')
await new Promise((resolve) => setTimeout(resolve, 5))
expect(io.written().split('❯ ').at(-1)).toMatch(/^machine-remote-1/)
io.press('down', 'enter')
expect(await picked).toEqual(machines[2])
})
it('an offline machine cannot be taken: Enter says so and the list stays', async () => {
const io = tty()
const picked = pickMachine(machines.filter((m) => !m.current), io)
await new Promise((resolve) => setTimeout(resolve, 5))
io.press('up', 'enter') // onto machine-remote-1 (offline)
await new Promise((resolve) => setTimeout(resolve, 5))
expect(io.written()).toContain('machine-remote-1 is offline. Run `harness start` there, or choose an online machine.')
io.press('down', 'enter')
expect(await picked).toEqual(machines[2])
})
it('wraps at both ends and also answers to j and k', async () => {
const io = tty()
const picked = pickMachine(machines.filter((m) => !m.current), io)
await new Promise((resolve) => setTimeout(resolve, 5))
io.press('j', 'j', 'k', 'k', 'k', 'k', 'enter') // 2→1→2→1→2→1→2
expect(await picked).toEqual(machines[2])
})
it('Esc, q and Ctrl-C leave with nothing chosen', async () => {
for (const key of ['esc', 'q', 'ctrl-c']) {
const io = tty()
const picked = pickMachine(machines.filter((m) => !m.current), io)
await new Promise((resolve) => setTimeout(resolve, 5))
io.press(key)
expect(await picked, key).toBeNull()
}
})
})
describe('orderForPicker', () => {
it('lists the online machines first, keeping each group in the order the account gave', () => {
const rows: RemoteMachineChoice[] = [
{ machineId: 'a', label: 'a', status: 'offline', current: false },
{ machineId: 'b', label: 'b', status: 'running', current: false },
{ machineId: 'c', label: 'c', status: 'offline', current: false },
{ machineId: 'd', label: 'd', status: 'online', current: false },
]
expect(orderForPicker(rows).map((row) => row.machineId)).toEqual(['b', 'd', 'a', 'c'])
})
})
describe('the loopback calls', () => {
it('openRemoteTerminal selects the machine and creates a terminal agent at home', async () => {
const daemon = workingDaemon()
expect(await openRemoteTerminal({ port: 18473, connect: daemon.connect }, 'box-2')).toBe('agent-remote-1')
expect(daemon.sent.map((frame) => frame.type)).toEqual(['machine_select', 'agent_create'])
expect(daemon.sent[0].payload).toEqual({ machineId: 'box-2', localProtocolVersion: 1 })
expect(daemon.sent[1].payload).toMatchObject({ engine: 'terminal', bypassPermission: false })
expect(daemon.sent[1].payload).not.toHaveProperty('cwd')
})
it('handoffTerminal selects this machine and names the pane', async () => {
const daemon = workingDaemon(2)
const result = await handoffTerminal({ port: 18473, connect: daemon.connect }, 'this-mac', { tmuxPane: '%7', machineId: 'box-2', agentId: 'agent-remote-1' })
expect(result).toEqual({ fromAgentId: 'agent-local-7', windows: 2 })
expect(daemon.sent[0].payload.machineId).toBe('this-mac')
expect(daemon.sent[1].payload).toMatchObject({ tmuxPane: '%7', machineId: 'box-2', agentId: 'agent-remote-1' })
})
it('a machine the daemon closes on with 4404 is NO_PEER_LINK; a reply with error is thrown by code', async () => {
const unlinked = new FakeDaemon((socket, frame) => { if (frame.type === 'machine_select') { socket.emit('close', 4404, 'NO_PEER_LINK') } })
await expect(openRemoteTerminal({ port: 18473, connect: unlinked.connect }, 'box-1')).rejects.toMatchObject({ code: 'NO_PEER_LINK' })
const refusing = new FakeDaemon((socket, frame) => {
if (frame.type === 'machine_select') socket.reply('connected', {})
if (frame.type === 'agent_create') socket.reply('agent_create_result', { requestId: frame.payload.requestId, error: 'INVALID_ENGINE', detail: 'no' })
})
await expect(openRemoteTerminal({ port: 18473, connect: refusing.connect }, 'box-1')).rejects.toMatchObject({ code: 'INVALID_ENGINE', message: 'no' })
})
it('a daemon that never answers is a TIMEOUT, not a hang', async () => {
const silent = new FakeDaemon(() => {})
const client = new LoopbackClient('ws://127.0.0.1:18473/api/local-ws', silent.connect)
await expect(client.select('box-1', 30)).rejects.toMatchObject({ code: 'TIMEOUT' })
client.close()
})
})
describe('remoteCommand', () => {
it('outside a Harness tile it says where it works and does nothing', async () => {
for (const tmuxPane of [undefined, '', '%x']) {
const d = deps({ tmuxPane })
expect(await remoteCommand(d)).toBe(1)
expect(d.errors[0]).toContain('inside a Harness terminal tile')
expect(d.io.written()).toBe('')
}
})
it('needs the daemon, a sign-in, and another machine', async () => {
const down = deps({ daemonRunning: () => false })
expect(await remoteCommand(down)).toBe(1)
expect(down.errors[0]).toContain('harness start')
const signedOut = deps({ localMachineId: null })
expect(await remoteCommand(signedOut)).toBe(1)
expect(signedOut.errors[0]).toContain('harness login')
const alone = deps({ listMachines: async () => [machines[0]] })
expect(await remoteCommand(alone)).toBe(1)
expect(alone.errors[0]).toContain('No other machines')
const allOffline = deps({ listMachines: async () => [machines[0], machines[1]] })
expect(await remoteCommand(allOffline)).toBe(1)
expect(allOffline.errors[0]).toContain('No other machine is online right now (machine-remote-1)')
})
it('a machine not linked yet is linked first, with its remote password', async () => {
const daemon = workingDaemon()
const d = deps({ connect: daemon.connect, isLinked: () => false })
const run = remoteCommand(d)
await new Promise((resolve) => setTimeout(resolve, 5))
d.io.press('enter')
expect(await run).toBe(0)
expect(d.links).toEqual(['box-2:hunter2'])
expect(d.io.written()).toContain('machine-remote-2 is not linked to this computer yet.')
expect(d.io.written()).toContain('✓ Linked machine-remote-2 (fingerprint AB:CD)')
expect(d.io.written()).toContain('Terminal opened on machine-remote-2. Switching this tile…')
})
it('an empty password, or a link the other machine refuses, stops before anything opens', async () => {
const daemon = workingDaemon()
const empty = deps({ connect: daemon.connect, isLinked: () => false, promptPassword: async () => ' ' })
const emptyRun = remoteCommand(empty)
await new Promise((resolve) => setTimeout(resolve, 5))
empty.io.press('enter')
expect(await emptyRun).toBe(1)
expect(empty.errors[0]).toContain('No password entered')
expect(daemon.sent).toEqual([])
const refused = deps({ connect: daemon.connect, isLinked: () => false, link: async () => ({ ok: false, message: 'Wrong password for machine-remote-2.' }) })
const refusedRun = remoteCommand(refused)
await new Promise((resolve) => setTimeout(resolve, 5))
refused.io.press('enter')
expect(await refusedRun).toBe(1)
expect(refused.errors[0]).toContain('Wrong password')
expect(daemon.sent).toEqual([])
})
it('picks, opens the terminal there, hands the tile over, and says so', async () => {
const daemon = workingDaemon()
const d = deps({ connect: daemon.connect })
const run = remoteCommand(d)
await new Promise((resolve) => setTimeout(resolve, 5))
d.io.press('enter') // the first online one: machine-remote-2
expect(await run).toBe(0)
expect(d.errors).toEqual([])
expect(d.io.written()).toContain('Opening a terminal on machine-remote-2')
expect(d.io.written()).toContain('Terminal opened on machine-remote-2. Switching this tile…')
expect(daemon.sent.map((frame) => frame.type)).toEqual(['machine_select', 'agent_create', 'machine_select', 'remote_terminal_handoff'])
})
it('cancelling is exit 0 and no call', async () => {
const daemon = workingDaemon()
const d = deps({ connect: daemon.connect })
const run = remoteCommand(d)
await new Promise((resolve) => setTimeout(resolve, 5))
d.io.press('esc')
expect(await run).toBe(0)
expect(d.io.written()).toContain('Cancelled.')
expect(daemon.sent).toEqual([])
})
it('with no window listening the terminal is still open, and it says where to find it', async () => {
const d = deps({ connect: workingDaemon(0).connect })
const run = remoteCommand(d)
await new Promise((resolve) => setTimeout(resolve, 5))
d.io.press('enter')
expect(await run).toBe(0)
expect(d.io.written()).toContain('No Harness window is open here')
})
it('a pin the daemon does not accept is linked again, once, and the open retried', async () => {
let dials = 0
const daemon = new FakeDaemon((socket, frame) => {
if (frame.type === 'machine_select' && frame.payload.machineId === 'box-2' && ++dials === 1) { socket.emit('close', 4404, 'NO_PEER_LINK'); return }
if (frame.type === 'machine_select') socket.reply('connected', {})
if (frame.type === 'agent_create') socket.reply('agent_create_result', { requestId: frame.payload.requestId, agent: { id: 'agent-remote-1' } })
if (frame.type === 'remote_terminal_handoff') socket.reply('remote_terminal_handoff_result', { requestId: frame.payload.requestId, ok: true, fromAgentId: 'a', windows: 1 })
})
const d = deps({ connect: daemon.connect }) // isLinked says yes; the daemon disagrees
const run = remoteCommand(d)
await new Promise((resolve) => setTimeout(resolve, 5))
d.io.press('enter')
expect(await run).toBe(0)
expect(d.links).toEqual(['box-2:hunter2'])
expect(d.io.written()).toContain('Terminal opened on machine-remote-2. Switching this tile…')
// And when the second link is refused too, the command says what to run.
const stubborn = new FakeDaemon((socket, frame) => { if (frame.type === 'machine_select') socket.emit('close', 4404, 'NO_PEER_LINK') })
const e = deps({ connect: stubborn.connect })
const eRun = remoteCommand(e)
await new Promise((resolve) => setTimeout(resolve, 5))
e.io.press('enter')
expect(await eRun).toBe(1)
expect(e.errors[0]).toContain('harness link connect box-2')
})
it('remoteFailureLine puts the daemon\'s codes into words', () => {
const box = machines[1]
expect(remoteFailureLine(new LoopbackError('NOT_A_HARNESS_PANE'), box)).toContain('inside a Harness terminal')
expect(remoteFailureLine(new LoopbackError('DAEMON_UNREACHABLE', 'ECONNREFUSED'), box)).toContain('harness start')
expect(remoteFailureLine(new LoopbackError('WEIRD', 'because'), box)).toBe('WEIRD: because')
expect(remoteFailureLine(new Error('plain'), box)).toBe('plain')
})
})
890dee055This file is pinned to the selected release. Download the original to open it in your design tool.