/**
 * TopicIntentCapture — the adapter-agnostic capture step that fills the
 * topic-intent store from live conversation (rung 0 of the Continuous Working
 * Awareness north star). Spec: docs/specs/topic-intent-capture-loop.md.
 *
 * This is the "clerk" that was missing: the store, briefing, and ArcCheck all
 * shipped, but nothing ever invoked `ingest()` on a real turn, so the cabinet
 * stayed empty (the textbook "shipped but asleep" bug). This module is the
 * single seam that watches each turn and files what matters.
 *
 * Design invariants (carried from the human-as-detector build + spec):
 *   - Best-effort, NEVER throws into the message/delivery path (acceptance #4).
 *   - Fire-and-forget: extraction runs off the delivery path; capture latency
 *     can never slow a message reaching the user.
 *   - Degrade-safe: no provider, cap breach, or provider error → a counter tick
 *     and a silent no-op, never an error.
 *   - Framework-agnostic: Telegram is merely the first wiring; the helper takes
 *     a generic entry, not a Telegram type.
 *
 * The pre-filter (`isSubstantiveTurn`) is a STATE-DETECTOR per
 * `[[feedback_state_detection_robustness]]`: deterministic + fail-open, shipped
 * with a canary (`runPreFilterCanary`) and registered in
 * docs/specs/06-state-detector-registry.md. Its silent failure mode is
 * sentinel-format drift → over-skip → real captures dropped → the original
 * "no record for the topic" bug recurs.
 */
import type { IntelligenceProvider } from './types.js';
import type { TopicIntentStore } from './TopicIntent.js';
import type { TopicIntentExtractor } from './TopicIntentExtractor.js';
/**
 * Decide whether a turn is worth sending to the extractor. Deterministic and
 * FAIL-OPEN: returns true (capture) unless the turn is high-confidence trivial.
 *
 * Skips: empty / whitespace-only; agent sentinel/heartbeat/proxy lines; short
 * bare acknowledgements. Everything else passes to the LLM, which makes the
 * real significance call with broader context.
 */
export declare function isSubstantiveTurn(text: string | undefined | null, fromUser: boolean): boolean;
export interface CanarySample {
    text: string;
    fromUser: boolean;
    /** Expected pre-filter verdict: true = should pass to LLM, false = should skip. */
    expectSubstantive: boolean;
    label: string;
}
/**
 * Known-good samples the pre-filter MUST classify correctly. If a code change
 * (or sentinel-format drift) breaks one of these, the canary fails loudly so we
 * notice BEFORE real captures are silently dropped.
 */
export declare const PRE_FILTER_CANARY_SAMPLES: CanarySample[];
export interface CanaryResult {
    ok: boolean;
    failures: Array<{
        label: string;
        expected: boolean;
        got: boolean;
    }>;
}
/** Run the pre-filter canary over the known samples. */
export declare function runPreFilterCanary(): CanaryResult;
export type EnqueueFn = (lane: 'interactive' | 'background', fn: (signal: AbortSignal) => Promise<string>, costCents?: number) => Promise<string>;
/** Fast-tier per-turn cost estimate (cents) for the daily-cap accounting. */
export declare const TOPIC_INTENT_CAPTURE_COST_CENTS = 0.2;
/**
 * Wrap an IntelligenceProvider so every call is admitted through the shared
 * LlmQueue (background lane: capture is never user-interactive, and must yield
 * to interactive work). The TRANSPORT stays the injected `intelligence` — which
 * production resolves to the subscription / REPL-pool provider — so this never
 * touches the raw Messages API (`[[feedback_anthropic_path_constraints]]`,
 * acceptance #6). On daily-cap breach the queue THROWS; createLlmExtractFn
 * catches it and degrades to a counter tick.
 */
export declare function createQueuedIntelligence(intelligence: IntelligenceProvider, enqueue: EnqueueFn, costCents?: number): IntelligenceProvider;
export interface CaptureTurnEntry {
    /** Server-assigned, non-user-forgeable message id (keys per-message dedup). */
    messageId: string | number;
    topicId?: number;
    text?: string;
    fromUser: boolean;
    /** ISO8601 string or epoch ms. */
    timestamp?: string | number;
}
/** Minimal rolling-summary surface (satisfied by TopicMemory). */
export interface RollingSummaryProvider {
    getTopicSummary(topicId: number): {
        summary: string;
    } | null;
}
export interface CaptureLoopDeps {
    extractor: TopicIntentExtractor;
    store: TopicIntentStore;
    /** Source of the rolling summary fed to the extractor (broader context). */
    topicMemory?: RollingSummaryProvider | null;
    /** Skip capture under quota pressure (QuotaTracker load-shedding). */
    shouldShed?: () => boolean;
    /** Per-topic extraction rate ceiling (defense beyond the pre-filter). */
    rateCeiling?: {
        maxPerWindow: number;
        windowMs: number;
    };
    now?: () => number;
}
export type CaptureStatus = 'captured' | 'skipped-prefilter' | 'skipped-shed' | 'skipped-rate' | 'no-topic' | 'degraded';
export interface CaptureOutcome {
    status: CaptureStatus;
    emitted?: number;
    createdRefs?: number;
}
/**
 * Capture one conversation turn. Pre-filter → (shed/rate gates) → build broader
 * context → extractor.ingest. Never throws; on any unexpected failure it ticks
 * `degraded_cap_or_error` and returns { status: 'degraded' }.
 *
 * `rateState` (topicId → recent attempt timestamps) is owned by the caller
 * (see createCaptureLoop) so the rate ceiling persists across turns.
 */
export declare function captureTurn(deps: CaptureLoopDeps, entry: CaptureTurnEntry, rateState?: Map<number, number[]>): Promise<CaptureOutcome>;
/**
 * Build a stateful capture closure. The returned function holds the per-topic
 * rate-limit state across turns; wire it onto the inbound message callback.
 */
export declare function createCaptureLoop(deps: CaptureLoopDeps): (entry: CaptureTurnEntry) => Promise<CaptureOutcome>;
//# sourceMappingURL=TopicIntentCapture.d.ts.map