/**
 * WebSocket handler for agent connections.
 *
 * Agents connect to the orchestrator via WebSocket to:
 * - Authenticate with a kat_ token (when agentAuthMode === 'token')
 * - Register their capabilities (labels, concurrency)
 * - Receive job dispatches
 * - Report job execution status
 * - Send periodic heartbeats
 *
 * Two-phase auth flow (when agentAuthMode === 'token'):
 * 1. pendingAuth (5s): Agent must send auth.request with a valid kat_ token.
 * 2. pendingRegistration (10s): After auth.success, agent must send agent.register.
 * 3. registered: Agent is fully connected and can exchange messages.
 *
 * When agentAuthMode === 'none', the auth phase is skipped (legacy behavior).
 */
import type { WSEvents } from 'hono/ws';
import type { RateLimiterConfig } from '@kici-dev/engine';
import type { AgentRegistry } from '../agent/registry.js';
import type { Dispatcher } from '../agent/dispatcher.js';
import type { AgentTokenStore } from '../agent/token-store.js';
import type { OwnershipTracker } from '../agent/ownership-tracker.js';
import type { SourceCache } from '../cache/source-cache.js';
import type { DepCache } from '../cache/dep-cache.js';
import type { UserCache } from '../cache/user-cache.js';
import type { DispatchCacheRefTracker } from '../cache/dispatch-cache-ref-tracker.js';
import type { PendingBuildTracker } from '../cache/pending-builds.js';
import type { PendingInitTracker } from '../cache/pending-inits.js';
import type { PendingDynamicTracker } from '../cache/pending-dynamics.js';
import type { CacheStorage } from '../storage/types.js';
import type { AgentMetricsAggregator } from '../metrics/agent-metrics-aggregator.js';
import type { AgentApiRegistry } from './agent-api-registry.js';
/**
 * Run the three token-bound authorization gates against a wire-supplied
 * `agent.register` payload. The gates are identical at first register
 * (Phase 2 / `pendingRegistration`) and on every subsequent re-register
 * arriving on the same registered WS — every code path that mutates the
 * registry's `agentId → entry` mapping for an authenticated WS MUST run
 * them, otherwise the §5.3 / §5.1 invariants only hold for the very first
 * register and a re-register can silently overwrite the authority.
 *
 * Gates:
 *  1. Token-scope subset — wire labels MUST be a subset of
 *     `agent_tokens.labels` when that set is non-null. Closes the WS with
 *     `WS_CLOSE_AGENT_AUTH_FAILED` listing the elevated labels.
 *  2. Ephemeral identity-binding — for `agent_type === 'ephemeral'`,
 *     wire `agentId` MUST equal `tokenCreatedBy` (the scaler-spawned
 *     agentId the token was issued for). Closes the WS with
 *     `WS_CLOSE_AGENT_AUTH_FAILED`.
 *  3. Static-token agentId-collision — a different `tokenId` must not
 *     already claim the wire `agentId`. Closes the WS with
 *     `WS_CLOSE_INVALID_MESSAGE`.
 *
 * Returns `true` when every gate passed and the caller may proceed;
 * returns `false` after closing the WS on the first violation, in which
 * case the caller MUST stop processing.
 */
/**
 * WebSocket close reasons are capped at 123 UTF-8 bytes (RFC 6455 §5.5.1).
 * A reason longer than that makes `ws.close()` throw a RangeError, which —
 * thrown from an async message handler — surfaces as an unhandled rejection
 * and skips the close entirely. Truncate on a byte boundary and drop any
 * trailing partial multi-byte char.
 */
export declare function truncateCloseReason(reason: string): string;
export interface AgentWsHandlerDeps {
    registry: AgentRegistry;
    dispatcher: Dispatcher;
    /** Token store for validating agent auth tokens. Undefined when auth disabled. */
    tokenStore?: AgentTokenStore;
    /** Agent authentication mode. 'token' requires auth.request before register. */
    agentAuthMode: 'token' | 'none';
    /** Optional callback to forward job status to the Platform client. */
    onJobStatus?: (agentId: string, msg: {
        runId: string;
        jobId: string;
        state: string;
        timestamp: number;
        data?: Record<string, unknown>;
    }) => void;
    /** Optional callback when agent sends log chunks. */
    onLogChunk?: (agentId: string, msg: {
        runId: string;
        jobId: string;
        stepIndex: number;
        lines: string[];
        timestamp: number;
    }) => void;
    /** Optional callback when agent sends step status updates. */
    onStepStatus?: (agentId: string, msg: {
        runId: string;
        jobId: string;
        stepIndex: number;
        stepName: string;
        state: string;
        timestamp: number;
        data?: Record<string, unknown>;
        secretsAccessed?: string[];
        /** Raw log bytes accumulated by this step's LogStreamer at terminal time. */
        logBytesStreamed?: number;
    }) => void;
    /**
     * Optional callback when a scaler-managed agent registers.
     *
     * Returns:
     * - `boundJobId` (optional): set when the scaler bound a specific queued
     *   job to this agent at spawn time. The orchestrator eagerly dispatches
     *   that job before the agent's idle timer fires.
     * - `mandatoryLabels` (always populated for scaler-managed agents): the
     *   spawning scaler's Kubernetes-taint-style gate. Threaded into the
     *   AgentRegistry so subsequent label matches (queue drain + eager
     *   dispatch) apply the same gate the scaler-side selector applied.
     *
     * Returns `null` for static agents not managed by any scaler.
     */
    onScalerAgentRegistered?: (agentId: string, labels: string[]) => {
        boundJobId?: string;
        mandatoryLabels: string[];
    } | null;
    /** Optional callback when an agent disconnects (for scaler lifecycle). */
    onScalerAgentDisconnected?: (agentId: string) => void;
    /** Optional callback when an agent completes a job (for scaler lifecycle). */
    onScalerJobComplete?: (agentId: string) => void;
    /** Optional callback when agent sends per-job heartbeats. */
    onJobHeartbeat?: (agentId: string, msg: {
        runId: string;
        jobId: string;
        timestamp: number;
    }) => void;
    /** Optional callback when agent sends operational log lines (stateful/external agents via WS). */
    onAgentLog?: (agentId: string, msg: {
        lines: string[];
        timestamp: number;
    }) => void;
    /** Optional callback when agent acknowledges config (for MMDS clearing in Firecracker). */
    onConfigAck?: (agentId: string) => void;
    /** Optional callback when agent emits a custom event via ctx.emit(). */
    onEventEmit?: (agentId: string, msg: {
        jobId: string;
        requestId: string;
        eventName: string;
        payload: Record<string, unknown>;
        target?: {
            repos?: string[];
        };
    }) => Promise<{
        deliveryId?: string;
        error?: string;
    }>;
    /** Optional callback when agent sends a run.event for infrastructure lifecycle tracking. */
    onRunEvent?: (agentId: string, msg: {
        runId: string;
        eventType: string;
        timestampMs: number;
        sourceService: string;
        jobId?: string | null;
        metadata?: Record<string, unknown>;
        durationMs?: number | null;
    }) => void;
    /** Optional callback when agent sends a job.context with execution environment details. */
    onJobContext?: (agentId: string, msg: {
        runId: string;
        jobId: string;
        context: Record<string, unknown>;
    }) => void;
    /** Bundle cache for generating pre-signed upload URLs. */
    sourceCache?: SourceCache;
    /** Dep cache for generating pre-signed upload URLs. */
    depCache?: DepCache;
    /** User-facing cache for serving restore/save requests from the sandbox. */
    userCache?: UserCache;
    /**
     * Server-side jobId -> user-cache-namespace store. The orchestrator records
     * `{orgId, repoId, cacheRefScope, runId}` per job at dispatch time; the
     * `cache.user.*` handlers resolve the ref from HERE (keyed by the wire jobId),
     * NEVER from the wire message body — a `cache.user.*` message carries only a
     * `jobId` + `key`, so an agent can name a job it owns but can never influence
     * which org/repo/scope that job resolves to.
     */
    dispatchCacheRefs?: DispatchCacheRefTracker;
    /** Cache storage for setting metadata after upload completion. */
    cacheStorage?: CacheStorage;
    /** Optional rate limiter configuration. */
    rateLimiterConfig?: RateLimiterConfig;
    /** Optional ownership tracker for validating job-related messages. */
    ownershipTracker?: OwnershipTracker;
    /** Optional pending build tracker for cleanup on agent disconnect. */
    pendingBuilds?: PendingBuildTracker;
    /** Optional pending init tracker for cleanup on agent disconnect. */
    pendingInits?: PendingInitTracker;
    /** Optional pending dynamic tracker for cleanup on agent disconnect. */
    pendingDynamics?: PendingDynamicTracker;
    /** Optional callback when agent sends encrypted secret outputs on job success. */
    onSecretOutputs?: (runId: string, jobId: string, secretOutputs: Record<string, {
        agentPublicKey: string;
        encrypted: string;
    }>) => Promise<void>;
    /** Agent metrics aggregator for receiving pushed metrics. */
    agentMetricsAggregator?: AgentMetricsAggregator;
    /** Optional callback when agent reports a job belongs to a concurrency group. */
    onConcurrencyReport?: (agentId: string, msg: {
        runId: string;
        jobId: string;
        group: string;
        messageId: string;
    }) => Promise<{
        action: 'proceed' | 'wait' | 'cancel';
        reason?: string;
    }>;
    /**
     * Optional callback fired when an agent's WS connection closes, after the
     * dispatcher's own cleanup has run. Used by the long-poll concurrency
     * pipeline to drop the agent's queued waiters and `cancelQueued` the
     * matching DB rows so the run is marked failed instead of dangling.
     */
    onConcurrencyAgentDisconnect?: (agentId: string) => void | Promise<void>;
    /** Agent private API registry for handling agent.api.request messages. */
    agentApiRegistry?: AgentApiRegistry;
}
export declare function createAgentWsHandler(deps: AgentWsHandlerDeps): WSEvents;
//# sourceMappingURL=agent-handler.d.ts.map