import { afterEach, describe, expect, it, vi } from 'vitest'; import { FleetLivenessReader } from '../liveness-reader.js'; import type { MinimalMatrixClient } from '../matrix-client.js'; import { PresenceAgent } from '../presence-agent.js'; import type { HeartbeatObservation, LivenessPolicy, PresenceStatus } from '../types.js'; /** * An in-memory fake homeserver room: records heartbeats with a controllable * server clock and reduces them exactly like the real readHeartbeats. Lets us * prove the PresenceAgent -> room -> FleetLivenessReader flow (including the A3 * hard-kill -> offline transition) deterministically, with no network. */ class FakeRoomClient { readonly beats: Array<{ slug: string; mxid: string; seq: number; ts: number; status: PresenceStatus; }> = []; presence: Record = {}; constructor(private readonly clock: () => number) {} async joinRoom(roomId: string): Promise { return roomId; } async setPresence(userId: string, status: PresenceStatus): Promise { this.presence[userId] = status; } async sendHeartbeat( _roomId: string, content: { agent: { slug: string; mxid: string }; seq: number; status: PresenceStatus }, ): Promise { this.beats.push({ slug: content.agent.slug, mxid: content.agent.mxid, seq: content.seq, ts: this.clock(), // server receive time status: content.status, }); return `$evt${this.beats.length}`; } async readHeartbeats(): Promise { const bySlug = new Map(); for (const b of this.beats) { const prev = bySlug.get(b.slug); if (!prev || b.seq > prev.lastSeq) { bySlug.set(b.slug, { slug: b.slug, mxid: b.mxid, lastSeenTs: b.ts, lastSeq: b.seq, assertedStatus: b.status, }); } } return [...bySlug.values()]; } } const policy: LivenessPolicy = { heartbeatIntervalMs: 1000, missTolerance: 2, darkThresholdMs: 5000, }; describe('presence flow (A2 + A3 at unit level)', () => { afterEach(() => vi.useRealTimers()); it('shows agents online while beating, then A3: a hard-killed agent goes offline within dark_threshold', async () => { vi.useFakeTimers(); vi.setSystemTime(0); const fake = new FakeRoomClient(() => Date.now()); const client = fake as unknown as MinimalMatrixClient; const reader = new FleetLivenessReader({ client, roomId: '!fleet', policy, now: () => Date.now(), }); const mk = (slug: string) => new PresenceAgent({ client, agent: { mxid: `@agent-${slug}:matrix.localhost`, slug, harness: 'claude-code' }, roomId: '!fleet', intervalMs: 1000, policy, }); const alpha = mk('alpha'); const bravo = mk('bravo'); const charlie = mk('charlie'); for (const a of [alpha, bravo, charlie]) { await a.connect(); a.start(); } // native presence set online for all three (Element dot) expect(fake.presence['@agent-alpha:matrix.localhost']).toBe('online'); // let a couple of beats flow — all three fresh => online (A2) await vi.advanceTimersByTimeAsync(1500); const board1 = Object.fromEntries((await reader.read()).map((r) => [r.slug, r.status])); expect(board1).toEqual({ alpha: 'online', bravo: 'online', charlie: 'online' }); // HARD-KILL charlie: stop its loop, no more beats. alpha/bravo keep beating. charlie.pauseHeartbeat(); // hard-kill: no graceful presence signal // advance to just before dark threshold from charlie's last beat... await vi.advanceTimersByTimeAsync(3000); const mid = Object.fromEntries((await reader.read()).map((r) => [r.slug, r.status])); expect(mid.alpha).toBe('online'); expect(mid.charlie).not.toBe('online'); // already stale (away) // ...advance past dark_threshold: charlie is deterministically offline. await vi.advanceTimersByTimeAsync(4000); const final = await reader.read(); const byslug = Object.fromEntries(final.map((r) => [r.slug, r])); expect(byslug.charlie!.status).toBe('offline'); expect(byslug.alpha!.status).toBe('online'); expect(byslug.bravo!.status).toBe('online'); for (const a of [alpha, bravo]) await a.stop(); }); it('formatBoard renders a human-readable liveness board (A4)', async () => { const fake = new FakeRoomClient(() => 10_000); fake.beats.push({ slug: 'alpha', mxid: '@agent-alpha:matrix.localhost', seq: 3, ts: 9_500, status: 'online', }); const reader = new FleetLivenessReader({ client: fake as unknown as MinimalMatrixClient, roomId: '!fleet', policy, now: () => 10_000, }); const board = await reader.formatBoard(); expect(board).toContain('Fleet presence'); expect(board).toContain('alpha'); expect(board).toContain('online'); expect(board).toContain('online=1'); }); });