Files
stack/tools/matrix-presence-harness/provision.ts
jason.woltje 2698ddb7b5
Some checks failed
ci/woodpecker/push/ci-image Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/publish Pipeline failed
feat(comms): P1 presence — minimal Synapse + fleet presence room + mosaic.presence heartbeat + liveness (#888)
Co-authored-by: jason.woltje <jason@diversecanvas.com>
Co-committed-by: jason.woltje <jason@diversecanvas.com>
2026-07-25 21:03:18 +00:00

90 lines
3.1 KiB
TypeScript

/**
* 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<ProvisionResult> {
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<ProvisionOptions, 'homeserverUrl' | 'asToken'>,
aliasFq: string,
): Promise<string> {
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;
}