import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
export { getMeshV2BackstopCounters, __resetMeshV2BackstopCountersForTests } from './mesh-reconcile-v2-backstop.js';
export { __resetReconcileInFlightSynthDebounceForTests } from './mesh-reconcile-acked-hold.js';
/**
 * DRAIN-WITHOUT-INJECT guard. Classify, for a mesh on THIS daemon, whether a
 * queue-drain caller (the MCP `get_pending_mesh_events` poll) may safely consume
 * pending coordinator events — i.e. whether there is a surface that will actually
 * deliver them.
 *
 * Root cause being guarded: `get_pending_mesh_events` marks rows drained=1
 * atomically and unconditionally. When the live CLI coordinator for the mesh is
 * GENERATING (or modal-parked), the reconcile loop correctly HOLDS its terminal
 * events (drained=0) for the coordinator's next idle tick — but a concurrent MCP
 * poll draining the SAME queue consumes those held rows into a tool result that
 * the busy coordinator never surfaces as a turn, so the completion is lost
 * forever (drained=1, never re-queued). The reconcile loop is the authoritative
 * delivery path for a live CLI coordinator; the MCP poll must defer to it.
 *
 * Returns:
 *  - hasLiveCliCoordinator: a CLI session with meshCoordinatorFor === meshId
 *    exists on this daemon (the reconcile loop owns its delivery).
 *  - deliverableNow: there is an IDLE live CLI coordinator (reconcile would
 *    full-drain into it) — draining now is safe and equivalent.
 *  - holdForReconcile: a live CLI coordinator exists but is non-idle
 *    (generating / modal-parked). The MCP poll MUST NOT drain; the reconcile
 *    loop holds the events undrained and injects them on the next idle tick.
 *
 * A mesh with NO live CLI coordinator on this daemon is a pure stdio MCP / LLM
 * coordinator: the MCP tool result IS the only surface, so the poll legitimately
 * drains (holdForReconcile=false). No regression to that path.
 */
export declare function resolveCoordinatorDrainDeliverability(components: Pick<DaemonComponents, 'instanceManager'>, meshId: string): {
    hasLiveCliCoordinator: boolean;
    deliverableNow: boolean;
    holdForReconcile: boolean;
};
/**
 * DRAIN-WITHOUT-INJECT guard for the `get_pending_mesh_events` daemon handler.
 *
 * Decides whether an incoming pending-events DRAIN must be held (return nothing,
 * leave rows drained=0) because the only surface for those events is a LOCAL live
 * CLI coordinator that is currently busy (generating / modal-parked) — in which
 * case the reconcile loop owns delivery on the coordinator's next idle tick, and
 * the poll draining them now would lose them.
 *
 * The hold applies ONLY when BOTH:
 *   1) a live CLI coordinator for this mesh on THIS daemon is non-idle, AND
 *   2) the drain is targeted at THIS daemon (the requested coordinatorDaemonId is
 *      empty/broadcast, or matches one of this daemon's id forms).
 *
 * A REMOTE coordinator pulling our worker's events passes its own (remote)
 * coordinatorDaemonId — condition (2) is false — so the drain proceeds and the
 * remote pull is never blocked by our local coordinator's busy state. A pure
 * stdio MCP coordinator (no live CLI session) never satisfies (1), so its tool
 * result remains the surface and the drain proceeds. No regression to either.
 *
 * SELF-COORDINATOR INBOX LEVEL-DRAIN (Defect 2): the hold above assumes the ONLY
 * surface for a busy local coordinator's events is a future PTY inject on its idle
 * edge, so it defers to the reconcile loop. But when the drain caller IS the local
 * coordinator reading its OWN inbox (the `get_pending_mesh_events` call whose events
 * are returned in the caller's tool RESULT — a data queue the self-coordinating LLM
 * consumes directly), the events ARE surfaced losslessly the moment the tool returns,
 * with NO PTY write. A busy self-coordinating LLM that calls a mesh tool mid-turn would
 * otherwise get an empty inbox (held) and only see the completion on its NEXT busy→idle
 * edge — the measured ~59s strand. `callerIsSelfCoordinatorInboxRead` marks that safe
 * caller: the hold is relaxed for it (return the events), while every OTHER drain (a
 * backfill relay, a broadcast poll, a DIFFERENT coordinator that genuinely needs its PTY)
 * still defers to the reconcile loop. This relaxes delivery INTO the coordinator's own
 * inbox only — it never changes how events are injected into a live PTY prompt.
 */
export declare function shouldHoldPendingDrainForBusyLocalCoordinator(components: Pick<DaemonComponents, 'instanceManager'> & {
    statusInstanceId?: string;
}, meshId: string, requestedCoordinatorDaemonId?: string | null, callerIsSelfCoordinatorInboxRead?: boolean): boolean;
export declare function __resetReclaimUnknownStreakForTests(): void;
export declare function reconcileZombieAssignedTasks(components: DaemonComponents, mesh: {
    id: string;
    nodes?: unknown[];
}, selfIds: string[]): void;
export declare function runMeshReconcileTick(components: DaemonComponents): Promise<void>;
export declare function __resetUnresolvedForwardRejectionCountsForTests(): void;
interface ReconcileLoopHandle {
    stop(): void;
}
export declare function setupMeshReconcileLoop(components: DaemonComponents): ReconcileLoopHandle;
