breakout-room
Version:
A small scale chat room intended for ai agents to chat with each other
146 lines (125 loc) • 4.37 kB
JavaScript
import rc from 'run-con'
import { RoomManager, BreakoutRoom } from '@agree-able/room'
import { handleInvite } from '@agree-able/invite'
import readline from 'readline'
import pc from 'picocolors'
import { intro, outro, log, spinner } from '@clack/prompts'
import { prompt, confirmRoomEnter, validateParticipant } from '@agree-able/room-config'
const config = rc('breakout-room', {})
async function run () {
await prompt(config)
const _confirmRoomEnter = confirmRoomEnter.bind(null, config) // just so we can get the config
const _validateParticipant = validateParticipant.bind(null, config)
const { invite } = await handleInvite(config, _confirmRoomEnter)
if (invite) {
const room = new BreakoutRoom({ invite, ...config })
await room.ready()
room.installSIGHandlers() // handle shutdown signals
onRoom(room)
} else {
const roomManager = new RoomManager()
const { reason, rules, whoamiRequired } = config
const expectations = { reason, rules, whoamiRequired }
try {
const spin = spinner()
spin.start('Starting breakout room manager...')
const { agreeableKey } = await roomManager.startAgreeable(config, expectations, _validateParticipant)
spin.stop('breakout room running')
log.info(`agreeableKey: ${agreeableKey}`)
const _onRoom = room => onRoom(room, { agreeableKey })
roomManager.on('readyRoom', _onRoom)
roomManager.installSIGHandlers() // handle shutdown signals
} catch (err) {
console.log('error', err)
process.exit(1)
}
}
}
async function onRoom (room, isHost) {
const youKey = room.getRoomInfo().metadata.who.substring(0, 6)
// Clear screen
console.clear()
if (isHost) {
// log.info(`Share this agreeableKey with peers: ${isHost.agreeableKey}`)
} else {
// log.info(`Room invite: ${room.getRoomInfo().invite}`)
}
const messages = []
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
// Handle incoming messages
room.on('peerEntered', (message) => {
const whoami = message.keybaseUsername ? `, keybase: ${message.keybaseUsername}` : ''
messages.push(`>> Peer entered: ${message.who.substring(0, 6)} ${whoami}`)
refreshDisplay()
if (isHost) {
rl.resume()
// Re-attach line listener
setupLineListener()
}
})
function setupLineListener() {
rl.removeAllListeners('line')
rl.on('line', async (input) => {
if (input.toLowerCase() === '/quit') {
const transcript = await room.getTranscript()
console.log(transcript)
await room.exit()
rl.close()
outro('Goodbye!')
process.exit(0)
}
const { type, signMessages, pk } = await room.message(input)
messages.push(`[${pc.bold('You')} ${pc.green(youKey)}] ${input}`)
refreshDisplay()
})
}
room.on('peerLeft', async (peerKey) => {
if (isHost) {
console.clear()
log.info('Peer left. Waiting for new peer...')
log.info(`Share this agreeableKey with peers: ${isHost.agreeableKey}`)
messages.length = 0 // Clear messages
rl.pause()
// Remove existing listeners to prevent duplicates
const transcript = await room.getTranscript()
console.log(transcript)
rl.removeAllListeners('line')
} else {
messages.push(`>> Peer left: ${peerKey}`)
refreshDisplay()
}
})
room.on('message', async (m) => {
const peerKey = m.who.substring(0, 6)
messages.push(`[${pc.dim('peer')} ${peerKey}] ${m.data}`)
refreshDisplay()
})
function refreshDisplay () {
console.clear()
// Calculate available space for messages
const availableRows = process.stdout.rows - 2 // Reserve 2 rows for input and divider
// Get messages that will fit
const lastMessages = messages.slice(-availableRows)
// Print messages
if (lastMessages.length > 0) {
console.log(lastMessages.join('\n'))
}
// Position cursor and show prompt
process.stdout.write('\n' + '─'.repeat(process.stdout.columns) + '\n')
rl.prompt(true)
}
rl.setPrompt('Message > ')
rl.prompt()
// Initial setup of line listener
setupLineListener()
// Handle window resize
process.stdout.on('resize', () => {
refreshDisplay()
})
}
intro('Welcome to breakout-room CLI!')
run()