/** * tools/matrix-presence-harness/agent-proc.ts * * *** DEV harness — LOCAL SANDBOX ONLY. *** * * Runs ONE presence agent as its own OS process: joins the fleet presence * room and heartbeats `mosaic.presence` via @mosaicstack/comms (RFC-001 P1). * Kept as a standalone process so the validation harness can `kill -9` it to * prove A3 (hard-kill -> offline within dark_threshold) against a real crash, * not a graceful shutdown. * * Auth in DEV is Application-Service masquerade: the process presents the * as_token and acts as its own @agent- MXID. (Per-agent minted tokens * are P2, RFC-001 §2.2/§8.) * * ESM / NodeNext: .js import extensions. */ import { MinimalMatrixClient, PresenceAgent, type LivenessPolicy } from '@mosaicstack/comms'; const env = (k: string, fallback?: string): string => { const v = process.env[k] ?? fallback; if (v === undefined) throw new Error(`missing env ${k}`); return v; }; async function main(): Promise { const homeserverUrl = env('MATRIX_CS_URL'); const asToken = env('MOSAIC_AS_TOKEN'); const serverName = env('MATRIX_SERVER_NAME'); const slug = env('AGENT_SLUG'); const roomId = env('FLEET_ROOM_ID'); const intervalMs = Number(env('HEARTBEAT_INTERVAL_MS', '1000')); const mxid = `@agent-${slug}:${serverName}`; const policy: LivenessPolicy = { heartbeatIntervalMs: intervalMs, missTolerance: Number(env('MISS_TOLERANCE', '2')), darkThresholdMs: Number(env('DARK_THRESHOLD_MS', '6000')), }; const client = new MinimalMatrixClient({ homeserverUrl, accessToken: asToken, actAsUserId: mxid, }); const agent = new PresenceAgent({ client, agent: { mxid, slug, harness: 'claude-code' }, roomId, intervalMs, policy, onError: (err) => console.error(`[${slug}] heartbeat error:`, (err as Error).message), }); await agent.connect(); agent.start(); console.log(`[${slug}] pid=${process.pid} mxid=${mxid} heartbeating every ${intervalMs}ms`); // Graceful stop only on SIGTERM; the A3 test uses SIGKILL (no cleanup runs). process.on('SIGTERM', () => { void agent.stop().finally(() => process.exit(0)); }); // Keep the event loop alive indefinitely. setInterval(() => {}, 1 << 30); } main().catch((err) => { console.error('agent-proc fatal:', err); process.exit(1); });