Files
stack/tools/matrix-presence-harness/agent-proc.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

71 lines
2.3 KiB
TypeScript

/**
* 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-<slug> 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<void> {
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);
});