/** * Watchdog — simple per-room timeout detector. * * `start()` begins a countdown. `heartbeat()` resets it. If the * countdown elapses without a heartbeat, `onTimeout` fires once * (subsequent heartbeats are no-ops after firing, unless `start()` * is called again). `stop()` cancels any pending timer. * * Used by the runner to detect stuck rooms: one watchdog per room, * scenarios call ctx.heartbeat(roomId) at each progress point, and * a firing watchdog logs + aborts the run. */ export class Watchdog { private timer: NodeJS.Timeout | null = null; private fired = false; constructor( private timeoutMs: number, private onTimeout: () => void, ) {} start(): void { this.stop(); this.fired = false; this.timer = setTimeout(() => { if (this.fired) return; this.fired = true; this.onTimeout(); }, this.timeoutMs); } heartbeat(): void { if (this.fired) return; this.start(); } stop(): void { if (this.timer) { clearTimeout(this.timer); this.timer = null; } } }