/**
 * ReapNotifier — the single coalescing listener for `sessionReaped`
 * (UNIFIED-SESSION-LIFECYCLE §P3; v2 per reap-notify spec R1.1–R1.5).
 *
 * `SessionManager.terminateSession` emits `sessionReaped` exactly once per kill
 * at the one chokepoint. This listener turns TERMINAL reaps of user-facing
 * sessions into "your session was shut down" notices so a session never
 * silently vanishes (the 2026-05-27 incident). It stays SILENT for:
 *   - `recovery-bounce` reaps (a kill-to-respawn is a bounce, not a disappearance),
 *   - `origin:'operator'` reaps (the user clicked kill — telling them is noise).
 *
 * v2 (perTopic: true, the shipped default):
 *   - Per-topic coalescing (R1.1): every topic that lost a session gets ONE
 *     notice in THAT topic; the lifeline gets unbound sessions plus — when >1
 *     topic is affected — a one-line cross-topic index. Never creates topics.
 *   - Affected-set tracking is SEPARATE from the bounded detail buffer, so in
 *     a storm larger than the buffer every affected topic still gets at least
 *     a correct count (count-only notice when its detail was dropped).
 *   - Durable delivery (R1.3): notices become PendingRelayStore rows keyed
 *     `reap-notify:<noticeId>` with the release hold riding `next_attempt_at`;
 *     the always-on ReapNoticeDrain delivers them. Outcome records land in
 *     the reap-log as append-only pairs. Enqueue failure degrades LOUDLY to
 *     one direct send attempt, recorded `enqueue-failed`.
 *   - Release tiers (decision 1 + R1.5): a mid-work reap with a QUEUED resume
 *     releases IMMEDIATE outside quiet hours (quiet-hours end inside them —
 *     never wakes the user; a queued resume means the system is already
 *     handling it); everything else releases on the SUMMARY window. At most
 *     `maxImmediatePerFlush` immediate releases per flush.
 *   - Plain English (R1.2): reason slugs map to human sentences (unknown slug
 *     ⇒ generic sentence with the slug parenthesized); no curl/API pointers
 *     in any user-facing body.
 *
 * Legacy (perTopic: false — the rollback lever): byte-compatible with the
 * pre-v2 behavior (single buffer; burst ⇒ ONE consolidated lifeline message).
 *
 * Sanitization: session names follow user-controlled topic renames, so the
 * dynamic fields (name, reason) are wrapped as inline-code spans — the
 * downstream Telegram formatter renders code spans as literal, HTML-escaped
 * text, never markup. The notifier never emits raw markup around
 * user-controlled values.
 */
import type { Session } from '../core/types.js';
import type { ReapNotifyOutcome } from './ReapLog.js';
export interface ReapEvent {
    session: Pick<Session, 'name' | 'tmuxSession'>;
    reason: string;
    disposition?: 'terminal' | 'recovery-bounce';
    origin?: 'operator' | 'autonomous';
    /** Mid-work stamp from the kill chokepoint (reap-notify spec R2.1). */
    midWork?: boolean;
    /** Clamped work-evidence names behind midWork. */
    workEvidence?: string[];
    /** Notifier-stamped (NOT emitter-supplied): set at ingest when an `age-limit`
     *  reap targets a session whose topic has an ACTIVE autonomous run — the recycle
     *  is a continuation (the run respawns on its next turn), not a death. The
     *  honest-recycle copy (honest-session-recycle-spec) keys on this instead of
     *  the "reached its maximum allowed runtime" death template. */
    autonomousRunActive?: boolean;
    /** Notifier-stamped: seconds left on that autonomous run, read at reap time. */
    runRemainingSeconds?: number;
}
export interface ReapNotifierDeps {
    /** Bound messaging topic for a session, or null if unbound. */
    resolveTopic: (tmuxSession: string) => number | null;
    /** The always-on system/lifeline topic, or null if none is configured. */
    lifelineTopic: () => number | null;
    /** Legacy delivery (perTopic:false and drainEnabled:false modes). */
    send: (topicId: number, text: string) => void | Promise<void>;
    /** Durable store enqueue (v2). Returns false on PK-dedupe no-op. */
    enqueueNotice?: (input: {
        delivery_id: string;
        topic_id: number;
        text: string;
        next_attempt_at: string;
    }) => boolean;
    /** Append a notify outcome record to the reap-log (R1.3 pairs). */
    recordNotify?: (e: {
        noticeId: string;
        topicId: number | null;
        outcome: ReapNotifyOutcome;
        detail?: string;
    }) => void;
    /** Epoch ms when the current quiet-hours window ends; null = not in quiet hours. */
    quietHoursEndAt?: (nowMs: number) => number | null;
    /** Epoch ms of the next SUMMARY-window release (≤30 min out — R1.5). */
    summaryReleaseAt?: (nowMs: number) => number;
    /** True when a resume-queue entry is QUEUED for this session AND the queue is
     *  LIVE (not dry-run) — gates the "resume queued" line (R1.2). */
    resumeQueuedFor?: (tmuxSession: string) => boolean;
    /** Resolve whether `tmuxSession`'s topic has an ACTIVE autonomous run, and how
     *  many seconds remain on it, read at reap time. Returns null when there is no
     *  active run (or on any error — the safe direction is the legacy death copy,
     *  never silence). Honest-recycle (honest-session-recycle-spec). */
    autonomousRunActiveFor?: (tmuxSession: string) => {
        active: boolean;
        remainingSeconds?: number;
    } | null;
    /** Loud degradation surface for enqueue failures (aggregated upstream). */
    reportDegradation?: (reason: string, impact: string) => void;
    now?: () => number;
}
export interface ReapNotifierOptions {
    enabled: boolean;
    coalesceWindowMs: number;
    /** Max reaps retained for detail lists (counts stay exact regardless). */
    maxBuffer: number;
    /** v2 per-topic grouping (R1.1). false = legacy single-buffer behavior. */
    perTopic: boolean;
    /** Max notices released IMMEDIATE in one flush (R1.5). */
    maxImmediatePerFlush: number;
    /** Durable delivery via store + drain (R1.3). false = legacy direct send
     *  (grouping unaffected; the durability guarantee lapses — stated rollback). */
    drainEnabled: boolean;
}
export declare const DEFAULT_REAP_NOTIFIER_OPTIONS: ReapNotifierOptions;
export declare function plainEnglishReason(slug: string): string;
/** Humanize a remaining-seconds count as "11h 42m" / "42m" / "" (when absent). */
export declare function formatRunRemaining(seconds: number | undefined): string;
export declare class ReapNotifier {
    private readonly deps;
    private readonly opts;
    private readonly now;
    private timer;
    private buffer;
    private windowCount;
    /** v2 affected-set (R1.1) — tracked separately from the detail buffer. */
    private affected;
    private affectedOverflow;
    private unbound;
    private noticeSeq;
    constructor(deps: ReapNotifierDeps, opts?: Partial<ReapNotifierOptions>);
    /** The `sessionReaped` event handler. */
    onReaped(event: ReapEvent): void;
    private trackAffected;
    /** Remove a buffer-evicted event from its topic's detail list (count stays). */
    private dropDetail;
    private safeResolveTopic;
    private safeAutonomousRunActive;
    private safeResumeQueued;
    /**
     * Emit the coalesced notice(s) for the closed window and reset. Public so the
     * lifecycle (and tests) can drive it deterministically.
     */
    flush(): Promise<void>;
    /** Legacy (pre-v2) flush — byte-compatible single-buffer behavior. */
    private flushLegacy;
    private nextNoticeId;
    private releaseAt;
    private deliver;
    private formatTopicNotice;
    private formatUnboundNotice;
    private formatCrossTopicIndex;
    private formatSingleLegacy;
    private formatBurstLegacy;
}
//# sourceMappingURL=ReapNotifier.d.ts.map