/**
 * sentinelWiring — constructs the runtime dependencies for the two
 * "silently-stopped" sentinels (SocketDisconnectSentinel and
 * ActiveWorkSilenceSentinel) from a SessionManager surface + config.
 *
 * Why this module exists: PR #334 merged both sentinels as standalone,
 * unit-tested detectors but never instantiated them in the server. The
 * release notes claimed "wired into server startup" — it wasn't true. This
 * module is the missing wire-up, extracted from server.ts so the dependency
 * construction is itself unit-testable (Testing Integrity Standard: every
 * dependency-injected component needs wiring-integrity tests proving the deps
 * are real functions that delegate to the underlying primitives, not nulls or
 * silent no-ops).
 *
 * Signal-vs-authority: everything here is a detector or a bounded recovery
 * primitive. `looksActivelyWorking` is a structural detector. All user-facing
 * escalation routes through the existing MessagingToneGate via the `/attention`
 * endpoint (category: degradation) — no new blocking authority is introduced.
 *
 * Spec: docs/specs/silently-stopped-trio.md
 */
import type { SocketDisconnectSentinelDeps } from './SocketDisconnectSentinel.js';
import type { ActiveWorkSilenceSentinelDeps, SessionRegistryEntry, SilenceFunnelEvent } from './ActiveWorkSilenceSentinel.js';
import type { ContextWedgeSentinelDeps } from './ContextWedgeSentinel.js';
import { getActivitySignal } from './frameworkActivitySignals.js';
import type { IntelligenceFramework } from '../core/intelligenceProviderFactory.js';
/** Minimal SessionManager surface the wiring depends on. */
export interface SentinelSessionSurface {
    captureOutput(tmuxSession: string, lines?: number): string | null;
    isSessionAlive(tmuxSession: string): boolean;
    sendKey(tmuxSession: string, key: string): boolean;
    listRunningSessions(): Array<{
        tmuxSession: string;
        framework?: IntelligenceFramework;
    }>;
}
/**
 * Posts a user-facing escalation through the tone-gated `/attention` route.
 * Returns true on 201 (delivered), false otherwise — including a 422 tone-gate
 * block (which is the gate doing its job, not an error to retry).
 */
export type AttentionPoster = (item: {
    id: string;
    title: string;
    summary: string;
    category?: string;
    priority?: string;
    /** Route into the calm "🩺 Agent Health" lane (see TelegramAdapter). */
    lane?: 'agent-health';
    /** Stable per-entity key for Agent-Health-lane suppression dedup. */
    healthKey?: string;
}) => Promise<boolean>;
export declare function makeAttentionPoster(opts: {
    port: number;
    authToken: string;
    fetchImpl?: typeof fetch;
}): AttentionPoster;
/**
 * Callback shape for routing a recovery-failed escalation through the
 * SentinelNotifier (or any equivalent sink). Decouples the wiring from the
 * specific delivery policy — server.ts wires a SentinelNotifier, tests wire a
 * capture function. The wiring no longer knows or cares whether the message
 * ends up in the logs, on Telegram, or in a fixture array.
 */
export type EscalateFn = (sessionName: string, text: string) => void | Promise<void>;
export declare function buildSocketDisconnectDeps(opts: {
    sessions: SentinelSessionSurface;
    escalate: EscalateFn;
}): SocketDisconnectSentinelDeps;
/**
 * Builds the ContextWedgeSentinel deps. The `recoverFn` encodes the
 * detect-only / dry-run / live recovery policy from autoRecovery config and
 * delegates the actual fresh respawn to the injected `freshRespawn` callback
 * (server bootstrap wires it to SessionRefresh.refreshSession({ fresh: true }),
 * which clears the topic's resume UUID so the new session never --resume-s the
 * corrupted transcript). Keeping policy here — not in the sentinel — means the
 * sentinel stays a pure detector + lifecycle and the rollout-staged flag is
 * observed in exactly one place.
 */
export declare function buildContextWedgeDeps(opts: {
    sessions: SentinelSessionSurface;
    escalate: EscalateFn;
    /** autoRecovery config (the Graduated-Feature-Rollout staged flag). */
    autoRecovery: {
        enabled: boolean;
        dryRun?: boolean;
    };
    /** Performs the destructive fresh respawn. Returns true if the session was
     *  actually killed + respawned. Wired to SessionRefresh fresh-mode. */
    freshRespawn: (sessionName: string) => Promise<boolean>;
}): ContextWedgeSentinelDeps;
/**
 * Detector: does this captured frame show the framework actively working
 * (spinner, tool call, "esc to interrupt", "(running)") rather than sitting
 * idle at its prompt? Used to filter the silence sentinel's candidate set so a
 * session that is simply waiting for the user is never flagged as "stopped".
 */
export declare function looksActivelyWorking(output: string, framework?: IntelligenceFramework): boolean;
/**
 * Stricter sibling of looksActivelyWorking (HONEST-PROGRESS-MESSAGING A1): is the
 * framework GENERATING RIGHT NOW? Uses only the live-generation markers
 * (`liveActivity` — the animated spinner / "Working (Ns" / "esc to interrupt"),
 * deliberately EXCLUDING `toolCallOrSpinner`'s scrollback-persistent tool names
 * which linger in an idle pane. This is the correct signal for the silence
 * sentinel's live-frame re-check: a frozen scrollback full of past `Read(`/`Bash(`
 * is NOT "generating now", but a still-rendered spinner / "esc to interrupt" IS.
 */
export declare function looksGeneratingNow(output: string, framework?: IntelligenceFramework): boolean;
/**
 * Tracks per-session output-change time so the silence sentinel can tell
 * "frozen mid-task" from "still producing output". Only sessions whose most
 * recent frame shows active-work signatures are surfaced as candidates; an
 * idle-at-prompt session is marked `paused` so the sentinel skips it — it is
 * not "actively working then stopped".
 *
 * Observed-change requirement (incident 2026-05-22): the tracker reports
 * `lastOutputAt: 0` for any session it has NOT yet watched produce a real
 * output change. A first sighting only records the baseline hash — it is never
 * treated as an output event. This is the critical discriminator between
 * "frozen mid-task" and "frozen since before I started watching": on a server
 * restart the tracker re-sees every leftover tmux session fresh, and a long-
 * dead session whose frozen last frame happens to contain "esc to interrupt"
 * would otherwise be misread as "was producing output at restart, then
 * stopped" — and flagged en masse 15 minutes later. By requiring an OBSERVED
 * active→silent transition (hash changes once, THEN stops), a session that was
 * already frozen before boot can never trip the silence threshold, because its
 * `lastOutputAt` stays 0 and the sentinel's `lastOutputAt <= 0` guard skips it.
 */
export declare class OutputActivityTracker {
    private readonly sessions;
    private readonly now;
    private readonly last;
    constructor(sessions: SentinelSessionSurface, now?: () => number);
    snapshot(): SessionRegistryEntry[];
    /**
     * Cached read of the LAST-computed output-change time for a session, WITHOUT
     * capturing (no tmux frame is read). Returns the `lastChangeAt` recorded by
     * the most recent `snapshot()` call, or null when the session has not been
     * observed yet OR its first sighting only recorded the baseline (lastChangeAt
     * is still 0 — i.e. no real output change has been observed).
     *
     * This is the AutonomousProgressHeartbeat's predicate #8 read (spec
     * §Shared-snapshot dependency): the heartbeat shares THIS tracker (the one
     * ActiveWorkSilenceSentinel ticks on its own 60s loop) and reads the value it
     * already computed — it NEVER captures its own frame. Returns null fails-closed
     * for the heartbeat (no emit).
     */
    lastOutputAtFor(sessionName: string): number | null;
}
export declare function buildActiveWorkSilenceDeps(opts: {
    tracker: OutputActivityTracker;
    sessions: SentinelSessionSurface;
    escalate: EscalateFn;
    /** Auto-recovery respawn primitive (DARK; only wired when autoRecover is on).
     *  Returns whether the respawn succeeded. */
    recoverFn?: (sessionName: string) => Promise<boolean>;
    /** Resolve a session to its Telegram topic, so silence/recovery notices land
     *  in the STALLED session's OWN topic (operator ask, 2026-06-09). */
    getTopicForSession?: (sessionName: string) => number | null | undefined;
    /** Deliver a notice to a specific topic. Returns success; never throws. */
    deliverToTopic?: (topicId: number, text: string) => Promise<boolean>;
    /** HONEST-PROGRESS-MESSAGING A1/A2 — capture the session's CURRENT live frame
     *  for corroboration before escalating. */
    captureFrame?: (sessionName: string) => string | null;
    /** A2(c) — does the session have a live sub-agent (SubagentTracker)? */
    hasActiveSubagents?: (sessionName: string) => boolean;
    /** Resolve the framework so the live-frame "generating now" check is accurate. */
    frameworkForSession?: (sessionName: string) => IntelligenceFramework | undefined;
    /** Observability funnel hook (E). */
    recordEvent?: (event: SilenceFunnelEvent, sessionName: string, detail?: string) => void;
}): ActiveWorkSilenceSentinelDeps;
export type RecoveryReachKind = 'recovery-reached' | 'recovery-unreachable';
/** The neutral "continue" nudge — NOT a compaction-resume payload (which would
 *  falsely tell the agent its memory was reset). */
export declare const RATE_LIMIT_RESUME_NUDGE = "The temporary server throttle should have cleared \u2014 please continue where you left off.";
export interface RateLimitRecoverySurface {
    isSessionAlive(sessionName: string): boolean;
    /**
     * Trusted internal nudge that bypasses the topic-prefix requirement.
     *
     * This is the ONLY channel the resume nudge is allowed to use. The resume
     * nudge is infrastructure poking the session to continue — it is NOT a user
     * message, so it must never carry a `[telegram:N]` prefix. A telegram-prefixed
     * nudge is byte-indistinguishable from a real message from the user, which
     * makes the agent answer it ("no throttle on my end") and relay that denial
     * back to the topic, contradicting the sentinel's own throttle notices.
     * (Incoherence incident 2026-06-05.)
     */
    injectInternalNudge(sessionName: string, text: string): boolean;
    getTopicForSession(sessionName: string): number | null | undefined;
    /** The always-available system topic; null during initial setup. */
    getLifelineTopicId(): number | null | undefined;
    /** Deliver a user-facing notice to a topic. Returns success; never throws. */
    deliverNotice(topicId: number, text: string): Promise<boolean>;
    /** Audit sink — invoked on EVERY recovery attempt. Never throws. */
    recordRecovery(kind: RecoveryReachKind, sessionName: string, detail: string, fallbackTried: string[]): void;
}
/**
 * Build the resume/notify functions for RateLimitSentinel. Both paths ALWAYS
 * record an audit event and never silently return:
 *  - resumeFn: ALWAYS internal inject (no `[telegram:N]` user-message prefix),
 *    topic-bound or not — the resume nudge is infrastructure, never a user turn.
 *  - notifyFn: session topic → lifeline topic → recovery-unreachable audit.
 */
export declare function buildRateLimitRecoveryDeps(s: RateLimitRecoverySurface): {
    resumeFn: (sessionName: string) => Promise<boolean>;
    notifyFn: (sessionName: string, text: string) => Promise<void>;
};
/** FNV-1a — enough to detect that captured output changed. Not security-sensitive. */
/**
 * Spinner-immune view of a captured pane for the change-detector hash. Removes
 * the host's animated working-status region — the rotating Braille glyph, the
 * ticking elapsed-time counter, the token/context counters, and the
 * `esc to interrupt` footer line — so only REAL scrollback content affects the
 * hash. Without this, the spinner's per-second clock made every poll look like
 * fresh output and blinded the silence sentinel to a stalled-but-spinning turn.
 * Conservative + anchored: real assistant/tool text is never stripped; unknown
 * frameworks still get the shared footer/glyph/timer stripping. (task #63)
 */
export declare function stripVolatileStatus(output: string, framework: Parameters<typeof getActivitySignal>[0]): string;
//# sourceMappingURL=sentinelWiring.d.ts.map