/**
 * Mesh node-identity & git-freshness helpers
 *
 * Extracted from commands/router.ts (behavior-preserving move). Contains:
 *   - daemon-id / machine-id / hostname normalization + canonicalization
 *   - inline-mesh node identity reconciliation and transient-state handling
 *   - git-status normalization, branch-convergence, and freshness/staleness derivation
 *   - the direct git-probe cache and remote git-status probe machinery
 *
 * router.ts re-exports every public symbol from here so existing import paths
 * (e.g. `from '.../commands/router.js'`) keep working.
 */
import type { ProviderLoader } from '../providers/provider-loader.js';
import type { MeshReportedMemberState } from '../repo-mesh-types.js';
import { awaitWithWarmupDeadline } from '../mesh/mesh-warmup-deadline.js';
export declare function readProviderPriorityFromPolicy(policy: unknown): string[];
/**
 * Normalize a providerRoles array (RepoMeshNodePolicy.providerRoles) from raw
 * tool args. Each entry binds a providerType to an optional `maxParallel` cap.
 * Entries without a usable providerType are dropped; the last entry wins on
 * duplicate providerType. Returns [] when no valid entries — callers then omit
 * the field entirely (full backward compat). Routing is governed by required_tags;
 * any legacy `role` field on the input is ignored.
 */
export declare function normalizeProviderRoles(value: unknown): Array<{
    providerType: string;
    maxParallel?: number;
}>;
export declare function readObjectRecord(value: unknown): Record<string, any>;
export declare function readStringValue(...values: unknown[]): string | undefined;
export declare function readBooleanValue(...values: unknown[]): boolean | undefined;
export declare function summarizeRepoMeshStatusDebug(status: any): Record<string, unknown>;
export declare function logRepoMeshStatusDebug(event: string, fields: Record<string, unknown>): void;
export declare function buildMeshNodeDisplayLabel(node: Record<string, unknown>, nodeId: string, providerPriority: string[]): string;
export declare function readMeshNodeMachineId(node: Record<string, unknown>): string | undefined;
export declare function readMeshNodeDaemonId(node: Record<string, unknown>): string | undefined;
export declare function readMeshNodeHostname(node: Record<string, unknown>): string | undefined;
export declare function buildMeshNodeMachineIdentity(node: Record<string, unknown>, opts: {
    localMachineId?: string;
    localDaemonId?: string;
    coordinatorHostname?: string;
    isSelfNode?: boolean;
}): Record<string, unknown>;
export declare function buildInlineMeshTransitGitStatus(node: any): Record<string, unknown> | undefined;
export declare function shouldRefreshStalePendingAggregate(snapshot: any, options?: {
    requireDirectPeerTruth?: boolean;
}): boolean;
export declare function buildLivePeerGitConnection(connection: Record<string, unknown>, timestamp?: string): Record<string, unknown>;
export declare function recordInlineMeshDirectGitTruth(node: any, git: Record<string, unknown>, source: 'selected_coordinator_local_git' | 'selected_coordinator_mesh_p2p_git'): {
    reporterPlatform: string | null;
    reporterArch: string | null;
    reporterMachineNickname: string | null;
    reporterProviderVersions: Record<string, string> | null;
    reporterDaemonBuildVersion: string | null;
    reportedMemberState: MeshReportedMemberState | null;
};
/**
 * Persist the live self-reported platform/arch onto the local meshes.json node
 * record so capability-tag os=/arch= self-heals across coordinator restarts.
 *
 * The in-memory stamp done by recordInlineMeshDirectGitTruth lives on the
 * mesh_status assembly object and is discarded after the response; only a
 * `local_config` mesh has a backing meshes.json node to write through to. Inline
 * cache/bootstrap meshes have no local node to update, so we no-op for them.
 * Fire-and-forget (same pattern as the worktreeBootstrap writer) — a persistence
 * failure must never block the status response.
 */
export declare function persistNodeReporterPlatform(meshSource: 'inline_cache' | 'inline_bootstrap' | 'local_config', mesh: any, nodeId: string | undefined, reporter: {
    reporterPlatform: string | null;
    reporterArch: string | null;
    reporterMachineNickname?: string | null;
    reporterProviderVersions?: Record<string, string> | null;
    reporterDaemonBuildVersion?: string | null;
    reportedMemberState?: MeshReportedMemberState | null;
}): void;
export declare function inlineMeshCarriesTransientNodeTruth(inlineMesh: any): boolean;
export declare function readInlineMeshNodeId(node: any): string;
export declare function isDeadLocalWorktreeNode(node: any): boolean;
export declare function foldMeshNodeIdentityToCanonical(node: any): any;
export declare function normalizeInlineMeshNodeIdentity(inlineMesh: any): any;
export declare function sanitizeInlineMesh(inlineMesh: any): any;
export declare function reconcileInlineMeshCache(cached: any, incoming: any): any;
export declare function deriveMeshNodeHealthFromGit(git: Record<string, unknown> | null | undefined): 'online' | 'dirty' | 'degraded';
/**
 * Resolve a node's EFFECTIVE health from whatever telemetry the node object carries,
 * in the same precedence the coordinator surfaces use (applyCachedInlineMeshNodeStatus):
 *   1. an explicit `node.health` scalar (set by a fresh mesh_status probe / status report),
 *   2. else the cached inline status health (`node.cachedStatus.health`),
 *   3. else derived from the node's git telemetry (`node.git` / cachedStatus.git) via
 *      deriveMeshNodeHealthFromGit,
 *   4. else 'unknown' (no telemetry — cannot prove unhealthy).
 *
 * This is the SINGLE source of truth for "what is this node's health right now" shared by
 * the auto-launch gate (isMeshNodeHealthLaunchable → isLaunchableNode) and the MAGI fan-out
 * planner, so the two never disagree about whether a degraded node is a viable target.
 * Returns a lowercased string (empty string is normalized to 'unknown').
 */
export declare function resolveEffectiveMeshNodeHealth(node: any): string;
/**
 * Whether a node's health permits launching / assigning a fresh worker session onto it.
 * Mirrors the auto-launch gate in mesh-queue-assignment.isLaunchableNode: 'online' and
 * 'unknown' (and an absent/empty health, treated as unknown) pass — we never block on
 * missing telemetry; every other resolved health ('degraded', 'offline', 'dirty',
 * 'wrong_branch') is NOT launchable. A task assigned to a non-launchable node parks in
 * `pending` forever (isLaunchableNode skips it → node_not_launch_ready) with no
 * re-assignment, so the MAGI planner must exclude such nodes UP FRONT rather than emit a
 * replica that can never run.
 */
export declare function isMeshNodeHealthLaunchable(node: any): boolean;
export declare function applyInlineMeshBranchConvergence(mesh: any, node: any, status: Record<string, unknown>): void;
export declare function summarizeInlineMeshBranchConvergence(nodes: Array<Record<string, unknown>>): Record<string, unknown>;
export declare function readCachedInlineMeshActiveSessions(node: any): string[];
/**
 * Wider session-ownership scan used ONLY by resolveRemoteMeshSessionOwnerDaemonId: collect
 * EVERY session id a mesh node currently hosts. readCachedInlineMeshActiveSessions above only
 * surfaces the node's single primary session (cachedStatus.activeSession) — other consumers
 * depend on that one-session semantics, so it is left untouched. A worker hosting more than one
 * session exposes its non-primary sessions only through the plural live-session arrays the
 * coordinator carries: status.activeSessions / activeSessionDetails (built from live records on
 * the aggregate snapshot, see get_mesh_status), or a worker's merged session report. A
 * controlbar/modal command (invoke_provider_script / resolve_action / set_mode / …) targeting a
 * non-primary remote session resolves its owner daemon only when those plural shapes are scanned
 * too. Mirrors sessionStatusFromNodes' shape tolerance (mesh-active-work.ts): plural arrays of
 * string ids OR objects keyed by id/sessionId/session_id/runtimeSessionId/instanceId, on both
 * camelCase and snake_case, at the node root and under cachedStatus / lastProbe.
 */
export declare function collectMeshNodeHostedSessionIds(node: any): Set<string>;
/**
 * Resolve the owning-node attribution for a mesh node record so a coordinator can
 * stamp the TRUE owner onto a synthetic session entry instead of letting the
 * dashboard fall back to the coordinator's own daemonId. Returns whichever of the
 * owning node's `daemonId` / display machine name could be read from the node's
 * (possibly multi-serialization-path) shape; both may be undefined for a node that
 * never carried machine identity.
 */
export declare function resolveMeshNodeAttribution(node: unknown): {
    daemonId?: string;
    machineName?: string;
};
export declare function readCachedInlineMeshActiveSessionDetails(node: any): Array<Record<string, unknown>>;
/**
 * Transient per-node marker the mesh_status render loop stamps onto a node
 * `status` at the two sites that obtain git truth from a FRESH probe this call
 * (a successful local `getGitRepoStatus`, or a successful P2P `git_status`
 * round-trip). finalizeMeshNodeStatus consumes and deletes it. Held/standing
 * truth (node.lastGit / cachedStatus / inline transit) is deliberately NOT
 * stamped — its absence is exactly how the freshness marker tells "live" apart
 * from "cached". Internal only; never serialized in the response.
 */
export declare const MESH_NODE_LIVE_TRUTH_MARKER = "__liveTruthProbed";
/**
 * Build the additive per-node `dataFreshness` marker. This NEVER mutates any
 * existing field — it only adds an explicit, machine-readable answer to the
 * question the legacy fields blurred: is this node's data live (just probed),
 * cached (held truth, maybe old), or absent because the peer was unreachable?
 *
 * The crucial separation: an UNREACHABLE peer (P2P probe failed / not connected)
 * is no longer indistinguishable from an idle/EMPTY node. Both used to render as
 * `health:'unknown'` with no sessions; now `dataFreshness.dataSource` and
 * `reachable` tell them apart so a coordinator never reads a dead peer as "online
 * but doing nothing".
 */
export declare function buildMeshNodeDataFreshness(args: {
    status: Record<string, unknown>;
    node?: any;
    isSelfNode: boolean;
    daemonId?: string;
    /** True when this node was stamped with a fresh live git probe this call. */
    liveTruthProbed: boolean;
    /** True when direct-peer-truth accounting classified this node unavailable. */
    directTruthUnavailable?: boolean;
    now?: () => number;
}): Record<string, unknown>;
/**
 * Canonical live-probe → freshness adapter. The coordinator-facing mesh_status
 * (mcp-server `meshStatus`) builds each node entry from a SINGLE fresh git_status
 * probe that either returns (live truth) or throws (peer unreachable). It used to
 * hand-reconstruct the freshness INPUT inline — a synthetic `{ git, connection }`
 * status plus the directTruthUnavailable/liveTruthProbed wiring — which is exactly
 * how a field added to `buildMeshNodeDataFreshness`'s input contract ends up "wired
 * on the daemon surface, null on the coordinator surface" (the rc.371
 * null-everywhere regression). Routing every live-probe surface through this one
 * adapter keeps the marker derivation canonical: there is a SINGLE place that turns
 * a probe outcome into freshness args, so the two mesh_status surfaces cannot drift.
 *
 * `liveTruthProbed` true → the probe returned (live/self truth); false → it threw,
 * so a configured peer is unreachable while an unconfigured node (no daemonId) falls
 * through to the classifier's `unconfigured` branch.
 */
export declare function buildMeshNodeProbeFreshness(args: {
    /** The git snapshot this probe stamped on the node entry (entry.git). */
    git: unknown;
    /** True when the fresh git_status probe RETURNED (live truth); false when it threw. */
    liveTruthProbed: boolean;
    isSelfNode: boolean;
    /** The node's resolved daemonId; absent → unconfigured node. */
    daemonId?: string;
    /** The mesh node record, for held-git fallback when the probe did not return live. */
    node?: any;
    now?: () => number;
}): Record<string, unknown>;
export declare function finalizeMeshNodeStatus(args: {
    status: Record<string, unknown>;
    node: any;
    daemonId?: string;
    isSelfNode: boolean;
    /** True when direct-peer-truth accounting classified this node unavailable. */
    directTruthUnavailable?: boolean;
}): void;
export declare const MESH_DIRECT_PROBE_TIMEOUT_MS: number;
export declare const MESH_DIRECT_PROBE_RETRY_TIMEOUT_MS: number;
export declare const MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS: number;
export declare const MESH_DIRECT_PROBE_REUSE_MS: number;
/**
 * De-duplicates and rate-limits per-peer git_status probes so a single mesh
 * refresh — or a burst of refreshes from the dashboard auto-retry loop — cannot
 * launch a storm of concurrent/back-to-back `refreshUpstream:true` commands to
 * the same slow peer.
 *
 * Two gates, both keyed by `daemonId::workspace`:
 *  - In-flight dedup: a second probe for a key with a probe already running
 *    shares (awaits) the in-flight promise instead of issuing a second command.
 *  - Recently-probed reuse: a successful probe younger than `reuseMs` is reused
 *    verbatim instead of issuing a fresh probe. Failures are NOT cached (so a
 *    transient timeout doesn't pin a peer to "no truth" for the whole window).
 *
 * Lives on the router instance so the gate spans separate mesh_status calls,
 * which is exactly where the refresh storm happens.
 */
export declare class MeshGitProbeCache {
    private readonly reuseMs;
    private readonly now;
    private inflight;
    private recent;
    constructor(reuseMs: number, now?: () => number);
    private key;
    /**
     * Local (same-machine) git_status dedup. The bootstrap direct-truth hydrate
     * and the per-node render loop both call getGitRepoStatus(refreshUpstream:true)
     * for the same local workspace within one mesh_status call. Each such probe
     * fans out ~13-15 git subprocesses, and because the two passes are separated by
     * the render/hydrate work of every OTHER node they routinely straddle the
     * getGitRepoStatus 1.5s TTL, so the second pass re-shells the whole ~14-process
     * collection. Routing both through this cache (namespaced under a reserved
     * daemon id so it never collides with a remote-peer key) collapses them to one
     * collection per workspace per request, and reuses it across the reuse window
     * so the dashboard auto-retry loop can't restart a fresh local probe seconds
     * apart either.
     */
    private static readonly LOCAL_PROBE_DAEMON_ID;
    probeLocal(workspace: string, probe: () => Promise<Record<string, unknown> | null>): Promise<Record<string, unknown> | null>;
    /**
     * Run `probe` for this peer, but reuse a fresh recent result or an in-flight
     * probe for the same key when one is available. `probe` is only invoked when
     * neither gate is satisfied.
     */
    probe(daemonId: string, workspace: string, probe: () => Promise<Record<string, unknown> | null>): Promise<Record<string, unknown> | null>;
}
export { awaitWithWarmupDeadline };
/**
 * Probe a remote peer's git_status with a bounded retry budget, but only while
 * the peer is reported `connected`. A single slow (often TURN-relayed) peer can
 * exceed one probe window; retrying — with the connection re-checked before each
 * attempt so we abandon a peer that dropped — recovers it without blocking the
 * mesh forever. Shared by the bootstrap hydrate path and the per-node render
 * path so both treat a connected-but-slow peer identically.
 *
 * Returns the git status on success, or null if every attempt failed/timed out
 * (caller decides how to classify). `getConnection` is consulted before each
 * attempt; a non-`connected` state short-circuits the retry loop (the very first
 * attempt always runs so a missing connection getter still gets one try).
 */
export declare function probeRemoteMeshGitStatusWithRetry(args: {
    dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
    daemonId: string;
    workspace: string;
    timeoutMs: number;
    /** Per-attempt timeout for retries (attempts > 0); defaults to timeoutMs. */
    retryTimeoutMs?: number;
    /** Cold-open warmup budget per attempt; defaults to MESH_DIRECT_PROBE_CONNECT_TIMEOUT_MS. */
    connectTimeoutMs?: number;
    getConnection?: (daemonId: string) => Record<string, unknown> | null;
    onConnection?: (connection: Record<string, unknown>) => void;
}): Promise<Record<string, unknown> | null>;
export declare function hydrateInlineMeshDirectTruth(args: {
    mesh: any;
    meshSource: 'inline_cache' | 'inline_bootstrap' | 'local_config';
    dispatchMeshCommand?: (daemonId: string, cmd: string, args: Record<string, unknown>) => Promise<unknown>;
    getMeshPeerConnectionStatus?: (daemonId: string) => Record<string, unknown> | null;
    statusInstanceId?: string;
    localMachineId?: string;
    probeRemotePeers: boolean;
    probeCache?: MeshGitProbeCache;
}): Promise<{
    directEvidenceCount: number;
    localConfirmedCount: number;
    peerAttemptedCount: number;
    peerConfirmedCount: number;
    standingEvidenceCount: number;
    unavailableNodeIds: string[];
    deadNodeIds: string[];
}>;
export declare function summarizeMeshSessionRecord(record: any): Record<string, unknown>;
export declare function readLiveMeshNodeWorkspace(args: {
    meshId: string;
    nodeId: string;
    liveSessionRecords: any[];
    allowCoordinatorSession?: boolean;
}): string;
export declare function collectLiveMeshSessionRecords(args: {
    meshId: string;
    node: any;
    nodeId: string;
    liveSessionRecords: any[];
    allowCoordinatorSession?: boolean;
}): any[];
export declare function buildHistoricalMeshSessions(args: {
    meshId: string;
    nodes: any[];
    liveSessionRecords: any[];
}): {
    count: number;
    sessions: Record<string, unknown>[];
    instruction: string;
} | undefined;
export declare function applyCachedInlineMeshNodeStatus(status: Record<string, unknown>, node: any, options?: {
    skipGit?: boolean;
    skipError?: boolean;
    skipHealth?: boolean;
}): boolean;
export declare function resolveProviderTypeFromPriority(args: {
    nodeId: string;
    providerPriority: string[];
    providerLoader: ProviderLoader;
    onStatusChange?: () => void;
}): Promise<{
    providerType?: string;
    error?: string;
}>;
