/** * The minimal client-side participant engine for ONE admitted chat session: the three behaviours * the native `CotalEndpoint` does not provide on its own (handoff r5 §Engine seam): * * 1. per-session attention and per-channel quiet/mute buffering (the session's own inbox); * 2. bounded coalescing of the live and durable copies of one message, keyed by the * subject-verified sender principal plus message id; * 3. reply resolution for senders that have no roster row, from authenticated senders only: * a DM, an anycast or a channel message, whose sender the delivering subject names. * * It holds no authority, opens nothing, publishes nothing. It consumes exactly what the native * `"message"` event hands a listener: `(CotalMessage, Delivery, MessageMeta)`. Every decision reads * subject-derived data only: `MessageMeta.kind` (from the delivering subject) and `msg.from.id` * (verified by the endpoint against the subject sender before the event fires, core * `endpoint.ts:6203-6213`, `:6293`). Payload names, mentions and routing fields never authorize. * * Acknowledgement is deferred: the native durable `Delivery.ack` is `JsMsg.ack()` and is not called * before the listener runs (`endpoint.ts:7192-7199`, `:6232`, `:5962`). Live copies (`durable:false`) * and history backfill have no-op acks; nothing redelivers them. The engine acks a durable copy only * when the caller reports it surfaced, or when it drops it on purpose (mute), never on receipt. */ import type { AttentionMode, ChannelMode, CotalMessage, Delivery, MessageMeta } from "@cotal-ai/core"; import type { SessionMessage } from "./session.js"; /** Bound on buffered items. Provisional; set at review. */ export declare const MAX_BUFFER = 500; /** Bound on remembered handled ids for cross-path coalescing (two rotating windows of this size). */ export declare const MAX_HANDLED_IDS = 4096; /** Bound on remembered unrostered senders for reply resolution. */ export declare const MAX_SENDERS = 256; /** What the engine tells its caller to do with an accepted message. */ export type Disposition = /** Show it now, then call {@link ParticipantEngine.surfaced} with its key. */ { readonly kind: "surface"; readonly message: SessionMessage; } /** Held in the session inbox (quiet channel or focus/dnd). Read with peek/drain. */ | { readonly kind: "buffered"; readonly message: SessionMessage; } /** Dropped on purpose (muted channel, or an already handled copy). Already acked when durable. */ | { readonly kind: "dropped"; readonly reason: "muted" | "duplicate-handled"; } /** A later copy of a message still pending here; the stored ack handle may have been upgraded. */ | { readonly kind: "coalesced"; }; /** The overflow report: what was let go and how, so the UI can show it instead of hiding it. */ export interface Overflow { /** Channel items released to make room. Durable ones were acked (dropped for good). */ readonly channelDropped: number; /** Directed items (DM/anycast) left unacked so the broker redelivers them later. */ readonly directedDeferred: number; } export declare class ParticipantEngine { private readonly selfPrincipal; private attention; private readonly modes; /** Pending by delivery key, in arrival order. Includes surfaced-not-yet-acked and buffered. */ private readonly pending; /** Sender+id coalescing key -> delivery key; only non-empty ids. */ private readonly byId; private handled; private handledPrev; /** Authenticated senders seen on a DM, an anycast or a channel: principal -> last display name. * Insertion-ordered LRU. */ private readonly senders; private seq; private overflow; constructor(selfPrincipal: string); setAttention(mode: AttentionMode): void; setChannelMode(channel: string, mode: ChannelMode | "normal"): void; channelModes(): Record; /** Feed one native `"message"` event. `mentionsMe` is advisory display only (payload-derived). */ accept(msg: CotalMessage, delivery: Delivery, meta: MessageMeta | undefined, mentionsMe: boolean): Disposition; /** Buffered items in arrival order, left buffered (peek). */ inbox(): readonly SessionMessage[]; /** Take buffered items out of the buffer for showing. They stay pending (unacked) until the caller * reports them with {@link surfaced}; if it never does, durable copies redeliver. */ drain(): readonly SessionMessage[]; bufferedCount(): number; /** Commit after the caller has shown these deliveries. Unknown keys are ignored (already acked). */ surfaced(keys: readonly string[]): void; private keyOf; /** Overflow since the last call, then reset. A UI shows it as a gap, never silently. */ takeOverflow(): Overflow; /** The last display name of a sender this session received a DM, an anycast or a channel message * from, by its exact principal. The principal came from the delivering subject, never from the * payload. */ senderName(id: string): string | undefined; /** Every sender this session received a DM, an anycast or a channel message from whose last * display name is `name`, both sides trimmed and compared case-insensitively. */ sendersNamed(name: string): { id: string; name: string; }[]; /** Release everything on close without acking: undelivered durable copies redeliver to the * next reader of this lifecycle's durable, which, after retirement, is nobody. */ clear(): void; private toMessage; private rememberSender; private markHandled; /** Keep the buffer bounded. Channel items go first (acked if durable: an explicit, reported drop); * directed items are never acked on overflow, only forgotten locally so the broker redelivers. */ private enforceBound; } //# sourceMappingURL=engine.d.ts.map