import type { ButtonCallback } from './types.js';
interface PendingRetry {
    jobId: number;
    agent: string;
    prompt: string;
    threadKey: string;
    platform: string;
    channelId: string;
    threadId: string;
    creatorId: string;
    /** ms-epoch when the entry expires (interrupted_at + window). */
    expiresAt: number;
    /** v1.2 — id of the choice card we sent for this entry (if the adapter
     *  supported sendChoiceCard). Used to edit the card to its terminal text
     *  after the user picks a button or replies via text. Null when we fell
     *  back to plain text. */
    cardMessageId: string | null;
}
/**
 * v1.2 — executor hook injected by cli.ts at boot. When the user accepts
 * a retry (via text "1" OR button "🔁 重发"), the new inline-job row is
 * created here but the agent never runs unless we feed a synthetic
 * MessageContext through cli's handleMessage. cli.ts owns that closure
 * (it has the registry, sink, agent dispatcher) so we register the
 * callback here and invoke it after a successful retry.
 *
 * Without this hook the row stays in 'pending' forever. That bug
 * affected the button path only (text path was working via the
 * messenger's own onMessage chain).
 */
export interface RecoveryExecutorArgs {
    entry: PendingRetry;
    newJobId: number;
}
export type RecoveryExecutor = (args: RecoveryExecutorArgs) => Promise<void>;
export declare function setRecoveryExecutor(fn: RecoveryExecutor | null): void;
/** Test hook — wipe the in-memory state. */
export declare function _resetPendingForTests(): void;
/** Test hook — inspect current state. */
export declare function _peekPendingForTests(): Map<string, PendingRetry>;
/** Run once on agim startup, after messengers + sink are up. */
export declare function scanInterruptedAndNotify(): Promise<{
    notified: number;
    abandoned: number;
}>;
/** Periodic in-memory expiry sweep. Removes entries whose `expiresAt` has
 *  passed without a user reply. Called from cli.ts on a setInterval. */
export declare function sweepExpiredPending(now?: number): number;
/** Result of a recovery-reply attempt. */
export type RecoveryReplyOutcome = {
    kind: 'not-pending';
} | {
    kind: 'not-recovery-reply';
} | {
    kind: 'retried';
    newJobId: number;
    oldJobId: number;
    entry: PendingRetry;
} | {
    kind: 'cancelled';
    oldJobId: number;
};
/** Try to interpret a user's reply as a recovery decision. Returns an
 *  outcome the caller can render. Side-effect: on a recognized decision,
 *  the pending entry is removed and the DB row transitions. When the
 *  entry was originally sent as a native choice card, this also edits
 *  the card to its terminal state so its buttons can't be tapped after
 *  the user already replied with text — otherwise a stale tap would
 *  hit the "请求已过期" branch and read as a glitch. */
export declare function tryHandleRecoveryReply(threadKey: string, text: string): RecoveryReplyOutcome;
/** Convenience for cli.ts: was the thread expecting a recovery reply just
 *  before we routed this message? Used to skip approval / reminder
 *  interceptors when the user is replying to a recovery prompt. */
export declare function hasPendingRecovery(threadKey: string): boolean;
/**
 * True if the given callback_data belongs to a recovery prompt — used by
 * approval-router's button dispatcher to route the tap to us instead of
 * the approval flow.
 */
export declare function isRecoveryButtonData(data: string): boolean;
/**
 * Handle a button tap on a recovery card. Mirrors tryHandleRecoveryReply's
 * semantics: returns the outcome the caller can act on. Also edits the
 * card (drops the buttons) so a stale tap can't fire twice.
 */
export declare function handleRecoveryButton(platform: string, cb: ButtonCallback): Promise<RecoveryReplyOutcome | null>;
export {};
//# sourceMappingURL=job-recovery.d.ts.map