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