Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
/**
* P4/P5 — the control interface (pair/control.ts): each tool maps to the right owner call or sealed RPC,
* write tools are refused without the pair harness's token, and the autonomy matrix decides what runs,
* what waits for a key, and what is refused.
*/
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { mkdtempSync, readFileSync, rmSync, statSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { CONTROL_TOOLS, PairControl, PROPOSAL_TTL_MS, PROPOSALS_MAX, StartedHarnesses, type ControlDeps } from './control.js'
import { PairToken } from './token.js'
import type { Autonomy } from './floor.js'
import type { DaemonSay } from './protocol.js'
type Result = Record<string, unknown>
/** An allow-class permission prompt on another machine, as its owner reports it (pair_read). */
const remoteQuestion = { requestId: 'q9', text: 'Approve Bash command: npm test', options: ['1. Yes', '2. No'], multi: false, deny: false, allow: true, permission: true, since: 0,
dialog: 'Bash command\n\n npm test\n\nDo you want to proceed?\n1. Yes\n2. No' }
let dir: string
beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'pair-control-')); vi.useFakeTimers({ now: 1_000_000 }) })
afterEach(() => { vi.useRealTimers(); rmSync(dir, { recursive: true, force: true }) })
function world(opts: { autonomy?: Autonomy; present?: boolean; fleet?: boolean } = {}) {
let autonomy: Autonomy = opts.autonomy ?? 'suggest'
const token = new PairToken(join(dir, 'token'))
const secret = token.rotate()
const owner = {
list: vi.fn(() => [{ agentId: 'api', name: 'api', engine: 'claude', status: 'idle' as const }]),
read: vi.fn((agentId: string): { ok: true; [key: string]: unknown } => ({ ok: true as const, harness: null, row: { agentId, name: agentId, engine: 'claude', status: 'idle' } })),
answer: vi.fn(async () => ({ ok: true as const, option: '1. Yes' })),
send: vi.fn(() => ({ ok: true as const, deliveryId: 'd1' })),
stop: vi.fn(() => ({ ok: true as const })),
start: vi.fn(async () => ({ ok: true as const, agentId: 'new-1' })),
pause: vi.fn(async () => ({ ok: true as const })),
resume: vi.fn(async () => ({ ok: true as const })),
}
const requests: Array<{ machineId: string; type: string; payload: Result }> = []
const fleet = {
isRunning: opts.fleet !== false,
machines: () => [
{ machineId: 'machine-a', name: 'desk', status: 'ok' as const, local: true },
{ machineId: 'machine-b', name: 'laptop', status: 'ok' as const, local: false },
{ machineId: 'machine-c', name: 'mini', status: 'unreachable' as const, local: false },
],
harnesses: () => [],
request: vi.fn(async (machineId: string, type: string, payload: Result): Promise<Result> => {
requests.push({ machineId, type, payload })
if (type === 'pair_list') return { harnesses: [{ agentId: 'web', name: 'web', engine: 'codex', status: 'waiting' }] }
if (type === 'pair_read') return { ok: true, harness: null, row: { agentId: payload.agentId, name: 'web', engine: 'codex', status: 'waiting', question: remoteQuestion } }
if (type === 'pair_start') return { ok: true, agentId: 'remote-new' }
return { ok: true }
}),
journals: vi.fn(async () => [{ machineId: 'machine-a', machine: 'desk', local: true, entries: [
{ epoch: 'e', seq: 1, at: 999_000, kind: 'done' as const, agentId: 'api', name: 'api', engine: 'claude' },
{ epoch: 'e', seq: 2, at: 999_500, kind: 'act' as const, agentId: 'api', name: 'api', engine: 'claude', by: 'rule' as const, action: 'answer' as const, text: 'answered "Yes"' },
] }]),
}
const said: DaemonSay[] = []
const unsaid: Array<{ id: string; reason: string }> = []
let seq = 0
const deps: ControlDeps = {
owner, fleet: fleet as unknown as ControlDeps['fleet'],
local: { machineId: () => 'machine-a', name: () => 'desk', journal: () => ({ epoch: 'e', seq: 0, entries: [] }), harnesses: () => [] },
pairing: { enabled: () => true, pairedDaemon: () => 'tim' },
autonomy: () => autonomy,
tokenMatches: (candidate) => token.matches(candidate),
voice: { say: (say) => { said.push(say); return true }, unsay: (id, reason) => { unsaid.push({ id, reason }); return true } },
present: () => opts.present !== false,
started: new StartedHarnesses(join(dir, 'started.json')),
now: Date.now,
newId: () => `id${++seq}`,
}
const control = new PairControl(deps)
const call = (verb: string, args: Result = {}, withToken = true): Promise<Result> =>
control.local({ verb, ...args, ...(withToken ? { token: secret } : {}) })
return { control, call, owner, fleet, requests, said, unsaid, deps, setAutonomy: (a: Autonomy) => { autonomy = a } }
}
describe('each tool maps to the right call', () => {
it('lists the BRAIN.md table, and nothing that deletes, restarts, forks or bypasses', () => {
expect(CONTROL_TOOLS.map((t) => t.name)).toEqual(['list_machines', 'list_harnesses', 'read_harness', 'brief', 'answer_question',
'send_prompt', 'stop_turn', 'start_harness', 'pause_harness', 'resume_harness', 'say'])
expect(CONTROL_TOOLS.map((t) => t.name).join(' ')).not.toMatch(/delete|restart|fork|bypass/i)
const start = CONTROL_TOOLS.find((t) => t.name === 'start_harness')!
expect(Object.keys((start.input as { properties: object }).properties)).not.toContain('bypassPermission')
})
it('reads: this machine from its owner, another over sealed pair_list / pair_read', async () => {
const w = world()
expect(await w.call('list_machines', {}, false)).toMatchObject({ ok: true, machines: expect.arrayContaining([expect.objectContaining({ machineId: 'machine-b' })]) })
const all = await w.call('list_harnesses', {}, false)
expect(all.machines).toEqual([
{ machineId: 'machine-a', machine: 'desk', harnesses: [expect.objectContaining({ agentId: 'api' })] },
{ machineId: 'machine-b', machine: 'laptop', harnesses: [expect.objectContaining({ agentId: 'web', status: 'waiting' })] },
{ machineId: 'machine-c', machine: 'mini', error: 'MACHINE_UNREACHABLE' },
])
await w.call('read_harness', { agentId: 'api' }, false)
expect(w.owner.read).toHaveBeenCalledWith('api')
await w.call('read_harness', { agentId: 'web', machineId: 'machine-b' }, false)
expect(w.requests.at(-1)).toMatchObject({ machineId: 'machine-b', type: 'pair_read', payload: { agentId: 'web' } })
const brief = await w.call('brief', { sinceMinutes: 30 }, false)
expect(brief).toMatchObject({ ok: true, sinceMinutes: 30, acted: [expect.objectContaining({ by: 'rule', action: 'answer' })] })
expect(w.fleet.journals).toHaveBeenCalledWith(1_000_000 - 30 * 60_000, 3_000)
})
it('a read tool answers the pair harness — a model — with secrets taken out, ids and folders kept', async () => {
const w = world()
const token = 'sk-ant-api03-abcdefghijklmnopqrstuvwxyz0123456789'
w.owner.read.mockImplementation((agentId: string) => ({ ok: true as const, harness: null, row: { agentId, name: 'api', engine: 'claude', status: 'waiting', cwd: '/w/api',
question: { requestId: 'q1', text: `export ANTHROPIC_API_KEY=${token}`, options: ['1. Yes', '2. No'], multi: false, deny: false, allow: false, permission: true, since: 0, dialog: `Bash command\n\n export ANTHROPIC_API_KEY=${token}` } },
recaps: [`used ${token}`], asks: [] }))
const read = await w.call('read_harness', { agentId: 'api' }, false)
expect(JSON.stringify(read)).not.toContain(token)
expect(read).toMatchObject({ ok: true, row: { agentId: 'api', cwd: '/w/api', question: { requestId: 'q1' } } })
})
it('writes on this machine go to its owner as `pair`; on another as the matching sealed request', async () => {
const w = world({ autonomy: 'act-on-key' })
// Started by the pair, so it may drive them without a key.
w.deps.started.add('machine-a', 'api')
w.deps.started.add('machine-b', 'web')
const local: Array<[string, Result, string, unknown[]]> = [
['answer_question', { agentId: 'api', requestId: 'q1', choice: 'Yes' }, 'answer', [{ agentId: 'api', requestId: 'q1', choice: 'Yes' }, 'pair']],
['send_prompt', { agentId: 'api', text: 'run it' }, 'send', [{ agentId: 'api', text: 'run it' }, 'pair']],
['stop_turn', { agentId: 'api' }, 'stop', [{ agentId: 'api' }, 'pair']],
['pause_harness', { agentId: 'api' }, 'pause', [{ agentId: 'api' }, 'pair']],
['resume_harness', { agentId: 'api' }, 'resume', [{ agentId: 'api' }, 'pair']],
]
for (const [verb, args, method, expected] of local) {
expect(await w.call(verb, args)).toMatchObject({ ok: true, machineId: 'machine-a' })
expect(w.owner[method as 'answer']).toHaveBeenLastCalledWith(...expected)
}
// Another machine: an answer only, sealed, and without a `by` — that machine decides who asked.
expect(await w.call('answer_question', { agentId: 'web', requestId: 'q9', choice: 'No', machineId: 'machine-b' })).toMatchObject({ ok: true, machineId: 'machine-b' })
expect(w.requests.at(-1)).toEqual({ machineId: 'machine-b', type: 'pair_answer', payload: { agentId: 'web', expectRequestId: 'q9', choice: 'No' } })
const before = w.requests.length
for (const verb of ['send_prompt', 'stop_turn', 'pause_harness', 'resume_harness', 'start_harness']) {
expect(await w.call(verb, { agentId: 'web', text: 'go', engine: 'codex', cwd: '/w', machineId: 'machine-b' })).toMatchObject({ ok: false, error: 'REMOTE_ANSWERS_ONLY' })
}
expect(w.requests.length).toBe(before)
})
})
describe('the token', () => {
it('refuses every write without the pair harness\'s token, or with an old one', async () => {
const w = world({ autonomy: 'act-on-key' })
w.deps.started.add('machine-a', 'api')
for (const tool of CONTROL_TOOLS.filter((t) => t.kind === 'write')) {
expect(await w.call(tool.name, { agentId: 'api', requestId: 'q', choice: 'Yes', text: 'x', engine: 'codex', cwd: '/w' }, false))
.toMatchObject({ ok: false, error: 'TOKEN_REQUIRED' })
}
const old = new PairToken(join(dir, 'token')).rotate() // a new launch: the token the old one held is dead
expect(await w.control.local({ verb: 'stop_turn', agentId: 'api', token: 'f'.repeat(64) })).toMatchObject({ error: 'TOKEN_REQUIRED' })
expect(old).toMatch(/^[a-f0-9]{64}$/)
expect(w.owner.stop).not.toHaveBeenCalled()
// Reads never need it.
expect(await w.call('list_harnesses', {}, false)).toMatchObject({ ok: true })
})
it('is kept 0600 and read back by a daemon that restarts', () => {
const token = new PairToken(join(dir, 'pair', 'token'))
expect(token.launched).toBe(false)
expect(token.matches('')).toBe(false)
const value = token.rotate()
expect(statSync(join(dir, 'pair', 'token')).mode & 0o777).toBe(0o600)
expect(readFileSync(join(dir, 'pair', 'token'), 'utf8').trim()).toBe(value)
expect(new PairToken(join(dir, 'pair', 'token')).matches(value)).toBe(true)
})
})
describe('the autonomy matrix', () => {
const write = { agentId: 'api', text: 'run the tests' }
it('watch: reads and say only; every write refused', async () => {
const w = world({ autonomy: 'watch' })
w.deps.started.add('machine-a', 'api')
expect(await w.call('send_prompt', write)).toMatchObject({ ok: false, error: 'AUTONOMY_WATCH' })
expect(await w.call('list_harnesses')).toMatchObject({ ok: true })
expect(await w.call('say', { line: 'api is done.' })).toEqual({ ok: true })
expect(w.owner.send).not.toHaveBeenCalled()
expect(w.said.map((s) => s.mood)).toEqual(['say'])
})
it('suggest: every write waits for the person\'s key, even on a harness it started', async () => {
const w = world({ autonomy: 'suggest' })
w.deps.started.add('machine-a', 'api')
const proposed = await w.call('send_prompt', write)
expect(proposed).toMatchObject({ ok: true, proposed: true })
expect(w.owner.send).not.toHaveBeenCalled()
expect(w.said).toEqual([expect.objectContaining({ id: proposed.id, mood: 'ask', from: 'pair', line: '[y/n] send api a prompt (13 chars)?',
detail: 'run the tests', harness: { machineId: 'machine-a', machine: 'desk', agentId: 'api', name: 'api' },
actions: [{ key: 'y', label: 'do it', choice: 'y' }, { key: 'n', label: 'skip', choice: 'n' }] })])
expect(w.control.owns(String(proposed.id))).toBe(true)
expect(await w.control.act(String(proposed.id), 'y')).toMatchObject({ ok: true, results: [expect.objectContaining({ ok: true, verb: 'send_prompt' })] })
// Run as the person's key, not as the pair.
expect(w.owner.send).toHaveBeenCalledWith({ agentId: 'api', text: 'run the tests' }, 'key')
expect(w.unsaid).toEqual([{ id: proposed.id, reason: 'answered' }])
expect(await w.control.act(String(proposed.id), 'y')).toMatchObject({ ok: false, error: 'GONE' })
})
it('suggest: `n` drops it, and a proposal nobody answered expires', async () => {
const w = world({ autonomy: 'suggest' })
const first = await w.call('stop_turn', { agentId: 'api' })
expect(await w.control.act(String(first.id), 'n')).toEqual({ ok: true, declined: 1 })
const second = await w.call('stop_turn', { agentId: 'api' })
vi.advanceTimersByTime(PROPOSAL_TTL_MS)
expect(await w.control.act(String(second.id), 'y')).toMatchObject({ ok: false, error: 'GONE' })
expect(w.owner.stop).not.toHaveBeenCalled()
})
it('act-on-key: drives a harness it started; everything else is its own proposal, one key each — never a batch', async () => {
const w = world({ autonomy: 'act-on-key' })
// Starting is never "its own": it waits for the key, then the new harness is its own to drive.
const start = await w.call('start_harness', { engine: 'codex', cwd: '/w/api', prompt: 'add a test for the login redirect' })
expect(start).toMatchObject({ proposed: true })
expect(w.said.at(-1)).toMatchObject({ line: '[y/n] start codex in api?', detail: 'start codex (mode ask) on desk\nfolder: /w/api\n\nfirst prompt:\nadd a test for the login redirect' })
expect(await w.control.act(String(start.id), 'y')).toMatchObject({ ok: true })
expect(w.owner.start).toHaveBeenCalledWith({ engine: 'codex', cwd: '/w/api', prompt: 'add a test for the login redirect', name: null }, 'key')
expect(await w.call('send_prompt', { agentId: 'new-1', text: 'now the docs' })).toMatchObject({ ok: true, deliveryId: 'd1' })
expect(w.owner.send).toHaveBeenLastCalledWith({ agentId: 'new-1', text: 'now the docs' }, 'pair')
const one = await w.call('stop_turn', { agentId: 'api' })
const two = await w.call('pause_harness', { agentId: 'web' })
expect(one.id).not.toBe(two.id)
expect(w.control.pending()).toEqual([
expect.objectContaining({ id: one.id, line: "[y/n] stop api's turn?", verb: 'stop_turn', from: 'pair', detail: 'stop the turn api is working on, on desk' }),
expect.objectContaining({ id: two.id, line: '[y/n] pause web?', verb: 'pause_harness', harness: expect.objectContaining({ name: 'web' }) }),
])
// One key runs one proposal.
expect(await w.control.act(String(one.id), 'y')).toMatchObject({ ok: true, results: [expect.objectContaining({ verb: 'stop_turn' })] })
expect(w.owner.stop).toHaveBeenCalledWith({ agentId: 'api' }, 'key')
expect(w.owner.pause).not.toHaveBeenCalled()
expect(w.control.pending().map((p) => p.id)).toEqual([two.id])
})
it('an answer proposal shows the WHOLE dialog and the option, and is refused now if the floor would refuse it', async () => {
const w = world({ autonomy: 'suggest' })
const dialog = `Bash command\n\n npm test -- --reporter=verbose --testNamePattern="a very long pattern that goes on and on past sixty characters"\n\nDo you want to proceed?\n1. Yes\n2. No`
const question = { requestId: 'q1', text: 'Approve Bash command: npm test', options: ['1. Yes', '2. No'], multi: false, deny: false, allow: true, permission: true, since: 0, dialog }
w.owner.read.mockImplementation((agentId: string) => ({ ok: true as const, harness: null, row: { agentId, name: 'api', engine: 'claude', status: 'waiting', question } }))
const proposed = await w.call('answer_question', { agentId: 'api', requestId: 'q1', choice: 'yes' })
expect(proposed).toMatchObject({ ok: true, proposed: true })
expect(w.said.at(-1)).toMatchObject({ line: '[y/n] answer api: "Yes"?', detail: `${dialog}\n\nanswer: 1. Yes` })
await w.control.act(String(proposed.id), 'y')
expect(w.owner.answer).toHaveBeenCalledWith({ agentId: 'api', requestId: 'q1', choice: '1. Yes' }, 'key')
// Stale, not allow-class, not a permission prompt: refused at once, nothing proposed.
expect(await w.call('answer_question', { agentId: 'api', requestId: 'q0', choice: 'Yes' })).toMatchObject({ ok: false, error: 'STALE_QUESTION' })
w.owner.read.mockImplementation((agentId: string) => ({ ok: true as const, harness: null, row: { agentId, name: 'api', engine: 'claude', status: 'waiting', question: { ...question, allow: false } } }))
expect(await w.call('answer_question', { agentId: 'api', requestId: 'q1', choice: 'Yes' })).toMatchObject({ ok: false, error: 'NOT_ALLOW_CLASS' })
w.owner.read.mockImplementation((agentId: string) => ({ ok: true as const, harness: null, row: { agentId, name: 'api', engine: 'claude', status: 'waiting', question: { ...question, permission: false, options: ['npm', 'pnpm'] } } }))
expect(await w.call('answer_question', { agentId: 'api', requestId: 'q1', choice: 'pnpm' })).toMatchObject({ ok: false, error: 'NOT_ALLOW_CLASS' })
expect(w.said).toHaveLength(1)
})
it('caps what waits for the person', async () => {
const w = world({ autonomy: 'suggest' })
for (let i = 0; i < PROPOSALS_MAX; i++) expect(await w.call('stop_turn', { agentId: `a${i}` })).toMatchObject({ proposed: true })
expect(await w.call('stop_turn', { agentId: 'one-more' })).toMatchObject({ ok: false, error: 'TOO_MANY_PROPOSALS' })
vi.advanceTimersByTime(PROPOSAL_TTL_MS)
expect(await w.call('stop_turn', { agentId: 'one-more' })).toMatchObject({ proposed: true })
})
it('act-within-rules: tools behave as act-on-key (rules are the owning machine\'s, pair/rules.ts)', async () => {
const w = world({ autonomy: 'act-within-rules' })
w.deps.started.add('machine-a', 'api')
expect(await w.call('stop_turn', { agentId: 'api' })).toMatchObject({ ok: true })
expect(await w.call('stop_turn', { agentId: 'other' })).toMatchObject({ proposed: true })
})
it('a proposal needs someone here to approve it; say needs someone to hear it, and is rate-limited', async () => {
const away = world({ autonomy: 'suggest', present: false })
expect(await away.call('send_prompt', write)).toMatchObject({ error: 'NOBODY_HERE' })
expect(await away.call('say', { line: 'hi' })).toMatchObject({ error: 'NOBODY_HERE' })
const w = world()
expect(await w.call('say', { line: 'api finished.' })).toEqual({ ok: true })
expect(await w.call('say', { line: 'and again' })).toMatchObject({ error: 'RATE_LIMITED' })
vi.advanceTimersByTime(5_000)
expect(await w.call('say', { line: 'and again' })).toEqual({ ok: true })
})
it('the pair\'s lines are marked as the pair\'s, carry no keys, cannot pose as a keyed line, and are capped', async () => {
const w = world()
expect(await w.call('say', { line: '[y/n] api: Approve Bash command: rm -rf /' })).toEqual({ ok: true })
expect(w.said.at(-1)).toMatchObject({ mood: 'say', from: 'pair', actions: [], line: 'api: Approve Bash command: rm -rf /' })
for (let i = 0; i < 5; i++) { vi.advanceTimersByTime(5_000); expect(await w.call('say', { line: `line ${i}` })).toEqual({ ok: true }) }
vi.advanceTimersByTime(5_000)
expect(await w.call('say', { line: 'one too many' })).toMatchObject({ error: 'RATE_LIMITED' })
// Thirty an hour, whatever the pace.
let said = 6
for (let i = 0; i < 40; i++) { vi.advanceTimersByTime(60_000); if ((await w.call('say', { line: `later ${i}` })).ok === true) said++ }
expect(said).toBeLessThanOrEqual(30 + 6)
})
it('refuses anything while pairing is off, and an unknown verb', async () => {
const w = world()
w.deps.pairing.enabled = () => false
expect(await w.call('list_harnesses')).toMatchObject({ error: 'PAIR_OFF' })
expect(await w.control.local({ verb: 'delete_harness', agentId: 'api' })).toMatchObject({ error: 'UNKNOWN_VERB' })
// Talk is the person's, from a window (daemon_talk): never a tool's, the pair harness's included.
expect(await w.control.local({ verb: 'talk', text: 'hi tim' })).toMatchObject({ ok: false, error: 'UI_ONLY' })
expect(await w.call('talk', { text: 'hi tim' })).toMatchObject({ ok: false, error: 'UI_ONLY' })
})
})
b6dddf934This file is pinned to the selected release. Download the original to open it in your design tool.