#!/usr/bin/env node // Mock choir: ask, get a proposal, approve it, see the tempo move. const ws = new WebSocket('ws://127.0.0.1:4317'); const pending = new Map(); const waiters = []; let proposal = null; let resolved = null; let history = null; const waitFor = (predicate) => new Promise((resolve, reject) => { const timer = setTimeout(() => reject(new Error('timeout')), 20000); waiters.push({ predicate, resolve: (value) => { clearTimeout(timer); resolve(value); } }); const existing = messages.find(predicate); if (existing) resolve(existing); }); const messages = []; ws.onopen = () => ws.send(JSON.stringify({ type: 'hello', protocol: 1, token: 'dev', client: 'agent-smoke' })); ws.onmessage = (message) => { if (typeof message.data !== 'string') return; const parsed = JSON.parse(message.data); messages.push(parsed); if (parsed.type === 'agent_history') history = parsed.messages; if (parsed.type === 'event' && parsed.event?.kind === 'agent_proposal') proposal = parsed.event; if (parsed.type === 'event' && parsed.event?.kind === 'agent_resolved') resolved = parsed.event; if (parsed.type === 'result') pending.set(parsed.id, parsed.result); for (const waiter of [...waiters]) { if (waiter.predicate(parsed)) { waiters.splice(waiters.indexOf(waiter), 1); waiter.resolve(parsed); } } }; const send = (id, command) => ws.send(JSON.stringify({ type: 'command', id, command })); await waitFor((message) => message.type === 'snapshot'); send(1, { type: 'agent_ask', text: 'raise the tempo a little' }); await waitFor((message) => message.type === 'event' && message.event?.kind === 'agent_proposal'); if (!proposal) throw new Error('no proposal'); if (!proposal.commands.some((command) => command.type === 'set_tempo')) throw new Error('expected a tempo proposal'); const before = pending.get(1)?.state?.transport?.bpm; send(2, { type: 'agent_approve', id: proposal.id }); await waitFor((message) => message.type === 'event' && message.event?.kind === 'agent_resolved'); const state = pending.get(2)?.state; if (!resolved?.applied) throw new Error('proposal was not applied'); if (state?.transport?.bpm !== 132) throw new Error(`tempo did not move: ${state?.transport?.bpm}`); const userLines = messages.filter((message) => message.type === 'event' && message.event?.kind === 'agent_message' && message.event.message.role === 'user').length; const agentLines = messages.filter((message) => message.type === 'event' && message.event?.kind === 'agent_message' && message.event.message.role === 'agent').length; console.log(JSON.stringify({ history: history?.length ?? 0, proposal: { id: proposal.id, commands: proposal.commands, description: proposal.description }, resolved, bpm: state.transport.bpm, userLines, agentLines, journal: state.journal_len }, null, 2)); ws.close(); console.log('agent smoke passed');