Co-authored-by: jason.woltje <jason@diversecanvas.com> Co-committed-by: jason.woltje <jason@diversecanvas.com>
90 lines
3.1 KiB
TypeScript
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;
|
|
}
|