Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
d9b6d6416This file is pinned to the selected release. Download the original to open it in your design tool.
import http from 'node:http'
import type { AddressInfo } from 'node:net'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { WebSocket } from 'ws'
import type { Frame, LocalClientSink } from './backendSocket.js'
import { attachLocalWsServer, type LocalWsBackend, type LocalWsServer } from './localWsServer.js'
import { encodeTerminalLocal, TerminalBinaryKind, type TerminalBinaryClear } from './lib/terminalBinary.js'
const machineId = 'machine-123'
const streamId = '00112233-4455-6677-8899-aabbccddeeff'
class FakeBackend implements LocalWsBackend {
sink: LocalClientSink | null = null
connId: string | null = null
frames: Frame[] = []
binaries: TerminalBinaryClear[] = []
unregisters: string[] = []
registerLocalClient(connId: string, sink: LocalClientSink): boolean {
this.connId = connId
this.sink = sink
return true
}
async unregisterLocalClient(connId: string): Promise<void> {
this.unregisters.push(connId)
}
handleLocalFrame(_connId: string, frame: Frame): void {
this.frames.push(frame)
}
async handleLocalBinary(_connId: string, frame: TerminalBinaryClear): Promise<void> {
this.binaries.push(frame)
}
}
function onceOpen(ws: WebSocket): Promise<void> {
return new Promise((resolve, reject) => {
ws.once('open', () => resolve())
ws.once('error', reject)
})
}
function onceMessage(ws: WebSocket): Promise<Frame> {
return new Promise((resolve, reject) => {
ws.once('message', (raw) => {
try { resolve(JSON.parse(raw.toString()) as Frame) } catch (error) { reject(error) }
})
ws.once('error', reject)
})
}
describe('local CLI WebSocket', () => {
let server: http.Server | null = null
let local: LocalWsServer | null = null
afterEach(async () => {
await local?.close()
await new Promise<void>((resolve) => server?.close(() => resolve()) ?? resolve())
local = null
server = null
})
async function start(backend: FakeBackend): Promise<string> {
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, { machineId, backend })
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const port = (server.address() as AddressInfo).port
return `ws://127.0.0.1:${port}/api/local-ws`
}
it('accepts loopback, selects the exact machine, and routes JSON plus HTRL binary', async () => {
const backend = new FakeBackend()
const url = await start(backend)
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({
type: 'machine_select',
payload: { machineId, localProtocolVersion: 1 },
}))
await expect(connected).resolves.toMatchObject({
type: 'connected',
payload: { machineId, transport: 'local', e2ee: false },
})
ws.send(JSON.stringify({ type: 'agents_list', payload: { requestId: 'r1' } }))
const binary = encodeTerminalLocal({
kind: TerminalBinaryKind.input,
streamId,
seq: 1,
bytes: new TextEncoder().encode('hello'),
compressed: false,
})!
ws.send(binary)
await new Promise((resolve) => setTimeout(resolve, 20))
expect(backend.frames).toEqual([{ type: 'agents_list', payload: { requestId: 'r1' } }])
expect(backend.binaries[0]).toMatchObject({
kind: TerminalBinaryKind.input,
streamId,
seq: 1,
})
const reply = onceMessage(ws)
expect(backend.sink?.sendFrame({ type: 'agents_list_result', payload: { requestId: 'r1', agents: [] } })).toBe(true)
await expect(reply).resolves.toMatchObject({ type: 'agents_list_result' })
ws.close()
})
it('takes the window\'s tile roster, keeps it off the wire, and forgets it on close', async () => {
const backend = new FakeBackend()
const rosters: string[][] = []
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId,
backend,
onAppPanes: (ids) => rosters.push(ids),
})
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const url = `ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
ws.send(JSON.stringify({ type: 'app_panes', payload: { agentIds: ['a1', 'a2', '', 7, null] } }))
await new Promise((resolve) => setTimeout(resolve, 20))
// Anything that is not a usable id is dropped rather than trusted.
expect(rosters).toEqual([['a1', 'a2']])
// Like app_focus: it describes a screen at this desk, so the machine never sees it.
expect(backend.frames.map((frame) => frame.type)).toEqual([])
ws.close()
// A window that went away has no tiles open. Left standing, the roster would go on silencing the
// dial for agents nobody can see any more — backwards, and permanently.
await vi.waitFor(() => expect(rosters).toEqual([['a1', 'a2'], []]))
})
it('takes the window\'s swarms, drops what is not a swarm, and forgets them on close', async () => {
const backend = new FakeBackend()
const seen: unknown[] = []
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId,
backend,
onAppSwarms: (swarms) => seen.push(swarms),
})
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const url = `ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
ws.send(JSON.stringify({ type: 'app_swarms', payload: {
active: 's2',
swarms: [
{ id: 's1', name: 'Workshop', agentIds: ['a1', '', 7, 'a2'] },
{ id: '', name: 'no id' },
'junk',
{ id: 's2', name: 'Launch' },
],
} }))
await new Promise((resolve) => setTimeout(resolve, 20))
expect(seen).toEqual([{ active: 's2', swarms: [
{ id: 's1', name: 'Workshop', agentIds: ['a1', 'a2'] },
{ id: 's2', name: 'Launch', agentIds: [] },
] }])
// Like app_panes: a fact about this desk, so the machine never sees it.
expect(backend.frames.map((frame) => frame.type)).toEqual([])
ws.close()
// The window is gone, and so are its tabs.
await vi.waitFor(() => expect(seen).toHaveLength(2))
expect(seen[1]).toBeNull()
})
it('follows an explicit app_focus, and keeps it off the wire', async () => {
const backend = new FakeBackend()
const moves: Array<{ machineId: string; agentId: string }> = []
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId,
backend,
onAppFocus: (m, a) => moves.push({ machineId: m, agentId: a }),
})
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const url = `ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
// Opening a terminal still counts: that is all an older app build sends.
ws.send(JSON.stringify({ type: 'terminal_open', payload: { agentId: 'agent-opened' } }))
// And a window that MOVED without opening anything now says so — which is every click on a pane
// that already holds a live session, the case the dial used to miss entirely.
ws.send(JSON.stringify({ type: 'app_focus', payload: { agentId: 'agent-focused' } }))
await new Promise((resolve) => setTimeout(resolve, 20))
expect(moves).toEqual([
{ machineId, agentId: 'agent-opened' },
{ machineId, agentId: 'agent-focused' },
])
// Consumed locally: it describes a hand at this desk, and the machine has no use for an unknown
// frame type arriving on every pane click.
expect(backend.frames.map((frame) => frame.type)).toEqual(['terminal_open'])
ws.close()
})
it('reports only explicit voice focus and clears by originating connection', async () => {
const backend = new FakeBackend()
const states: Array<{ machine: string; agent: string | null; conn: string }> = []
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId, backend,
onAppFocusState: (machine, agent, conn) => states.push({ machine, agent, conn }),
})
await new Promise<void>(resolve => server!.listen(0, '127.0.0.1', resolve))
const ws = new WebSocket(`ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
ws.send(JSON.stringify({ type: 'terminal_open', payload: { agentId: 'background' } }))
ws.send(JSON.stringify({ type: 'app_focus', payload: { agentId: 'focused' } }))
ws.send(JSON.stringify({ type: 'terminal_open', payload: { agentId: 'another-background' } }))
await vi.waitFor(() => expect(states).toHaveLength(1))
expect(states[0]).toMatchObject({ machine: machineId, agent: 'focused' })
ws.send(JSON.stringify({ type: 'app_focus', payload: { agentId: null } }))
await vi.waitFor(() => expect(states).toHaveLength(2))
expect(states[1]).toEqual({ ...states[0], agent: null })
expect(backend.frames.map(frame => frame.type)).toEqual(['terminal_open', 'terminal_open'])
ws.close()
await vi.waitFor(() => expect(states).toHaveLength(3))
expect(states[2]).toEqual(states[1])
})
it('keeps stale automatic focus off the dial and forwards its revision for validation', async () => {
const backend = new FakeBackend()
const focus = vi.fn(() => false), dial = vi.fn()
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, { machineId, backend, onAppFocusState: focus, onAppFocus: dial })
await new Promise<void>(resolve => server!.listen(0, '127.0.0.1', resolve))
const ws = new WebSocket(`ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
ws.send(JSON.stringify({ type: 'app_focus', payload: { agentId: 'first', focusRevision: 'old:0' } }))
await vi.waitFor(() => expect(focus).toHaveBeenCalledTimes(1))
expect(focus).toHaveBeenCalledWith(machineId, 'first', expect.any(String), 'old:0')
expect(dial).not.toHaveBeenCalled()
expect(backend.frames).toHaveLength(0)
ws.close()
})
it('carries the window\'s answer about a spoken task, and keeps it off the wire', async () => {
const backend = new FakeBackend()
const replies: Array<{ voiceId: string; reply: unknown }> = []
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId,
backend,
onVoiceRouteReply: (voiceId, reply) => replies.push({ voiceId, reply }),
})
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const url = `ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
ws.send(JSON.stringify({ type: 'voice_route_reply', payload: { voiceId: 'v1', state: 'taken' } }))
ws.send(JSON.stringify({ type: 'voice_route_reply', payload: { voiceId: 'v1', state: 'sent', agentId: 'a7' } }))
ws.send(JSON.stringify({ type: 'voice_route_reply', payload: { voiceId: 'v2', state: 'cancelled' } }))
// Neither of these is an answer: one names no request, the other a state this side cannot read.
// Guessing at either would settle a spoken turn on something nobody said.
ws.send(JSON.stringify({ type: 'voice_route_reply', payload: { state: 'sent', agentId: 'a9' } }))
ws.send(JSON.stringify({ type: 'voice_route_reply', payload: { voiceId: 'v3', state: 'maybe' } }))
await new Promise((resolve) => setTimeout(resolve, 20))
expect(replies).toEqual([
{ voiceId: 'v1', reply: { t: 'taken' } },
{ voiceId: 'v1', reply: { t: 'sent', agentId: 'a7' } },
{ voiceId: 'v2', reply: { t: 'cancelled' } },
])
// It describes a hand at this desk. The machine has no use for it.
expect(backend.frames).toEqual([])
ws.close()
})
it('answers a routed task on the id it was asked with, and sends nothing', async () => {
const backend = new FakeBackend()
const asked: string[] = []
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId,
backend,
onRouteTask: async (text) => {
asked.push(text)
return {
agentId: 'a1', machineId: 'm-local', name: 'auth-api', confidence: 0.86, reason: 'name matches',
weighed: 4, machines: 2, via: 'model',
candidates: [{ agentId: 'a1', machineId: 'm-local', name: 'auth-api', machine: 'this computer', engine: 'claude', recent: 'token rotation', confidence: 0.86 }],
}
},
})
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const url = `ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
const answered = onceMessage(ws)
ws.send(JSON.stringify({ type: 'route_task', payload: { requestId: 'q-7', text: ' fix the retry ' } }))
const reply = await answered // onceMessage already parses
// The requestId comes back UNTOUCHED — it is the window's own rpc id, and the only thing tying this
// answer to the spinner it is holding open.
expect(reply.type).toBe('route_result')
expect(reply.payload).toMatchObject({ requestId: 'q-7', agentId: 'a1', machineId: 'm-local', name: 'auth-api', confidence: 0.86 })
expect(asked).toEqual(['fix the retry'])
// Asking is not sending. Nothing reached the machine.
expect(backend.frames.map((frame) => frame.type)).toEqual([])
ws.close()
})
it('still answers when there is nothing to answer with', async () => {
// The window holds a spinner on the id it asked with, so silence is the one reply it cannot recover
// from — an empty task and a router that throws must both come back as frames.
const backend = new FakeBackend()
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId,
backend,
onRouteTask: async () => { throw new Error('router unavailable') },
})
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const url = `ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
const blank = onceMessage(ws)
ws.send(JSON.stringify({ type: 'route_task', payload: { requestId: 'q-empty', text: ' ' } }))
expect((await blank).payload).toMatchObject({ requestId: 'q-empty', agentId: '', reason: 'empty task' })
const failed = onceMessage(ws)
ws.send(JSON.stringify({ type: 'route_task', payload: { requestId: 'q-throw', text: 'anything' } }))
expect((await failed).payload).toMatchObject({ requestId: 'q-throw', agentId: '', reason: 'router unavailable' })
ws.close()
})
it('delivers a committed route, and keeps the frame off the wire', async () => {
const backend = new FakeBackend()
const sent: Array<{ agentId: string; text: string }> = []
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId,
backend,
onRouteSend: (agentId, text) => {
sent.push({ agentId, text })
return { ok: true as const }
},
})
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const url = `ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
const delivered = onceMessage(ws)
ws.send(JSON.stringify({ type: 'route_send', payload: { requestId: 's-1', agentId: 'a2', text: 'do the thing' } }))
expect((await delivered).payload).toMatchObject({ requestId: 's-1', ok: true })
// The half-formed one is dropped rather than delivered to whoever sorts first — and it is REFUSED
// out loud, because the window is waiting on this id either way.
const refused = onceMessage(ws)
ws.send(JSON.stringify({ type: 'route_send', payload: { requestId: 's-2', agentId: '', text: 'nobody' } }))
expect((await refused).payload).toMatchObject({ requestId: 's-2', ok: false })
expect(sent).toEqual([{ agentId: 'a2', text: 'do the thing' }])
expect(backend.frames.map((frame) => frame.type)).toEqual([])
ws.close()
})
it('says so when the agent could not be delivered to', async () => {
// The remote leg carries no ack of its own: a machine that has gone deaf takes the turn and nothing
// comes back. With the palette closing silently on a confident route, that is a task that vanishes
// without a mark anywhere — so the refusal has to travel.
const backend = new FakeBackend()
server = http.createServer((_req, res) => { res.statusCode = 404; res.end() })
local = attachLocalWsServer(server, {
machineId,
backend,
onRouteSend: () => ({ ok: false as const, machine: 'mac-mini', reason: 'the last request to it did not come back' }),
})
await new Promise<void>((resolve) => server!.listen(0, '127.0.0.1', resolve))
const url = `ws://127.0.0.1:${(server.address() as AddressInfo).port}/api/local-ws`
const ws = new WebSocket(url)
await onceOpen(ws)
const connected = onceMessage(ws)
ws.send(JSON.stringify({ type: 'machine_select', payload: { machineId, localProtocolVersion: 1 } }))
await connected
const answered = onceMessage(ws)
ws.send(JSON.stringify({ type: 'route_send', payload: { requestId: 's-3', agentId: 'a9', text: 'work' } }))
expect((await answered).payload).toMatchObject({
requestId: 's-3', ok: false, machine: 'mac-mini', reason: 'the last request to it did not come back',
})
ws.close()
})
it('does not require a credential on the loopback transport', async () => {
const url = await start(new FakeBackend())
const ws = new WebSocket(url, ['legacy-client-label'])
await onceOpen(ws)
ws.close()
})
it('rejects browser origins and machine-id mismatches', async () => {
const url = await start(new FakeBackend())
const browser = new WebSocket(url, { origin: 'https://example.com' })
const error = await new Promise<Error>((resolve) => browser.once('error', resolve))
expect(error.message).toContain('403')
const ws = new WebSocket(url)
await onceOpen(ws)
const closed = new Promise<number>((resolve) => ws.once('close', resolve))
ws.send(JSON.stringify({
type: 'machine_select',
payload: { machineId: 'other-machine', localProtocolVersion: 1 },
}))
await expect(closed).resolves.toBe(4403)
})
})