/**
 * TopicLinkageHandler — Routes threadline replies back to the Telegram topic
 * session that originated the conversation, and tracks the await as a
 * CommitmentTracker one-time-action so PromiseBeacon picks it up automatically.
 *
 * Per THREAD-TOPIC-LINKAGE-SPEC.md (Rev 2).
 *
 * Two surfaces:
 *
 *  1. `captureOriginOnSend()` — called from /threadline/relay-send after a
 *     successful outbound send. Stamps ThreadResumeMap with originTopicId
 *     and creates a commitment with verificationMethod 'threadline-reply'.
 *     The commitment auto-opts into PromiseBeacon because it has a topicId
 *     attached, so the user gets "still waiting on X" heartbeats for free.
 *
 *  2. `tryRouteReplyToTopic()` — called from ThreadlineRouter on inbound
 *     replies. If the thread has an originTopicId pointing to a live topic,
 *     classifies salience, routes the payload to the topic session (live-
 *     inject or resume), fires a Telegram notification when user-visible,
 *     and marks the commitment delivered. Returns null when there's no
 *     topic linkage (router falls through to the existing thread-worker
 *     path).
 *
 * Architectural notes:
 *
 *  - The salience gate is the only NEW judgment authority (LLM-backed, full
 *    context). The routing decision itself is structural (does this thread
 *    have an originTopicId?) — transport-layer dispatch, not judgment.
 *
 *  - Failure-visible: if a topic-linked reply can't be delivered to the
 *    topic session (no live session, no resume entry), we fall back to the
 *    thread-worker path AND post a Telegram notification regardless of
 *    salience, so the user always sees that a reply arrived even if our
 *    auto-pickup failed.
 */
import type { TopicResumeMap } from '../core/TopicResumeMap.js';
import type { CommitmentTracker } from '../monitoring/CommitmentTracker.js';
import type { ThreadResumeMap } from './ThreadResumeMap.js';
import type { SalienceGate, SalienceVerdict } from './SalienceGate.js';
import type { MessageEnvelope } from '../messaging/types.js';
import type { MessageStore } from '../messaging/MessageStore.js';
export interface TopicLinkageDeps {
    topicResumeMap: TopicResumeMap;
    threadResumeMap: ThreadResumeMap;
    commitmentTracker: CommitmentTracker;
    salienceGate: SalienceGate;
    messageStore?: MessageStore | null;
    /**
     * Inject text into a live tmux session, returning whether the session
     * actually CONSUMED it (not merely dispatched). May be async — production
     * confirms submission across the paste-recovery window. A `false`/rejected
     * result means the inject stalled, so the caller must fall back to surfacing.
     */
    injectIntoSession: (sessionName: string, text: string) => boolean | Promise<boolean>;
    /** Check if a tmux session is alive. */
    isSessionAlive: (sessionName: string) => boolean;
    /** Post a notification to a Telegram topic. */
    sendTelegramToTopic?: ((topicId: number, text: string) => Promise<unknown>) | null;
    /** Returns the live tmux session name registered for a topic, if any. */
    getSessionForTopic?: (topicId: number) => string | null;
    /** Local-agent name, for sender filtering. */
    localAgent: string;
    /** Optional clock for deterministic tests. */
    now?: () => number;
}
export interface CaptureOriginInput {
    threadId: string;
    remoteAgent: string;
    remoteAgentDisplayName?: string;
    originTopicId: number;
    /** Free-text intent from the calling session. Stored on the commitment;
     *  NEVER serialized into the outbound MessageEnvelope. */
    purpose?: string;
    /** Subject line of the outbound message, for the resume prompt. */
    subject?: string;
    /** When the send happened (defaults to now). */
    sentAt?: string;
    /** Optional session name override; resolved from TopicResumeMap when omitted. */
    originSessionName?: string;
    /** TTL for the commitment + thread cache. Defaults to 7 days. */
    ttlMs?: number;
}
export interface CaptureOriginResult {
    commitmentId: string;
    threadResumeStamped: boolean;
}
export type TopicRouteOutcome = {
    kind: 'no-linkage';
} | {
    kind: 'topic-expired';
    reason: string;
} | {
    kind: 'routed';
    deliveryMode: 'live-inject' | 'resume-pending' | 'failure-visible';
    verdict: SalienceVerdict;
    reason: string;
    commitmentDelivered: boolean;
    telegramSent: boolean;
};
export interface RouteReplyInput {
    envelope: MessageEnvelope;
    threadEntry: {
        remoteAgent: string;
        subject?: string;
        originTopicId?: number;
        originSessionName?: string;
    };
}
export declare class TopicLinkageHandler {
    private readonly deps;
    private readonly nowFn;
    private readonly recentSurfacesByThread;
    /** Per-topic surface log (timestamps) for the cross-thread rate-limit. */
    private readonly recentSurfacesByTopic;
    constructor(deps: TopicLinkageDeps);
    /**
     * Outbound capture (§5.2 of the spec).
     *
     * Stamps ThreadResumeMap with originTopicId and originSessionName, and
     * creates a one-time-action commitment that ties the thread, the topic,
     * and the stated purpose together. The commitment auto-opts into
     * PromiseBeacon (existing CommitmentTracker behavior).
     *
     * Idempotent on threadId: if a non-terminal commitment already exists for
     * the thread, it is returned unchanged. Repeated calls within the same
     * send burst do not create duplicates.
     */
    captureOriginOnSend(input: CaptureOriginInput): CaptureOriginResult | null;
    /**
     * Inbound dispatch (§5.3 of the spec). Returns null when the thread has no
     * topic linkage (caller falls back to thread-worker path).
     *
     * On topic-linked threads:
     *   - Looks up the commitment + the topic's session.
     *   - Classifies salience via SalienceGate (smart authority).
     *   - Live-injects or resume-stamps the payload into the topic session.
     *   - Posts a Telegram notification when user-visible.
     *   - Marks the commitment `delivered`.
     *
     * On topic-linked threads whose topic has been archived:
     *   - Returns { kind: 'topic-expired', reason }. Caller falls through.
     *   - Commitment is still transitioned to `delivered` with a clear note.
     *
     * Never throws — failures degrade to 'failure-visible' (Telegram surface
     * fires regardless of salience so the user always sees the reply arrived).
     */
    tryRouteReplyToTopic(input: RouteReplyInput): Promise<TopicRouteOutcome>;
    private fetchThreadHistory;
    private buildSessionPayload;
    private buildTelegramSurface;
    private passesRateLimit;
    private recordSurface;
    /**
     * Topic-scoped rate-limit. Caps user-visible Telegram surfaces at
     * USER_VISIBLE_PER_TOPIC_LIMIT per USER_VISIBLE_PER_TOPIC_WINDOW_MS.
     *
     * Defends against the rotate-threadIds bypass (a malicious or merely
     * chatty peer can open many threads against the same topic; without this
     * cap, each thread independently passes the per-thread rate-limit and
     * floods the user). Also defends against the fail-open Telegram-flood
     * scenario when the salience classifier outages.
     */
    private passesTopicRateLimit;
    private recordTopicSurface;
}
//# sourceMappingURL=TopicLinkageHandler.d.ts.map