Files
stack/tools/matrix-presence-harness/agent-proc.ts
mosaic-coder 2e7c124e4d
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
feat(comms): P1 presence — minimal Synapse + fleet presence room + mosaic.presence heartbeat + liveness
Implements RFC-001 P1 (the first shippable slice): deterministic Matrix
presence/liveness on a single-instance dev Synapse.

- infra/matrix/: DEV Synapse (RFC-002 Mode B, federation OFF, self-signed TLS,
  enable_registration:false, appservice registration wired). Rendered from
  parameterized templates — zero hardcoded topology. .data is gitignored.
- packages/comms/: minimal MACP presence SDK — set presence, run the
  mosaic.presence heartbeat (seq + interval per RFC-001 §4.5), and a
  deterministic liveness reader (online/away/offline from heartbeat age, NOT
  native-presence-timeout). Liveness core written RED-FIRST. 19 vitest tests.
- tools/matrix-presence-harness/: minimal provisioner (registers >=3 agent
  MXIDs, creates the fleet presence room, joins them — reuses the existing
  @mosaicstack/appservice intent lib) + an E2E validation harness proving
  A2/A3/A4 against a real Synapse.
- eslint.config.mjs: register packages/comms/vitest.config.ts with the
  type-aware project service (same as other packages' vitest configs).

DEV-compose validated only; production deploy is a separate coordinated step
(deploy-holds respected).

Part of the comms-evolution program (RFC-001 P1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0158NZqN2n2ymKFeJAZ4GUCb
2026-07-24 20:18:18 -05: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);
});