/**
 * Coding-agent harness selection — taxonomy, coercion, and the session-lock invariant.
 *
 * A "harness" is the coding-agent CLI a sandbox drives (opencode / codex /
 * claude-code / …). The shell governs WHICH harness a chat session uses and
 * enforces that a session is LOCKED to the harness it started with — the model
 * may change mid-session, the harness may not (swapping it mid-session would
 * orphan the session's running agent state). Every product otherwise hand-rolls
 * this and hard-codes a single harness; this is the one place the rule lives.
 *
 * Substrate-free: the harness list mirrors the sandbox SDK's `BackendType` as a
 * plain string union (no sandbox dependency). The consumer owns storage — which
 * harness a workspace defaults to, which one a session locked — and maps the
 * resolved value onto the SDK's `backend.type`.
 */
/** The known coding-agent backends. Mirrors `@tangle-network/sandbox`'s
 *  `BackendType`; kept structural so this module needs no sandbox dependency. */
declare const KNOWN_HARNESSES: readonly ["opencode", "claude-code", "kimi-code", "codex", "amp", "factory-droids", "pi", "hermes", "forge", "openclaw", "acp", "cursor", "cli-base"];
type Harness = (typeof KNOWN_HARNESSES)[number];
declare const DEFAULT_HARNESS: Harness;
declare function isHarness(value: unknown): value is Harness;
/** Coerce an arbitrary value to a known harness, falling back (default `opencode`). */
declare function coerceHarness(value: unknown, fallback?: Harness): Harness;
interface ResolveSessionHarnessInput {
    /** The harness already locked to this session (recorded at its first turn). */
    sessionHarness?: unknown;
    /** The harness requested now — a new session's choice, or a turn's attempt to switch. */
    requested?: unknown;
    /** The workspace's default harness, used only when starting a fresh session. */
    workspaceDefault?: unknown;
    /** Final fallback when nothing else resolves (default `opencode`). */
    fallback?: Harness;
}
interface ResolvedSessionHarness {
    /** The harness to actually run — the locked one when the session already has it. */
    harness: Harness;
    /** True when the session already had a locked harness (this turn did not pick it). */
    locked: boolean;
    /** True when `requested` differs from the locked harness — a forbidden mid-session
     *  swap the caller should reject or warn on. The lock always wins regardless. */
    swapAttempted: boolean;
}
/**
 * Resolve the harness for a turn, enforcing the session lock.
 *
 * - **Session already started** (`sessionHarness` is a known harness): that harness
 *   wins (`locked: true`); a differing `requested` sets `swapAttempted` so the caller
 *   can reject the swap. The model is a separate per-turn concern and is unaffected.
 * - **Fresh session**: pick `requested → workspaceDefault → fallback`. The caller
 *   persists the result as the session's lock for every subsequent turn.
 */
declare function resolveSessionHarness(input?: ResolveSessionHarnessInput): ResolvedSessionHarness;
/**
 * Harness ↔ model compatibility policy — the ONE source of truth, server-side.
 *
 * Native CLI harnesses are vendor-locked: claude-code only drives Anthropic
 * models, codex only OpenAI, kimi-code only Moonshot. Router-backed harnesses
 * (opencode, etc.) accept any catalog model (`providers: null`). The pickers in
 * sandbox-ui keep the pair coherent in the UI; this is the same policy the SHELL
 * enforces so a bypassed/forged request can't pair claude-code with a gpt model
 * and fail at the sidecar. Operates on plain canonical ids ("provider/model") so
 * it stays substrate-free — no model-catalog or UI dependency.
 */
interface HarnessModelPolicy {
    /** Canonical-id provider prefixes the harness can run; null = any. */
    providers: readonly string[] | null;
    /** Snap-target patterns, best first; highest version within a pattern wins. */
    preferred: readonly RegExp[];
}
declare const HARNESS_MODEL_POLICIES: Partial<Record<Harness, HarnessModelPolicy>>;
/** Native harness for a model's provider (anthropic → claude-code, …). */
declare const PROVIDER_PREFERRED_HARNESS: Record<string, Harness>;
/** Provider prefix of a canonical id ("anthropic/claude-…" → "anthropic"). */
declare function modelProvider(modelId: string): string | null;
/** Provider-less ids (sentinels like "default", or a session's own config) are
 *  compatible everywhere — every harness honors its own configuration. */
declare function isModelCompatibleWithHarness(harness: Harness, modelId: string): boolean;
/** Keep `modelId` when the harness can run it; else the harness's best compatible
 *  catalog id (preferred patterns in order, highest version). When nothing in the
 *  catalog fits, return the original so the caller sees the incompatibility. */
declare function snapModelToHarness(harness: Harness, modelId: string, canonicalIds: readonly string[]): string;
/** Keep the harness when it can run `modelId`; else the model's native harness
 *  (anthropic → claude-code, openai → codex), falling back to opencode. */
declare function snapHarnessToModel(harness: Harness, modelId: string): Harness;
/** Fail-loud server guard: throw when a harness is asked to run a model it can't.
 *  Call before dispatching a sandbox turn so a bypassed UI can't reach the sidecar
 *  with an incompatible pair. */
declare function assertHarnessModelCompatible(harness: Harness, modelId: string): void;

export { DEFAULT_HARNESS, HARNESS_MODEL_POLICIES, type Harness, type HarnessModelPolicy, KNOWN_HARNESSES, PROVIDER_PREFERRED_HARNESS, type ResolveSessionHarnessInput, type ResolvedSessionHarness, assertHarnessModelCompatible, coerceHarness, isHarness, isModelCompatibleWithHarness, modelProvider, resolveSessionHarness, snapHarnessToModel, snapModelToHarness };
