import type { DaemonComponents } from '../boot/daemon-lifecycle.js';
import type { RepoMeshSchedulingStrategy } from '../repo-mesh-types.js';
/** Whether the assignment path currently holds an ff lease on this node's workspace —
 *  used to skip a task claim while an auto ff is mutating the same workspace. */
export declare function isWorkspaceAutoFastForwardInFlight(workspace: string | undefined): boolean;
export declare function __resetIdleAutoFastForwardForTests(): void;
export declare function getMeshWithCache(components: DaemonComponents, meshId: string): any | undefined;
export declare function tryAssignQueueTask(components: DaemonComponents, meshId: string, nodeId: string, sessionId: string, providerType: string): boolean;
export declare const AUTO_LAUNCH_AWAIT_CLAIM_MS = 90000;
interface AwaitClaimBackoffState {
    cycles: number;
    nextAttemptAtMs: number;
}
export declare function __resetAutoLaunchAwaitClaimBackoffForTests(): void;
export declare function __seedAutoLaunchAwaitClaimBackoffForTests(meshId: string, taskId: string, state: AwaitClaimBackoffState): void;
/** Active assignments that hold the one-active-per-node / global-parallel invariant
 *  (everything except read-only diagnoses, which run unbounded by the write cap). */
export declare function activeWriteAssignedCount(meshId: string): number;
/** Active read-only assignments, for the read-only safety cap. */
export declare function activeReadonlyAssignedCount(meshId: string): number;
/**
 * Order eligible nodes for assignment per the mesh scheduling pipeline:
 *   PRIORITY (schedulingPriority desc) → TIE-BREAK (strategy).
 *
 * The caller has already applied the TAG hard-filter and is responsible for the
 * MAX-ALLOC capacity gate (the per-node launch/claim checks). This function only
 * decides the *preference order* among nodes that are otherwise eligible.
 *
 * - 'first_eligible' (default): returns the input order verbatim and does NOT touch
 *   the round-robin cursor — byte-for-byte the pre-feature behavior.
 * - 'priority_only': schedulingPriority desc, then input order (load ignored).
 * - 'least_loaded' (the user-facing 'spread' mode): schedulingPriority desc, then
 *   active load asc, then — among nodes still tied at (priority, load) — the input
 *   order is rotated by a per-mesh cursor that advances once per pass. The rotation
 *   tie-break is ALWAYS on for least_loaded so a single 'spread' mode subsumes the
 *   former separate 'round_robin' strategy (two equal-load nodes still alternate
 *   fairly across passes instead of the lower index always winning).
 * - 'round_robin': retained as an escape-hatch alias; behaves identically to
 *   'least_loaded' now that least_loaded carries the rotation.
 *
 * `nodes` carries the original config/array index so the tie-break can fall back to
 * deterministic input order. `bumpCursor` advances the round-robin cursor exactly
 * once per scheduling pass (consulted for both 'least_loaded' and 'round_robin').
 */
interface RankableNode {
    nodeId: string;
    node: any;
    index: number;
}
/** Test-only: resolve a mesh's effective scheduling strategy through the full
 *  LOCAL-WINS path (`.adhdev/mesh.json` distribution → strategy, else stored policy).
 *  Exposed so the repo-file overlay wiring can be exercised by a direct call. */
export declare function __resolveSchedulingStrategyForTests(mesh: any): RepoMeshSchedulingStrategy;
/** Test-only: the pure node-ordering stage (PRIORITY → TIE-BREAK). Exposed so the
 *  scheduling pipeline can be unit-tested without standing up live CLI sessions. */
export declare function __orderEligibleNodesForTests(meshId: string, strategy: RepoMeshSchedulingStrategy, nodes: RankableNode[], opts?: {
    bumpCursor?: boolean;
    task?: {
        difficulty?: string;
        requiredTags?: string[];
    };
}): RankableNode[];
/** One idle session eligible to claim a queued task, together with the resolved
 *  mesh node record it belongs to. Local candidates come from live CLI instances,
 *  remote candidates from the registered remote-idle-session store; the two arrive
 *  with their `nodeId` under different serialization forms. */
type IdleCandidate = {
    nodeId: string;
    sessionId: string;
    providerType: string;
    origin: 'local' | 'remote';
    node: any;
};
/** Test-only: the pool-canonicalization + unique-node collapse stage. Exposed so
 *  the mixed-form dedup invariant can be unit-tested without a live daemon. */
export declare function __buildSchedulingPoolForTests(localCandidates: IdleCandidate[], remoteCandidates: IdleCandidate[]): {
    pool: IdleCandidate[];
    uniqueNodes: RankableNode[];
};
export declare function sessionHasActiveAssignment(meshId: string, sessionId: string): boolean;
/**
 * CANON-IDENTITY single-flight hardening (restart-safe, observation-based).
 *
 * The in-memory single-flight Set (mesh-task-inflight) is process-local and is LOST on a
 * daemon restart — after a restart, a task still being generated by a live local worker is
 * no longer marked in-flight, so requeueTask's Set check passes and would re-open the task
 * for a duplicate second dispatch. This recovers the "still generating" signal from
 * observable runtime state instead of the in-memory mark: a session is actively generating
 * when its live local CLI instance reports an active (generating/streaming/…) status — the
 * same predicate the dispatch active-work gate uses (sessionStateLooksActive).
 *
 * Local-only by design: it inspects THIS daemon's instanceManager. The primary cross-process
 * fix (IpcTransport requeue delegating to the mesh-host daemon) keeps begin (dispatch) and
 * check (requeue guard) co-located so the in-memory mark stays authoritative in the common
 * path; this is the restart-safety net for sessions hosted on this daemon. A genuinely
 * dead/stale session is not generating → returns false → the requeue proceeds as before.
 */
export declare function isSessionActivelyGenerating(components: DaemonComponents, sessionId: string): boolean;
/**
 * RECLAIM-FALSEPOS tri-state busy verdict for a session id.
 *
 * The binary isSessionActivelyGenerating() folds "absence of a positive generating
 * signal" into a definitive NEGATIVE (returns false when the instance is absent). But a
 * REMOTE session (never in THIS daemon's instanceManager) — or a locally-present session
 * looked up under a skewed id form — then looks "not generating" and can be reclaimed out
 * from under a worker that is genuinely mid-turn. This resolves an explicit three-way
 * verdict instead:
 *   - GENERATING       — a locally-present instance reports an active/streaming state.
 *   - IDLE_CONFIRMED    — a locally-present instance reports a non-active (idle/terminal)
 *                         state. Positive local evidence the worker is not working.
 *   - UNKNOWN           — no locally-present instance matches (remote / gone / id-skew) or
 *                         the observation failed. NEVER treated as IDLE_CONFIRMED.
 *
 * The lookup scans getByCategory('cli') with sessionIdsEquivalent (the same equivalence
 * matching nodeHasActiveMeshWork / liveSessionCountForNode use) rather than a raw
 * instanceManager.getInstance(id) Map.get, so an id-form-skewed but present session is
 * found (closing the same id-form-skew hole class e245c2f9's F1 fixed elsewhere).
 */
export type SessionBusyVerdict = 'GENERATING' | 'IDLE_CONFIRMED' | 'UNKNOWN';
export declare function resolveSessionBusyVerdict(components: DaemonComponents, sessionId: string): SessionBusyVerdict;
export interface MeshQueueTriggerResult {
    success: true;
    meshId: string;
    pendingBefore: number;
    assignedBefore: number;
    pendingAfter: number;
    assignedAfter: number;
    claimed: boolean;
    newlyAssignedTasks: Array<{
        id: string;
        nodeId?: string;
        sessionId?: string;
    }>;
    localIdleSessionsChecked: number;
    remoteIdleSessionsChecked: number;
    skippedSessions: Array<{
        nodeId?: string;
        sessionId?: string;
        reason: string;
        status?: string;
    }>;
    autoLaunchStarted: boolean;
    /**
     * True when a worker session is already on its way to claim a still-pending task —
     * either launched this tick (autoLaunchStarted) or launched on a prior tick and still
     * booting/awaiting-claim. Callers MUST treat this as "wait, do not launch another
     * session": a second launch double-edits the worktree. Mutually informative with
     * `noIdleMeshSessionAvailable`, which is suppressed whenever this is true.
     */
    autoLaunchPending?: boolean;
    noIdleMeshSessionAvailable?: boolean;
}
export declare function triggerMeshQueue(components: DaemonComponents, meshId: string): Promise<MeshQueueTriggerResult>;
export declare function maybeAutoFastForwardIdleNode(components: DaemonComponents, args: {
    meshId: string;
    nodeId: string;
    sessionId?: string;
    providerType?: string;
}): Promise<void>;
/**
 * Continuous-mode remote auto fast-forward scan (mode:"continuous" only). Called by
 * the reconcile tick BEFORE queue claim. Scans every connected, eligible, non-worktree
 * remote node of every mesh this daemon hosts and delegates an ff to its owning daemon
 * when it is online/clean/behind within policy. Per-node cooldown + the workspace lease
 * keep the ~4s reconcile cadence from hammering peers or racing an assignment.
 *
 * Ephemeral worktree nodes are DELIBERATELY excluded from the continuous catch-up: a
 * Refinery worktree branch must not be silently advanced by a background scan (only its
 * own idle-edge ff, which the coordinator drives intentionally). Non-worktree base
 * nodes are the sole continuous target.
 */
export declare function runContinuousAutoFastForwardScan(components: DaemonComponents, mesh: any): Promise<void>;
/**
 * DS3: drain and act on `coordinator_catchup` markers queued by a remote node's Refinery
 * after it pushed the base branch to origin. The originating coordinator is THIS daemon;
 * its local base checkout is now behind origin. Bring it up to date with a guarded ff-only
 * merge — but ONLY when the coordinator base node has no active mesh work (busy → leave the
 * marker for the next idle tick) and fastForwardMeshNode's own clean/ahead=0/behind>0 gate
 * is satisfied (ahead/diverged/dirty → it returns a structured block, never a rebase).
 *
 * These markers are drained on a DEDICATED event-name filter so they never reach the
 * coordinator chat-injection path (they are actions, not messages). A busy/blocked node
 * re-queues the marker so a later idle tick retries; a successful/no-op ff consumes it.
 */
export declare function runPendingCoordinatorCatchupScan(components: DaemonComponents, mesh: any): Promise<void>;
export declare function runIdleMaintenanceThenAssignQueue(components: DaemonComponents, args: {
    meshId: string;
    nodeId: string;
    sessionId: string;
    providerType: string;
}): void;
export {};
