/** Circular buffer of recent agent events for reconnection catch-up. */
export class EventBuffer {
    /** Floor sentinel reserved for snapshot ordering. All snapshot `_order`
     *  values are strictly less than every live `seq` (which starts at 1).
     *  See docs/design/unified-message-ordering-reducer.md §3.2. */
    static SNAPSHOT_ORDER_FLOOR = -1_000_000_000;
    buffer = [];
    maxSize;
    nextSeq = 1;
    constructor(maxSize = 1000) {
        this.maxSize = maxSize;
    }
    /** Append an event, assigning it a monotonic `seq` and wall-clock `ts`.
     *  Returns the stored entry so callers can attach seq/ts to the broadcast. */
    push(event) {
        const entry = { seq: this.nextSeq++, ts: Date.now(), event };
        this.buffer.push(entry);
        if (this.buffer.length > this.maxSize) {
            this.buffer.shift();
        }
        return entry;
    }
    /** Like push() but for non-`event` frames (e.g. `tool_permission_needed`)
     *  that need a seq for client ordering but do NOT need to be retained for
     *  reconnect resume — the client recovers them via the messages snapshot.
     *  Stamps a monotonic `seq` and wall-clock `ts` without touching the ring.
     *  See docs/design/unified-message-ordering-reducer.md §3.1. */
    pushFrame() {
        return { seq: this.nextSeq++, ts: Date.now() };
    }
    /** All buffered entries, oldest first. */
    getAll() {
        return [...this.buffer];
    }
    /** Return entries whose `seq > fromSeq`, preserving buffer order. */
    since(fromSeq) {
        if (this.buffer.length === 0)
            return [];
        // If fromSeq is older than our oldest retained - 1, we cannot resume.
        // Callers should check canResumeFrom first; we return all as a best-effort.
        const first = this.buffer[0].seq;
        if (fromSeq < first - 1)
            return [...this.buffer];
        const out = [];
        for (const e of this.buffer) {
            if (e.seq > fromSeq)
                out.push(e);
        }
        return out;
    }
    /** True if `fromSeq` falls inside the retained window (i.e. we still hold
     *  `fromSeq + 1`, or the buffer is empty meaning no events were missed). */
    canResumeFrom(fromSeq) {
        // Empty buffer: resume is only safe if the client is already caught up
        // (fromSeq === lastSeq). Otherwise events were evicted or never seen.
        if (this.buffer.length === 0)
            return fromSeq === this.lastSeq;
        // Non-empty: we need at least seq === fromSeq + 1 retained,
        // i.e. the oldest retained entry has seq <= fromSeq + 1.
        return this.buffer[0].seq <= fromSeq + 1;
    }
    clear() {
        this.buffer = [];
        this.nextSeq = 1;
    }
    /** Reseed the next-seq counter so a freshly constructed buffer continues
     *  the previous one's monotonic sequence space. Used by `_restartAgent`-class
     *  flows to preserve the client-side `_highestSeq` frame of reference across
     *  a server-side process respawn (clients do NOT disconnect during the
     *  restart, so their `_highestSeq` keeps the old high-water mark — if the
     *  new buffer started back at 1, every fresh event would be silently dropped
     *  by the client's `seq <= _highestSeq` dedup gate).
     *  See docs/design/restart-preserves-streaming-frame-of-reference.md. */
    seedNextSeq(seq) {
        if (!Number.isFinite(seq) || seq < 1)
            return;
        if (seq <= this.nextSeq)
            return; // never go backwards
        this.nextSeq = seq;
    }
    get size() {
        return this.buffer.length;
    }
    /** Highest seq assigned so far (0 if nothing has been pushed). */
    get lastSeq() {
        return this.nextSeq - 1;
    }
}
//# sourceMappingURL=event-buffer.js.map