/**
 * OrcDeliveryEnforcerService
 *
 * **Why this exists** (2026-05-23 incident, the "affiliate research" case):
 *
 * Steve asked the team to research affiliate programs at 12:59 PM. ORC
 * acknowledged at 1:00 PM with "Atlas + Sage/Kai 派出去了, ETA 30-40min".
 * Sage finished Phase 1 at 1:15, Kai finished Phase 2 at 1:28 — but
 * Steve never saw the result. ORC's terminal logs claimed the pipeline
 * was "closed" and the result "already routed to Steve at Slack thread
 * ...", but there were zero `/api/slack/send` calls all day. ORC's
 * internal mental model believed delivery happened; the actual skill
 * call didn't.
 *
 * Root cause: ORC's prompt has a HARD pre-yield gate ("any `[CHAT:slack-…]`
 * MUST have a `reply-slack` before yielding"). But for a long-running
 * delegation, the user-visible deliverable comes from the *agent* via
 * `[DONE]`-style status reports — long AFTER ORC's initial ack. ORC
 * reads the agent's `[DONE]` as internal team chatter, narrates "pipeline
 * closed", and yields without delivering to Steve. The prompt rule
 * doesn't catch this case because the prompt looks for `[CHAT:slack-…]`
 * (user-originating) not `[DONE]` (agent-originating).
 *
 * **What this service does:**
 *
 *   1. When a worker agent posts `[DONE]` / `[COMPLETED]` to a
 *      slack-prefixed conversation (chat.controller's "Agent status
 *      routed to orchestrator" path), call {@link markPendingDelivery}.
 *      The service records a pending delivery with `dueBy = now + 3 min`.
 *
 *   2. When ORC successfully posts via `/api/slack/send` to the same
 *      thread, call {@link markDelivered}. Pending delivery cleared.
 *
 *   3. A timer scans every 30 s. For any pending delivery whose `dueBy`
 *      has passed AND ORC hasn't replied, enqueue a `[DELIVER_REQUIRED]`
 *      system message to ORC via MessageQueueService. ORC's prompt
 *      treats `[DELIVER_REQUIRED]` exactly like `[CHAT:slack-…]` for
 *      the pre-yield gate.
 *
 *   4. Exponential backoff between reminders for the same thread, to
 *      avoid spamming ORC if it's genuinely stuck or rate-limited.
 *      First reminder at 3 min, then 10 min, then 30 min, then stop.
 *
 * Fire-and-forget on all writes — a fault in the enforcer MUST NOT
 * block chat or slack flow.
 *
 * @module services/orc/orc-delivery-enforcer.service
 */
/**
 * Identifier for a slack thread — channelId + threadTs (or empty if
 * not a slack conversation, in which case we skip tracking entirely).
 */
export interface SlackThreadKey {
    channelId: string;
    threadTs: string;
}
export interface PendingDelivery {
    key: SlackThreadKey;
    /** The full conversation id, e.g. `slack-D0AC…-1779…`. Stored for the reminder text. */
    conversationId: string;
    /** Session name of the agent that posted [DONE]. */
    agentSender: string;
    /** First ~200 chars of the agent's [DONE] message — surfaces in the reminder. */
    deliverableSummary: string;
    /** When the agent posted (ms). */
    doneAt: number;
    /** Number of reminders fired so far (drives REMINDER_CADENCE_MS index). */
    remindersFired: number;
    /** Next eligible reminder time (ms). */
    nextDueAt: number;
}
/**
 * Function the enforcer uses to send a `[DELIVER_REQUIRED]` message to
 * ORC. Injected at construction time so tests can stub it and so the
 * service has no hard dep on MessageQueueService.
 */
export type DeliveryReminderSink = (params: {
    conversationId: string;
    text: string;
}) => void;
export interface OrcDeliveryEnforcerOptions {
    /** Wired from index.ts to enqueue the reminder via MessageQueueService. */
    reminderSink: DeliveryReminderSink;
    /** Scan interval. Default 30 s. */
    tickIntervalMs?: number;
}
/**
 * Singleton scope — there's one ORC, one queue of pending deliveries.
 */
export declare class OrcDeliveryEnforcerService {
    private static instance;
    private readonly logger;
    private readonly reminderSink;
    private readonly tickIntervalMs;
    private timer;
    /** key = `${channelId}::${threadTs}` */
    private readonly pending;
    constructor(opts: OrcDeliveryEnforcerOptions);
    static getInstance(): OrcDeliveryEnforcerService | null;
    static setInstance(next: OrcDeliveryEnforcerService | null): void;
    /** Start the periodic scan. Idempotent. */
    start(): void;
    stop(): void;
    /**
     * Call this from `chat.controller` when an agent posts a `[DONE]`-style
     * status to a slack-prefixed conversation. Safe to call multiple times
     * for the same thread — the most recent agent message wins (we
     * surface only the latest deliverable summary in the reminder).
     */
    markPendingDelivery(input: {
        conversationId: string;
        agentSender: string;
        text: string;
    }): void;
    /**
     * Call this from `slack.controller` after a successful `/api/slack/send`.
     * Cleared regardless of which agent originated the underlying deliverable.
     */
    markDelivered(input: {
        channelId: string;
        threadTs?: string | null;
    }): void;
    /** Test affordance — peek at the in-memory ledger. */
    _peekPending(): PendingDelivery[];
    /**
     * Public for tests + the manual-trigger endpoint. Otherwise driven by
     * the internal timer.
     */
    tick(now?: number): {
        firedFor: string[];
    };
    private formatReminder;
}
export declare function isAgentDeliveryMarker(text: string): boolean;
/**
 * Parse a chat-v2 conversation id of the form `slack-<channelId>-<ts>`
 * into { channelId, threadTs }. Returns null for non-slack ids. Mirrors
 * the regex in `request-sla.subscriber` so the enforcer agrees with the
 * SLA layer on what "the same thread" means.
 */
export declare function parseSlackConversationId(conversationId: string): SlackThreadKey | null;
//# sourceMappingURL=orc-delivery-enforcer.service.d.ts.map