// Row 208: isolated real broker + chat HTTP; no config, slot, provider or live host. import { createServer, request } from 'node:http'; import { mkdtempSync, readFileSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { Broker } from '../../bus/src/broker.mjs'; import { Store } from '../../bus/src/store.mjs'; import { createChat } from '../src/chat.mjs'; import { chatHTTP } from '../src/chat-http.mjs'; import { consoleAccess } from '../src/access.mjs'; import { BusReadError } from '../src/bus.mjs'; export const mutants = { composer: ["${threadWrites() ? `
({ id: 'g-' + user, user, business: 'demo', projects: ['*'], instances: ['assistant'], level: 'collaborator' })) }; const broker = new Broker({ store, access: () => access, businesses: { demo: { id: 'demo', human: 'jason', arbiters: { technical: 'chief', delivery: 'chief' }, roles: { chief: { definition: 'chief-of-staff', authority: { withinRole: ['message.send'], crossRole: [] } }, assistant: { definition: 'assistant', authority: { withinRole: ['message.send'], crossRole: [] } } }, definitions: { 'chief-of-staff': {}, assistant: { messaging: JSON.parse(readFileSync(new URL('../../../roles/assistant.json', import.meta.url))).messaging } }, } } }); const human = broker.bindHuman({ business: 'demo', human: 'jason', via: 'cli', outsideAgent: true }); const invoke = (cap, verb, args = {}) => broker.request(cap, { verb, args }); const alice = () => broker.bindUser({ business: 'demo', user: 'alice' }); const bob = () => broker.bindUser({ business: 'demo', user: 'bob' }); const project = invoke(human, 'project.create', { name: 'Viewer project' }).id; const other = invoke(human, 'project.create', { name: 'Collaborator project' }).id; const own = invoke(alice(), 'thread.create', { project, instance: 'assistant', name: 'Old own thread' }).id; const writable = invoke(alice(), 'thread.create', { project: other, instance: 'assistant', name: 'Writable thread' }).id; const shared = invoke(bob(), 'thread.create', { project, instance: 'assistant', name: 'Shared thread ' }).id; invoke(alice(), 'thread.send', { thread: own, body: 'Own history' }); invoke(bob(), 'thread.send', { thread: shared, body: 'Shared history' }); invoke(bob(), 'thread.share', { thread: shared, to: 'user:alice' }); access.grants[0].level = 'viewer'; const hints = consoleAccess({ load: () => access, business: () => 'demo' }); const principal = () => owner ? human : alice(); const chat = createChat({ call: async (verb, args) => { calls.push([verb, args]); if (verb === 'plugins') return { plugins: { channel: ['webui'] } }; try { return invoke(principal(), verb, args); } catch (e) { throw new BusReadError(e.code); } } }); const source = readFileSync(new URL('../src/public/chat.js', import.meta.url), 'utf8'); let chatSource = source; if (mutant) { const [before, after] = mutants[mutant] ?? []; if (!before || !source.includes(before)) throw Error('mutant anchor missing: ' + mutant); chatSource = source.replace(before, after); } const server = createServer(async (req, res) => { const url = new URL(req.url, 'http://fixture'); if (url.pathname === '/api/config') { res.writeHead(200, { 'content-type': 'application/json', 'cache-control': 'no-store' }); return res.end(JSON.stringify({ send: true, user: !owner, chatGrants: hints.chatGrants({ user: 'alice', provider: 'fake', sub: 'alice-sub' }) })); } if (url.pathname.startsWith('/api/chat/')) return chatHTTP(req, res, { path: url.pathname, search: url.search, chat, named: true, send: true, preview: { sso: true, user: owner ? 'jason' : 'alice', origin: origin() }, log: line => lines.push(line), }); if (url.pathname === '/chat.js') { res.writeHead(200, { 'content-type': 'text/javascript' }); return res.end(chatSource); } if (url.pathname === '/') { res.writeHead(200, { 'content-type': 'text/html' }); return res.end('Sharing fixture
'); } res.writeHead(404); res.end(); }); await new Promise(r => server.listen(0, '127.0.0.1', r)); const origin = () => `http://127.0.0.1:${server.address().port}`; t.after(async () => { await new Promise(r => server.close(r)); store.close(); rmSync(dir, { recursive: true, force: true }); }); const http = (path, value, extra = {}) => new Promise((resolve, reject) => { const body = value === undefined ? undefined : JSON.stringify(value); const req = request(origin() + path, { method: body ? 'POST' : 'GET', headers: body ? { origin: origin(), 'sec-fetch-site': 'same-origin', 'content-type': 'application/json', ...extra } : extra }, res => { let data = ''; res.on('data', chunk => { data += chunk; }); res.on('end', () => resolve({ status: res.statusCode, body: JSON.parse(data) })); }); req.on('error', reject); req.end(body); }); return { store, access, broker, human, alice, bob, invoke, project, other, own, writable, shared, calls, lines, origin: origin(), http, hints, source, chatSource }; }