/**
 * WarrantsReplyGate — "does this inbound even need a reply?"
 *
 * Phase 1 keystone (THREADLINE-CONVERSATION-KEYSTONE-SPEC.md §3). The root cause
 * of the echo↔codey ack-loop is that an amnesiac one-shot worker reflexively
 * replies to everything — including a bare "thanks" — and nothing owns the turn
 * count or decides a conversation is over. This gate runs ONCE at the inbound
 * funnel, upstream of all three routing branches (pipe-spawn / warm-listener /
 * cold-spawn), so a no-reply verdict short-circuits ALL of them.
 *
 * Layered per instar's signal-vs-authority principle:
 *  - SIGNAL (free, deterministic): decisive control tokens + questions +
 *    imperatives ALWAYS pass; pure content-free acks are a strong terminal
 *    signal; novelty is measured against the conversation's last inbound.
 *  - AUTHORITY (only when genuinely ambiguous): a Haiku classifier with a
 *    NO_REPLY label. It can only SUPPRESS; it is never the sole reason to reply.
 *  - A novelty-gated turn budget (counted from turn 1) is the hard backstop:
 *    forward progress (novel content) is required to keep an autonomous thread
 *    going; an ack-storm trips it fast, a genuine 30-turn collaboration never
 *    does. Human-in-loop threads are exempt and stay instant.
 *
 * The gate is PURE — it reads the Conversation and returns a verdict. The caller
 * (the funnel) owns the ConversationStore mutate that bumps turnCount + records
 * the normalized inbound. State lives on the Conversation, never on this gate
 * (the one-shot worker provably can't self-police).
 */
import type { Conversation, ConversationStore } from './ConversationStore.js';
import type { IntelligenceProvider } from '../core/types.js';
export interface WarrantsReplyInput {
    /** The thread this inbound belongs to. */
    threadId: string;
    /** The inbound message text (opaque data — never executed). */
    text: string;
    /** Current conversation record (null = genuine first contact). */
    conversation: Conversation | null;
    /**
     * Whether a verified human is in this thread. MUST be derived from instar's
     * OWN records (e.g. a Telegram topic with a real user) — NEVER from anything
     * the peer sends. Default false (autonomous = stricter) when uncertain.
     */
    humanInLoop: boolean;
    /** Sender hint — forces past suppression, but NOT past the turn budget. */
    expectsReply?: boolean;
    /** Override the soft cap (tests). */
    softCap?: number;
}
export type WarrantsReplySignal = 'human-in-loop' | 'control-token' | 'question' | 'imperative' | 'novel' | 'expects-reply' | 'first-contact' | 'classifier-reply' | 'pure-ack' | 'classifier-no-reply' | 'budget-exhausted';
export interface WarrantsReplyVerdict {
    /** true = a reply worker should be spawned/resumed; false = suppress. */
    warrants: boolean;
    /** Which layer decided. */
    signal: WarrantsReplySignal;
    /** Human-readable reason (for the ledger + the spawn self-view). */
    reason: string;
    /**
     * Set when the novelty-gated turn budget is exhausted — the caller should
     * escalate ONE attention-queue item (not silently drop).
     */
    budgetExhausted: boolean;
    /** Normalized inbound form the caller stores as `lastInboundHash`. */
    normalizedInbound: string;
    /**
     * Whether this inbound was NOVEL vs the conversation's last inbound (forward
     * progress). The caller uses this to reset the no-progress counter: a novel
     * turn resets it (a 30-turn novel collaboration never trips the budget), a
     * non-novel turn accrues toward the backstop.
     */
    novel: boolean;
}
/** Lowercase, strip greetings/sign-offs, drop punctuation, collapse space. */
export declare function normalizeForNovelty(text: string): string;
/** Unique sorted token set of a normalized string. */
export declare function tokenSet(normalized: string): Set<string>;
/** Jaccard similarity between two token sets (1 = identical, 0 = disjoint). */
export declare function tokenSetSimilarity(a: Set<string>, b: Set<string>): number;
export declare class WarrantsReplyGate {
    private softCap;
    private intelligence?;
    constructor(opts?: {
        softCap?: number;
        intelligence?: IntelligenceProvider;
    });
    evaluate(input: WarrantsReplyInput): Promise<WarrantsReplyVerdict>;
    private classify;
}
export interface InboundReplyParams {
    threadId: string;
    text: string;
    senderFingerprint: string;
    senderName: string;
    trustLevel: string;
    /** From OUR OWN verified records, never the peer. Default false (autonomous). */
    humanInLoop: boolean;
    expectsReply?: boolean;
}
export interface InboundReplyDecision {
    /** true → do NOT spawn a reply worker (short-circuit all routing branches). */
    suppress: boolean;
    verdict: WarrantsReplyVerdict;
}
/**
 * The single funnel step: evaluate the warrants-a-reply gate AND record the
 * inbound on the Conversation in one place, so the relay inbound funnel and the
 * integration tests exercise the SAME code (no copy that could drift). The
 * no-progress counter resets on a novel turn and accrues otherwise; a suppress
 * verdict flips the conversation to 'idle'. The caller owns the
 * attention-escalation + branch short-circuit (it has the messaging surface).
 */
export declare function evaluateAndRecordInbound(gate: WarrantsReplyGate, store: ConversationStore, params: InboundReplyParams): Promise<InboundReplyDecision>;
//# sourceMappingURL=WarrantsReplyGate.d.ts.map