/** * tools/matrix-presence-harness/provision.ts * * *** DEV harness — LOCAL SANDBOX ONLY. *** * * The MINIMAL P1 provisioner (RFC-001 §10 P1): its ONLY job is to register a * few agent MXIDs, create the fleet presence room, and join the agents. It is * deliberately NOT the P2 appservice (no auto-enroll, no room taxonomy, no * token minting, no introductions). * * It reuses the existing, tested `@mosaicstack/appservice` core library * (AppserviceIntent: register / createRoom / join over the AS API) rather than * reinventing Matrix plumbing. * * ESM / NodeNext: .js import extensions. */ import { AppserviceIntent, type AppserviceConfig } from '@mosaicstack/appservice'; export interface ProvisionOptions { homeserverUrl: string; serverName: string; asToken: string; hsToken: string; agentSlugs: string[]; fleetAlias?: string; // localpart, default "mosaic-fleet" } export interface ProvisionResult { roomId: string; senderUserId: string; agents: { slug: string; mxid: string }[]; } /** Register agents, create the fleet presence room, join everyone. Idempotent. */ export async function provision(opts: ProvisionOptions): Promise { const cfg: AppserviceConfig = { homeserverUrl: opts.homeserverUrl, domain: opts.serverName, asToken: opts.asToken, hsToken: opts.hsToken, }; const intent = new AppserviceIntent(cfg); const fleetAlias = opts.fleetAlias ?? 'mosaic-fleet'; // 1. Register the virtual agent MXIDs (bypasses enable_registration:false). const agents = []; for (const slug of opts.agentSlugs) { const mxid = await intent.ensureRegistered(slug); await intent.setDisplayName(slug, `agent ${slug} (DEV)`); agents.push({ slug, mxid }); } // 2. Create the single fleet presence room, inviting all agents (RFC-001 §4.6). // Idempotent: if the alias already exists, reuse that room instead. let roomId: string; try { const created = await intent.createRoom({ name: 'Fleet Presence (DEV)', alias: fleetAlias, topic: 'RFC-001 P1 — mosaic.presence heartbeats. Who is alive?', invite: agents.map((a) => a.mxid), }); roomId = created.roomId; } catch (err) { // Alias already taken (re-run): resolve the existing room id. const aliasFq = `#${fleetAlias}:${opts.serverName}`; roomId = await resolveAlias(opts, aliasFq); void err; } // 3. Join every agent into the fleet room (invite + join; idempotent). for (const { slug } of agents) { await intent.ensureJoined(roomId, slug); } return { roomId, senderUserId: intent.senderUserId, agents }; } async function resolveAlias( opts: Pick, aliasFq: string, ): Promise { const url = `${opts.homeserverUrl.replace(/\/$/, '')}/_matrix/client/v3/directory/room/${encodeURIComponent(aliasFq)}`; const res = await fetch(url, { headers: { Authorization: `Bearer ${opts.asToken}` } }); if (!res.ok) throw new Error(`resolveAlias ${aliasFq} -> ${res.status}`); const data = (await res.json()) as { room_id?: string }; if (!data.room_id) throw new Error(`resolveAlias ${aliasFq} returned no room_id`); return data.room_id; }