/**
 * SpawnRequestManager — handles on-demand session spawning for message delivery.
 *
 * Per Phase 5 of INTER-AGENT-MESSAGING-SPEC v3.1:
 * - Evaluates spawn requests against resource constraints
 * - Spawns sessions with full context about why they were created
 * - Delivers pending messages to newly spawned sessions
 * - Handles denials with retry and escalation
 * - Enforces cooldown, session limits, memory pressure checks
 *
 * §4.2 additions (Threadline Cooldown & Queue Drain spec v7):
 * - Failure-suppressive cooldown reservation: `lastSpawnByAgent.set` BEFORE
 *   async spawn, never rolled back on failure. Prevents a peer who triggers
 *   fast-failing spawn errors from beating the cooldown.
 * - Classified failure attribution: Phase 1 classifier treats only
 *   locally-generated typed errors as agent-attributable. Everything else is
 *   ambiguous and does NOT bump penalty.
 * - Penalty state in separate fields: `penaltyUntil` (timestamp), and
 *   `consecutiveSpawnFailures` (counter). Reset on success. After 3
 *   attributable failures, `penaltyUntil = now + 2 * cooldownMs`.
 * - Single cooldown-remaining read path: `cooldownRemainingMs(agent)`.
 *   No consumer computes `now - lastSpawn` directly — closes the alias bug.
 * - State stored as `#private` ECMAScript fields so external consumers can't
 *   bypass the helpers. tsconfig target is ES2022; private fields are native.
 */
import type { Session } from '../core/types.js';
/**
 * §4.3: compute the versioned envelope hash for a queue entry's payload.
 * Hashes a canonical JSON of `{ context, threadId }` so two requests with
 * the same payload but differently-ordered keys produce the same hash.
 */
export declare function computeEnvelopeHash(input: {
    context?: string;
    threadId?: string;
}): string;
export interface SpawnRequest {
    requester: {
        agent: string;
        session: string;
        machine: string;
    };
    target: {
        agent: string;
        machine: string;
    };
    reason: string;
    context?: string;
    priority: 'low' | 'medium' | 'high' | 'critical';
    suggestedModel?: string;
    suggestedMaxDuration?: number;
    pendingMessages?: string[];
    /**
     * §4.5: Provenance tag forwarded to `spawnSession` so the resulting
     * session can be filtered, observed, or routed differently based on
     * how it was triggered.
     * - `spawn-request`: inline `evaluate()` from a fresh inbound message.
     * - `spawn-request-drain`: re-attempt from the drain loop's
     *   `onDrainReady` callback.
     * Defaults to `spawn-request` if unset. Future tags may be added
     * (e.g., `spawn-request-retry`).
     */
    triggeredBy?: 'spawn-request' | 'spawn-request-drain';
    /**
     * Threadline A2A continuity (claude-code only): when set, the spawned
     * session is launched with `--session-id <uuid>` so its transcript is
     * created at a deterministic, caller-chosen id. ThreadlineRouter.spawnNewThread
     * passes the uuid it stores as the thread's resume-map entry. Forwarded
     * verbatim into the `spawnSession` callback options. Mutually exclusive with
     * `resumeSessionId` (sessionId wins).
     */
    sessionId?: string;
    /**
     * Threadline A2A continuity (claude-code only): when set, the spawned
     * session is launched with `--resume <uuid>` so it reloads the full prior
     * transcript captured by an earlier `sessionId` spawn. ThreadlineRouter.resumeThread
     * passes the resume-map entry's uuid here. Forwarded verbatim into the
     * `spawnSession` callback options.
     */
    resumeSessionId?: string;
    /**
     * Warm-session A2A (a.k.a. keepAlive): when true, the spawnSession callback
     * routes to an INTERACTIVE persistent worker (REPL that stays alive between
     * messages) instead of the headless one-shot `claude -p`. The grounded prompt
     * is delivered as the worker's first turn. ThreadlineRouter.spawnWarmThread
     * sets this; forwarded verbatim into the spawnSession callback options. Absent
     * on every existing spawn path → headless behavior is byte-for-byte unchanged.
     */
    interactive?: boolean;
}
export interface SpawnResult {
    approved: boolean;
    sessionId?: string;
    tmuxSession?: string;
    reason?: string;
    retryAfterMs?: number;
}
/**
 * Classified cause of a spawn failure (§4.2).
 *
 * Callers that wrap `spawnSession` SHOULD emit a typed failure with one of
 * these `cause` values. Untagged errors default to `ambiguous` — fail-open
 * by design so legitimate infra flakes don't penalize a peer.
 *
 * Only `agent-attributable` causes count toward `consecutiveSpawnFailures`.
 */
export type SpawnFailureCause = 'envelope-validation' | 'admission-cap' | 'safety-refusal-on-payload' | 'memory-pressure' | 'session-cap' | 'provider-5xx' | 'gate-llm-timeout' | 'ambiguous';
/**
 * §4.5: Edge-transition events the manager emits via `onDegradation`. Each
 * event represents a state change worth surfacing to operators — not every
 * cooldown denial.
 *
 * - `spawn-penalty-tripped`: agent crossed `consecutiveSpawnFailures >= 3`
 *   threshold and is now in penalty cooldown.
 * - `spawn-infra-degraded`: agent crossed infra-failure threshold (5 in 10 min)
 *   and is now in degraded admission.
 */
export type SpawnDegradationEvent = {
    kind: 'spawn-penalty-tripped';
    agent: string;
    consecutiveFailures: number;
    penaltyMs: number;
    at: number;
} | {
    kind: 'spawn-infra-degraded';
    agent: string;
    failureCount: number;
    degradationMs: number;
    at: number;
};
/** Error class callers throw from inside `spawnSession` to tag attributable failures. */
export declare class SpawnFailureError extends Error {
    readonly cause: SpawnFailureCause;
    constructor(message: string, cause: SpawnFailureCause);
}
export interface SpawnRequestManagerConfig {
    /**
     * Max concurrent sessions allowed.
     *
     * Used as the fallback when no live accessor is provided (back-compat).
     * Prefer `getMaxSessions` so the manager reflects config reloads without
     * needing a constructor rebuild.
     */
    maxSessions: number;
    /**
     * Optional live accessor for the session cap. Called on every admission
     * check (line ~519). When provided, takes precedence over the constructor
     * snapshot `maxSessions`. This fixes the split-brain where operators
     * raised `sessions.maxSessions` in the config but the manager kept
     * denying at the old cap because the constructor value was frozen.
     */
    getMaxSessions?: () => number;
    /** Function to list current running sessions */
    getActiveSessions: () => Session[];
    /**
     * Function to spawn a new session. Returns the session ID.
     *
     * §4.5: `options.triggeredBy` forwards the SpawnRequest's provenance tag
     * (defaulting to `spawn-request`) so the consumer can label the resulting
     * session (e.g., for log filtering or routing).
     */
    spawnSession: (prompt: string, options?: {
        model?: string;
        maxDurationMinutes?: number;
        triggeredBy?: 'spawn-request' | 'spawn-request-drain';
        /** Threadline A2A continuity: forwarded to `--session-id` (set a
         *  deterministic conversation id). Mutually exclusive with resumeSessionId. */
        sessionId?: string;
        /** Threadline A2A continuity: forwarded to `--resume` (reload the prior
         *  transcript captured by an earlier sessionId spawn). */
        resumeSessionId?: string;
        /** Warm-session A2A: when true, spawn an INTERACTIVE persistent worker
         *  (keep-alive REPL) instead of headless `claude -p`. The prompt becomes
         *  the worker's first turn. Absent on every non-warm spawn. */
        interactive?: boolean;
    }) => Promise<string | {
        sessionId: string;
        tmuxSession?: string;
    }>;
    /** Function to check memory pressure. Returns true if pressure is too high. */
    isMemoryPressureHigh?: () => boolean;
    /**
     * Optional subscription-quota gate (june15-headless-spawn-reroute,
     * security finding S1). When the subscription-path reroute is active
     * (intelligence.subscriptionPath.mode auto/force), A2A cold spawns land on
     * the operator's subscription 5h window — without this gate, inbound peer
     * traffic could drive that window to exhaustion and block the USER's own
     * conversations (a peer-triggered rate-limit DoS). The server wires this
     * to QuotaTracker.shouldSpawnSession ONLY when the reroute is active;
     * absent (the default), admission behavior is byte-for-byte unchanged.
     */
    shouldSpawnSession?: (priority?: string) => {
        allowed: boolean;
        reason: string;
    };
    /** Cooldown between spawn requests per agent (ms). Default: 30s */
    cooldownMs?: number;
    /** Max spawn retries before giving up. Default: 3 */
    maxRetries?: number;
    /** Max retry window (ms). Default: 30 min */
    maxRetryWindowMs?: number;
    /** Callback for escalation (e.g., Telegram notification) */
    onEscalate?: (request: SpawnRequest, reason: string) => void;
    /** Optional clock injection for deterministic tests. Defaults to Date.now(). */
    nowFn?: () => number;
    /**
     * §4.5: optional sink for degradation breadcrumbs. When provided, the
     * manager calls this on edge transitions (penalty trip, infra-degraded
     * entry). Wiring at the server layer typically targets DegradationReporter.
     * Decoupled to keep the manager testable without the global reporter.
     */
    onDegradation?: (event: SpawnDegradationEvent) => void;
    /**
     * §4.2: Called per ready agent during a drain tick. The consumer is
     * responsible for running a spawn/deliver cycle for that agent — typically
     * by constructing a synthetic `SpawnRequest` and calling back into
     * `evaluate`. Optional: if unset, the drain loop is a no-op and queued
     * messages only drain on the next inline `evaluate` call (legacy behavior).
     */
    onDrainReady?: (agent: string) => Promise<void>;
    /** §4.2: max drains per tick. Default 8. */
    maxDrainsPerTick?: number;
    /** §4.2: max queued messages per agent while in degraded admission. Default 1. */
    degradedMaxQueuedPerAgent?: number;
    /**
     * §4.3: max envelope size in bytes (UTF-8). Spawn requests with `context`
     * larger than this are refused at admission with an `envelope-too-large`
     * reason. Bounds drain-tick cost and prevents a peer from hogging budget
     * with bulk content. Default: 256 KiB.
     */
    maxEnvelopeBytes?: number;
    /**
     * §4.3: global cap on total queued messages across ALL agents. Refuses new
     * enqueues with `global-queue-full` reason when the cap is reached. Bounds
     * total memory regardless of how many distinct peers are queueing.
     * Default: 1000.
     */
    maxGlobalQueued?: number;
}
export declare class SpawnRequestManager {
    #private;
    /** Max queued messages per agent before oldest are dropped */
    static readonly MAX_QUEUED_PER_AGENT = 10;
    /** Max age for queued messages (10 minutes) */
    static readonly QUEUE_MAX_AGE_MS: number;
    constructor(config: SpawnRequestManagerConfig);
    /**
     * Single read path for "how long until this agent may spawn again" (§4.2).
     *
     * Returns the MAX of (remaining cooldown, remaining penalty, 0). No external
     * consumer should compute `now - lastSpawn` — this closes the alias bug
     * where subtracting a fresh timestamp against a stale one could produce
     * a negative elapsed and grant an unintended spawn.
     */
    cooldownRemainingMs(agent: string): number;
    /**
     * Returns true if the agent has exceeded `INFRA_FAILURE_THRESHOLD`
     * infra-attributable failures within `INFRA_FAILURE_WINDOW_MS` AND the
     * resulting degradation window (`INFRA_DEGRADATION_DURATION_MS` since the
     * threshold-tripping failure) has not yet elapsed.
     *
     * No penalty implied — degraded admission ONLY caps queue depth via
     * `effectiveMaxQueuedPerAgent(agent)`.
     */
    isInfraDegraded(agent: string): boolean;
    /** Effective per-agent queue cap, accounting for soft-limiter degradation. */
    effectiveMaxQueuedPerAgent(agent: string): number;
    /**
     * Evaluate and potentially approve a spawn request.
     * Returns the result with approval status and session info if spawned.
     */
    evaluate(request: SpawnRequest): Promise<SpawnResult>;
    /**
     * Handle a denied spawn request — track retries and escalate if needed.
     */
    handleDenial(request: SpawnRequest, result: SpawnResult): void;
    /** §4.3: returns true if this agent's queue was truncated since last drain. */
    isTruncated(agent: string): boolean;
    /** Get count of queued messages for an agent (for monitoring) */
    getQueuedCount(agent: string): number;
    /** Get current spawn state for monitoring */
    getStatus(): {
        cooldowns: Array<{
            agent: string;
            remainingMs: number;
        }>;
        pendingRetries: number;
        queuedMessages: Array<{
            agent: string;
            count: number;
        }>;
        penalties: Array<{
            agent: string;
            untilMs: number;
            consecutiveFailures: number;
        }>;
    };
    /**
     * Compute the drain-tick interval from the configured cooldown (§4.2).
     *
     * Formula: `max(min(cooldownMs / 4, 5000), 1000)`. Floor at 1 s prevents
     * a tiny cooldown from producing a hot loop; ceiling at 5 s preserves
     * responsiveness under long cooldowns.
     */
    getDrainTickMs(): number;
    /**
     * Start the shared drain tick. Idempotent — safe to call twice; a second
     * call is a no-op. The tick runs on a real `setInterval` so consumers
     * can use vitest fake timers for test determinism.
     */
    start(): void;
    /**
     * Stop the drain tick and clear deficit/attempt state. Idempotent.
     * Use this OR `reset()` between tests — `reset()` also clears cooldown.
     */
    dispose(): void;
    /**
     * Run a single drain tick. Exposed for tests + for consumers who want
     * to drive ticks manually (e.g., drive-by-event rather than by timer).
     *
     * Behavior:
     * 1. O(1) early return if no agents have queued messages.
     * 2. Collect ready agents: those with `cooldownRemainingMs <= tickGraceMs`
     *    AND queued messages present.
     * 3. Add DRR quantum to each ready agent's deficit (with age boost for
     *    agents whose oldest queued message has been drain-attempted > 1 time).
     * 4. Select up to `maxDrainsPerTick` agents ordered by descending deficit;
     *    decrement deficit by cost for each selected agent.
     * 5. Fire `onDrainReady` for selected agents concurrently via allSettled.
     *
     * Returns the count of drain callbacks invoked this tick (useful for tests).
     */
    runTick(): Promise<number>;
    /** Test seam: snapshot of DRR deficit state. */
    getDrrDeficitSnapshotForTests(): ReadonlyMap<string, number>;
    /**
     * §4.4 commit 3: runtime-tunable subset of the config. Exposed for the
     * GET endpoint that lets operators inspect current values without exposing
     * sensitive callbacks (`spawnSession`, `getActiveSessions`, etc.).
     */
    getRuntimeConfig(): {
        cooldownMs: number;
        maxDrainsPerTick: number;
        maxEnvelopeBytes: number;
        maxGlobalQueued: number;
        degradedMaxQueuedPerAgent: number;
        drainTickMs: number;
    };
    /**
     * §4.4 commit 3: update the runtime-tunable subset of the config in place.
     *
     * Changing `cooldownMs` updates the gate logic immediately, but the drain
     * tick interval is fixed at `start()`. To pick up a new tick interval,
     * callers should `dispose()` then `start()`. Returns true if the timer
     * needs to be restarted to pick up tick-interval changes.
     *
     * Validation: each field is rejected if not a positive finite number (or
     * if it would result in nonsensical state). The whole patch is atomic —
     * any invalid field rejects the entire update.
     */
    updateConfig(patch: {
        cooldownMs?: number;
        maxDrainsPerTick?: number;
        maxEnvelopeBytes?: number;
        maxGlobalQueued?: number;
        degradedMaxQueuedPerAgent?: number;
    }): {
        applied: true;
        tickIntervalChanged: boolean;
    } | {
        applied: false;
        reason: string;
    };
    /** Clear all state (for testing) */
    reset(): void;
}
//# sourceMappingURL=SpawnRequestManager.d.ts.map