/** * Inbound message de-duplication, shared across ingress paths. * * A surface can reach the adapter through more than one route. ntfy is the * proven case: the same message arrives on the long-lived JSON subscription * AND on the HTTP webhook route, and the subscription's own seen-id cache is * scoped to a single subscribe call, so it cannot see the webhook's copy (or * its own copy after a reconnect). Each delivery ran the full pipeline, which * is how one message produced two agents. * * This store is deliberately module-scoped rather than per-context: the * adapter context is rebuilt per inbound message, so anything held on the * context would be a fresh, empty cache every time and would dedupe nothing. * * Bounded and expiring, a dedup cache that grows forever is its own defect. */ export declare class InboundMessageDedup { private readonly ttlMs; private readonly maxEntries; private readonly now; private readonly seen; constructor(ttlMs?: number, maxEntries?: number, now?: () => number); /** * Claim a message id. Returns true on the FIRST delivery and false for every * repeat inside the TTL, so callers read as * `if (!dedup.claim(key)) return alreadyHandledResponse;`. */ claim(key: string): boolean; has(key: string): boolean; /** * Give a claim back, so the next delivery of that message is treated as the * first one again. * * `claim()` records the key BEFORE the caller does its work, which is what * stops two concurrent deliveries of one message both running the pipeline. * That ordering has a consequence: if the work then FAILS, the key stays * claimed, and a retry is suppressed as a duplicate of an attempt that never * finished. The message is silently dropped, which, for a caller whose * whole purpose is to tell somebody a message arrived, is worse than the * double-delivery the claim was preventing. * * So a caller that claims-then-fails releases, and its retry does real work. * The inbound-mail watcher is the case this was added for: its cursor * advances only after processing completes, so a failure there is * DESIGNED to redeliver, and the redelivery has to be allowed to succeed. * * Releasing a key that was never claimed is a no-op, not an error, an error * path that can itself throw is not much of an error path. */ release(key: string): void; get size(): number; clear(): void; private prune; } /** * Build a dedup key from a surface, a scope (topic/channel/chat), and the * provider's own message id. A missing id yields '' so {@link * InboundMessageDedup.claim} lets the message through rather than collapsing * every id-less message onto one key. */ export declare function inboundDedupKey(surface: string, scope: string | undefined, messageId: string | undefined): string; /** The ntfy adapter's shared cache, see the module docstring for why it is here. */ export declare const ntfyInboundDedup: InboundMessageDedup; //# sourceMappingURL=inbound-dedup.d.ts.map