import { ft as Provider, wn as ThinkingLevel } from "./agent-BHkvYIH9.js";
import { OAuthProviderInterface } from "@earendil-works/pi-ai/oauth";
import { ReactNode } from "react";

//#region src/chat/providers.d.ts
/**
 * Structural model metadata — compatible with pi-ai's `Model` interface but
 * not coupled to it, so hosts can pass either pi-ai-shaped objects or a
 * custom registry of their own.
 *
 * Deliberately a structural duplicate of pi-ai's `Model` rather than a
 * re-export: keeps the public API stable when pi-ai bumps shape, and lets
 * hosts implement their own registry without depending on pi-ai's types.
 *
 * Lives here (rather than `./config`) because `ProviderDescriptor.models`
 * needs it — pushing it to `config.tsx` created an import cycle.
 */
interface ModelInfo {
  id: string;
  name?: string;
  contextWindow: number;
  maxTokens?: number;
  reasoning?: boolean;
  input?: readonly ('text' | 'image')[];
  cost?: {
    input: number;
    output: number;
    cacheRead?: number;
    cacheWrite?: number;
  };
  provider?: string;
  /**
   * Model-specific toggleable knobs surfaced in the UI (e.g. Anthropic's
   * "fast mode"). Each option is an independent boolean the user flips on the
   * active model; enabled options are collected into `StreamOptions.modelOptions`
   * keyed by {@link ModelOption.id} and applied to the wire by the provider.
   * Omitted / empty → the model has no custom options and the picker is hidden.
   */
  options?: readonly ModelOption[];
}
/**
 * A single toggleable model option. Declarative metadata only — the provider
 * owns the actual request-body translation (keyed on {@link id}). Kept generic
 * (not "fast mode"-specific) so new per-model knobs land without a type change.
 */
interface ModelOption {
  /** Stable identifier; the key under which the toggle persists + reaches the provider. */
  id: string;
  /** Short label shown in the picker (e.g. "Fast mode"). */
  label: string;
  /** One-line description of the trade-off (shown in the picker row). */
  description?: string;
  /**
   * Optional cost multipliers applied to the model's base rates while the
   * option is enabled. `{ input: 2, output: 2 }` doubles both. Used by the
   * cost estimator so the footer reflects premium-priced modes (fast mode).
   */
  costMultiplier?: {
    input?: number;
    output?: number;
    cacheRead?: number;
    cacheWrite?: number;
  };
}
/**
 * Free-form configuration field that a provider needs the user to supply
 * alongside (or instead of) an API key — e.g. a self-hosted base URL, an
 * Azure deployment name, a default model id.
 *
 * The wizard prompts for each field in order. Submitted values are persisted
 * into the apikey credential (as `customFields[key]`) and mirrored into
 * `process.env[envVar]` so the provider's factory can read them via the same
 * env-var convention as `envKey`.
 *
 * Descriptors without an `envKey` use `customFields` as the sole credential
 * mechanism — the wizard skips its API-key step in that case.
 */
interface CustomField {
  /** Storage key inside the credential's `customFields` record. */
  key: string;
  /** Human-readable label shown in the wizard step's title. */
  label: string;
  /** Env var name to mirror this field into at TUI launch + on submit. */
  envVar: string;
  /** Optional placeholder shown in the wizard input. */
  placeholder?: string;
  /** Optional helper copy rendered above the input. */
  hint?: string;
  /** When true, an empty submission is rejected by the wizard. Default: false. */
  required?: boolean;
}
interface ProviderDescriptor {
  /**
   * Unique identifier. Used as the registry key, persisted in `state.json`
   * as the resumed provider, and (by default) as the credential-file key.
   */
  key: string;
  /** Display name shown in the TUI's provider picker and labels. */
  label: string;
  /**
   * Factory that builds a fresh `Provider` instance. Called on every session
   * activation. Some factories (e.g. zidane's built-in `anthropic`) eagerly
   * resolve credentials at construction time and throw when none are
   * configured — set {@link defaultModel} on the descriptor to avoid the TUI
   * calling the factory before the user has picked + authed a provider.
   */
  factory: () => Provider;
  /**
   * Default model id to seed the picker with. When omitted, the TUI falls
   * back to `descriptor.factory().meta.defaultModel`, which constructs the
   * provider — fine for lazy-credential factories, but breaks for factories
   * that throw on missing credentials before the wizard runs. Setting this
   * eagerly lets the TUI render the auth wizard without ever instantiating
   * the provider.
   */
  defaultModel?: string;
  /**
   * Env var checked when detecting whether the user has an API key
   * configured for this provider. Optional — set to `undefined` when the
   * provider only supports OAuth (or only credentials via a custom path).
   */
  envKey?: string;
  /**
   * Key under which credentials live in `credentials.json`. Defaults to
   * `key`. The only built-in that overrides this is OpenAI Codex
   * (`openai` → `openai-codex`) to stay compatible with the harness
   * provider's existing lookup.
   */
  credentialFileKey?: string;
  /** Placeholder shown in the wizard's API-key input. */
  apiKeyPlaceholder?: string;
  /**
   * pi-ai (or compat) OAuth provider. When present, the wizard offers an
   * OAuth option in addition to API key.
   */
  oauthProvider?: OAuthProviderInterface;
  /**
   * Optional copy appended to the wizard's "OAuth" method description.
   * Use to communicate why a user might pick OAuth (e.g. subscription tier,
   * org-wide SSO). Shown after `browser-based sign-in`. Skip the leading
   * space — the wizard adds it. Example: `'Claude Pro/Max subscription'`.
   */
  oauthHint?: string;
  /**
   * pi-ai provider id used to look up models in pi-ai's built-in registry.
   * Defaults to `key`. Only Codex differs (`openai` → `openai-codex`).
   */
  piProviderId?: string;
  /**
   * Override the model list returned for this provider's picker. When set,
   * skips pi-ai's registry entirely. Useful for hosts maintaining their
   * own model catalogue or for custom providers pi-ai doesn't know about.
   */
  models?: readonly ModelInfo[];
  /**
   * Additional free-form fields the wizard should collect from the user
   * (e.g. base URL for a self-hosted endpoint, default model slug for a
   * provider with no fixed catalogue). Each field is persisted in the
   * apikey credential's `customFields` map and mirrored into the matching
   * `envVar` at TUI launch.
   *
   * When `envKey` is also set, the wizard collects custom fields **before**
   * the API key. When `envKey` is omitted, the wizard skips the API-key
   * step entirely — `customFields` becomes the sole credential mechanism
   * (used by the `local` provider, which authenticates via baseURL only).
   */
  customFields?: readonly CustomField[];
  /**
   * Extra models merged AHEAD of pi-ai's registry list (de-duped by id, pi-ai
   * wins on collision). Unlike {@link models} this augments rather than
   * replaces — used to surface freshly-shipped models pi-ai hasn't bundled
   * yet without losing the rest of the registry. Ignored when {@link models}
   * is set.
   */
  extraModels?: readonly ModelInfo[];
  /**
   * Resolve the custom {@link ModelOption}s a given model id supports. Used to
   * attach options (e.g. fast mode) to models that come from pi-ai's registry —
   * which {@link extraModels} can't reach because pi-ai wins the id collision.
   * Returns `undefined` / `[]` for models with no options. Models that already
   * carry `options` (e.g. via {@link extraModels}) keep theirs.
   */
  optionsFor?: (modelId: string) => readonly ModelOption[] | undefined;
  /**
   * Env var holding a user-supplied context window (in tokens) for providers
   * that have no model registry to advertise one — chiefly the `local`
   * provider, whose runtime (Ollama, vLLM, …) doesn't expose its loaded
   * model's window over the OpenAI-compat API. When set, {@link getContextWindow}
   * falls back to resolving this env var (via {@link resolveContextWindow})
   * after the registry lookup comes up empty. The value is either a single
   * window (`"32768"`, applied to every model) or a per-model map
   * (`"llama3.1:8b=32768, qwen=128k, 64k"`, with an optional bare fallback).
   * Pair it with a {@link CustomField} that mirrors the user's value into the
   * same `envVar` so the footer's context indicator and auto-compaction work
   * for local models.
   */
  contextWindowEnvVar?: string;
}
/** Convenience accessor — returns `credentialFileKey ?? key`. */
declare function credKeyOf(desc: ProviderDescriptor): string;
/** Convenience accessor — returns `piProviderId ?? key`. */
declare function piIdOf(desc: ProviderDescriptor): string;
declare const anthropicDescriptor: ProviderDescriptor;
declare const openaiDescriptor: ProviderDescriptor;
declare const openrouterDescriptor: ProviderDescriptor;
declare const cerebrasDescriptor: ProviderDescriptor;
/**
 * Local OpenAI-compatible LLM (Ollama, vLLM, LM Studio, Lemonade, llama.cpp).
 *
 * No fixed base URL or model catalogue — both come from the user via
 * `customFields`. No `envKey`, so the wizard skips the API-key prompt and
 * treats the customFields-only credential as the auth signal (see
 * `applyApiKeyEnv` + `detectAuth`).
 *
 * Vision / cache / reasoning are off by default in the factory — flip them
 * by passing a custom descriptor that calls `local({ capabilities })`.
 *
 * `models` is a getter, not a static list: there's no fixed catalogue to ship,
 * but once the user has configured a default model (mirrored into
 * `LOCAL_LLM_DEFAULT_MODEL` by `applyApiKeyEnv`), we surface it as a
 * single-entry list so the model picker + footer aren't empty. Resolved at
 * access time because the env var is populated at TUI launch, after this
 * module loads. Empty list when unconfigured — the picker hides, the user
 * types a slug at the prompt as before.
 */
declare const localDescriptor: ProviderDescriptor;
/**
 * Default provider registry. Passed verbatim when `runTui` is invoked without
 * an explicit `providers` option. Hosts that want to override per-provider
 * metadata can spread this and replace specific entries:
 *
 * ```ts
 * runTui({ providers: { ...BUILTIN_PROVIDERS, anthropic: myOwnAnthropicDescriptor } })
 * ```
 *
 * `cursor` is intentionally NOT registered here: OAuth works, but inference
 * isn't wired (`cursor.stream()` throws — Cursor speaks a protobuf/HTTP-2
 * agent protocol, not OpenAI-compat). Shipping it in the picker only lets users
 * select a model that errors every turn. The descriptor stays exported so a
 * host can opt in (`{ ...BUILTIN_PROVIDERS, cursor: cursorDescriptor }`) once
 * the transport lands — re-add it here at that point.
 */
declare const BUILTIN_PROVIDERS: Readonly<Record<string, ProviderDescriptor>>;
/**
 * Resolve the model list for a given provider. Honors `descriptor.models`
 * when set; otherwise queries pi-ai via `descriptor.piProviderId`. Returns
 * `[]` for descriptors with no known mapping (custom providers without a
 * model list) — callers should hide the model picker in that case.
 */
declare function modelsForDescriptor(descriptor: ProviderDescriptor): readonly ModelInfo[];
/**
 * Resolve a single model's metadata via the descriptor's model source.
 * Mirrors {@link modelsForDescriptor} routing: descriptor's own list wins,
 * pi-ai's registry is the fallback. Returns `null` when the model isn't
 * known.
 */
declare function getModelInfo(descriptor: ProviderDescriptor, modelId: string): ModelInfo | null;
/**
 * Look up the model's max context window via the descriptor's model source.
 * Falls back to {@link ProviderDescriptor.contextWindowEnvVar} (parsed via
 * {@link resolveContextWindow}, which supports a per-model map) when the
 * registry has nothing — this is how local LLMs, whose runtimes don't
 * advertise a window, get one. Returns `null` when neither source resolves a
 * value (custom slugs, providers without a registry or a configured window);
 * callers should hide the context indicator and skip auto-compaction then.
 */
declare function getContextWindow(descriptor: ProviderDescriptor, modelId: string): number | null;
/**
 * Reserved output budget subtracted from the raw context window when
 * computing the effective ceiling for compaction / warning thresholds.
 *
 * Aligned with Claude Code's `COMPACT_MAX_OUTPUT_TOKENS = 20_000`
 * (`utils/context.ts:12`) — covers p99.99 of summary + response output.
 * A turn that consumed N input tokens leaves `effectiveWindow - N`
 * headroom for the next prompt's response; once headroom shrinks
 * below the threshold, auto-compaction fires.
 */
declare const OUTPUT_RESERVE_TOKENS = 20000;
/**
 * Effective context window — what the next user turn can actually pack
 * before the provider reserves space for the assistant's reply. Equals
 * `rawWindow - OUTPUT_RESERVE_TOKENS`, clamped to `>= 1` so a tiny
 * (or absurd) window doesn't yield zero / negative thresholds.
 *
 * Used by both the footer's context indicator and the auto-compact
 * trigger so the two surfaces agree on "how full are we, really".
 * Pass `null` through unchanged so callers can pipe `getContextWindow`
 * directly without an intermediate check.
 */
declare function effectiveContextWindow(rawWindow: number | null): number | null;
/**
 * Whether the given model exposes a reasoning / extended-thinking knob.
 * Drives the TUI's effort picker visibility — the `ctrl+n` shortcut and
 * the bottom-bar `effortName` segment only surface when this is `true`.
 * Returns `false` for unknown models (no registry entry → no advertised
 * capability).
 */
declare function modelSupportsReasoning(descriptor: ProviderDescriptor, modelId: string): boolean;
/**
 * Custom {@link ModelOption}s the given model supports (e.g. fast mode), or `[]`
 * when none. Drives the TUI/GUI options picker visibility — surfaces only when
 * non-empty.
 */
declare function modelOptionsFor(descriptor: ProviderDescriptor, modelId: string): readonly ModelOption[];
/**
 * Normalize a model-options blob to an enabled-only `{ id: true }` map.
 *
 * Single source of truth shared by every surface that reads or persists model
 * options — the TUI run path, the GUI engine reader, and the GUI IPC handlers.
 * Tolerant of arbitrary input (persisted JSON metadata may be anything): a
 * non-object → `{}`, and only entries strictly equal to `true` survive. This
 * keeps the persisted shape minimal (no `{ fast: false }` noise) and means
 * callers never forward a disabled option to `agent.run`.
 */
declare function enabledModelOptions(raw: unknown): Record<string, boolean>;
/**
 * Resolve a model's remembered options for a (re)pick: keep only options the
 * model still declares AND that are enabled, dropping any that no longer apply
 * (e.g. switching away from a model that supported `fast`). Returns `undefined`
 * when nothing applies so callers can omit the field entirely.
 *
 * Shared by the TUI pick path and the launch-time resume path so "which options
 * survive a model switch / relaunch" is decided in exactly one place.
 */
declare function restoreModelOptions(descriptor: ProviderDescriptor, modelId: string, remembered: Record<string, Record<string, boolean>> | undefined): Record<string, boolean> | undefined;
//#endregion
//#region src/chat/auth.d.ts
/**
 * Provider identifier as known to the TUI. Built-in keys are `anthropic`,
 * `openai`, `openrouter`, `cerebras`, but hosts can register additional
 * keys via {@link ProviderDescriptor} — so the type is open.
 */
type ProviderKey = string;
interface AuthMethod {
  source: 'env' | 'oauth' | 'apikey';
  /** Human-readable detail (env var name, expiry timestamp, …). */
  detail: string;
}
interface ProviderAuth {
  key: ProviderKey;
  label: string;
  /** True when at least one credential source is present. */
  available: boolean;
  methods: AuthMethod[];
}
/**
 * Detect available auth for every registered provider.
 *
 * Resolution order per provider (a method appears in `methods` for each
 * layer that has a credential — the agent itself resolves them in the same
 * order via its provider factories):
 *
 *   1. `kind: 'apikey'` from `credentials.json` (injected into env at TUI launch)
 *   2. explicit env var (descriptor's `envKey`)
 *   3. `kind: 'oauth'` from `credentials.json` (or legacy `cwd/.credentials.json`)
 *
 * Pure read — never refreshes or rewrites the credentials file.
 */
declare function detectAuth(dataDir: string, registry: Readonly<Record<string, ProviderDescriptor>>, env?: Record<string, string | undefined>): ProviderAuth[];
//#endregion
//#region src/chat/completion-core.d.ts
/**
 * Prompt autocompletion framework — pure core.
 *
 * Provider contract types + the renderer-agnostic span/reference helpers
 * (`findActiveTrigger`, `applyInsert`, `mergeReferences`, `collectReferences`).
 * No React, no node — so a browser-context renderer imports these via
 * `zidane/chat/pure`. The `useCompletion` React hook lives in `./completion`
 * (main barrel) and re-exports everything here for back-compat.
 *
 * Providers plug in by registering a `trigger` character (e.g. `/` for skills,
 * `@` for files) and exposing two operations:
 *
 *   1. `suggest(query)` — return ranked items for the live query.
 *   2. `parseReferences(text)` — find all references to the provider's
 *      items in arbitrary text. Used to highlight in-prompt mentions and
 *      drive submit-time side effects (activate the skill, attach the
 *      file, …).
 */
/**
 * A discoverable, selectable thing — one row in the autocomplete popover.
 * `TItem` is provider-specific; consumers can inspect `data` when they
 * need the originating payload (e.g. the full `SkillConfig` for tooltips).
 */
interface CompletionItem<TItem = unknown> {
  /** Stable identifier within the provider. Used as React key + selection equality. */
  id: string;
  /** User-visible primary string ("research"). */
  label: string;
  /** Optional one-line secondary string ("In-depth research with citations"). */
  description?: string;
  /**
   * Text that replaces the active span (trigger + query) on commit. Usually
   * `${trigger}${label}` with a trailing space so the cursor lands ready
   * for the next token.
   */
  insertText: string;
  /** Original provider-specific payload. */
  data: TItem;
}
/**
 * A reference to a provider item inside arbitrary prompt text. Producers:
 * `provider.parseReferences(text)`. Consumers: the TUI for highlighting,
 * the run flow for "activate every referenced skill before agent.run()".
 *
 * Spans are half-open `[start, end)` codepoint offsets into the source
 * string. Overlapping spans from the same or different providers are
 * caller-resolved — the helpers below ship a "first wins" merger.
 */
interface CompletionReference<TItem = unknown> {
  providerId: string;
  start: number;
  end: number;
  itemId: string;
  data: TItem;
}
/**
 * Provider contract. Implementations decide their own ranking, fuzzy-match
 * rules, async loading behavior, and reference grammar.
 */
interface CompletionProvider<TItem = unknown> {
  /** Stable id used for tagging references + React keys. */
  id: string;
  /**
   * Single character that activates this provider. The engine considers a
   * trigger "active" when it appears at the start of the buffer or
   * immediately after whitespace, and is not closed by whitespace before
   * the cursor (so a trigger plus query is a contiguous token).
   */
  trigger: string;
  /** Human-readable name. Reserved for future multi-provider popover headers. */
  label: string;
  /**
   * Returns items for the active `query` (the text between the trigger and
   * the cursor, excluding the trigger itself). Synchronous return is the
   * common case; promises let providers paginate or hit a backend.
   *
   * `signal` is aborted when the query changes or the popover closes —
   * use it to cancel in-flight network calls.
   */
  suggest: (query: string, ctx: CompletionContext, signal: AbortSignal) => CompletionItem<TItem>[] | Promise<CompletionItem<TItem>[]>;
  /**
   * Find every reference to this provider's items in `text`. Pure: must not
   * mutate ctx. The TUI calls this on every keystroke for highlighting, so
   * keep it cheap (linear scan, no I/O).
   */
  parseReferences: (text: string, ctx: CompletionContext) => CompletionReference<TItem>[];
}
/**
 * Read-only view of the active prompt buffer + cursor passed to provider
 * callbacks. Kept minimal so providers stay portable across renderers.
 */
interface CompletionContext {
  /** Full prompt text. */
  text: string;
  /** Codepoint offset of the cursor (0-based). */
  cursor: number;
}
/**
 * Identified active trigger span. Returned by `findActiveTrigger` so
 * callers can show the popover, query the provider, and on commit replace
 * the span with the selected item's `insertText`.
 */
interface ActiveTrigger<TItem = unknown> {
  provider: CompletionProvider<TItem>;
  /** Substring after the trigger, up to the cursor. Empty if cursor sits right after the trigger. */
  query: string;
  /** `[start, end)` — span covered by the trigger + query in the source. */
  span: {
    start: number;
    end: number;
  };
}
/**
 * Resolve the provider trigger active at `cursor`, or `null` when none fits.
 *
 * Rules:
 *   - The trigger character must sit at position 0 of the buffer OR be
 *     preceded by whitespace. This prevents `http://` from triggering the
 *     `/`-bound skills provider mid-URL.
 *   - The cursor must be at or past the trigger position.
 *   - Nothing between the trigger and the cursor may be whitespace (the
 *     query is one contiguous token).
 *   - The query length is bounded — `maxQueryLength` defaults to 64 — so
 *     a runaway buffer scan can't pin the renderer.
 */
declare function findActiveTrigger<TItem>(text: string, cursor: number, providers: readonly CompletionProvider<TItem>[], options?: {
  maxQueryLength?: number;
}): ActiveTrigger<TItem> | null;
/**
 * Replace `[span.start, span.end)` in `text` with `insertText`. Returns the
 * mutated text and the new cursor position (end of insertion).
 */
declare function applyInsert(text: string, span: {
  start: number;
  end: number;
}, insertText: string): {
  text: string;
  cursor: number;
};
/**
 * Merge reference lists from multiple providers into one ordered list with
 * earlier-start-wins disambiguation when spans overlap. Ties broken by
 * insertion order. Spans are sorted ascending so renderers can walk them
 * sequentially with a cursor through the source string.
 */
declare function mergeReferences<TItem>(refs: readonly CompletionReference<TItem>[]): CompletionReference<TItem>[];
/**
 * Collect every provider's references in one pass. Convenience wrapper —
 * the TUI textarea component calls this on every keystroke to highlight
 * in-prompt mentions.
 */
declare function collectReferences<TItem>(text: string, providers: readonly CompletionProvider<TItem>[], cursor?: number): CompletionReference<TItem>[];
//#endregion
//#region src/chat/types.d.ts
type Screen = 'auth' | 'sessions' | 'chat';
/** Identifies the agent that produced a streaming event. */
type Owner = 'parent' | string;
interface StreamEvent {
  kind: 'thinking' | 'tool' | 'tool-result' | 'error'
  /**
   * Echo of a submitted user prompt — the `❯` chevron block in the
   * transcript. Distinct from `'info'` (generic informational text)
   * so consumers can count user messages and rebuild prompt history
   * without filtering by text-prefix regex.
   *
   * `text` is the **raw** prompt without the `❯ ` prefix; renderers
   * prepend the chevron. `refs` offsets are aligned to this raw text.
   */
  | 'user-prompt'
  /**
   * Generic informational banner — status notice, transient message.
   * Reserved for non-user content; user prompts use `'user-prompt'`.
   */
  | 'info' | 'separator' | 'markdown' | 'spawn-start' | 'spawn-end'
  /**
   * Compaction boundary — produced by `eventsFromTurns` when a turn
   * carries a `compact-summary` block. Marks the point at which the
   * model's view of history was replaced by an LLM-generated summary.
   * `text` is the summary body; `compact` carries the extra metadata
   * needed for the visual card (replaced-turn count, model, usage).
   *
   * The renderer is free to style this as a collapsed card; the user
   * can still scroll above the marker to see the original turns —
   * compaction never deletes from disk.
   */
  | 'compact-summary'
  /**
   * Background task lifecycle banner. Produced when a task started via
   * `shell({ run_in_background: true })` terminates — natural exit, kill,
   * or `agent.destroy()` teardown. The framework injects a
   * `<task-notification>` text block into the next user-turn so the model
   * sees it inline with its prompt; the renderer suppresses the raw text
   * block in favor of this structured event for a cleaner transcript.
   *
   * `text` is a short human-readable summary (e.g. `npm run build (4.2s)
   * — exited 0`); `task` carries the structured fields the banner reads.
   * On replay (`eventsFromTurns`), the event is re-synthesized from the
   * persisted `<task-notification>` XML inside the user turn's text block;
   * the underlying text block is dropped from the rendered stream so the
   * banner doesn't double-paint alongside it.
   */
  | 'task-notification';
  text: string;
  /**
   * Background task metadata — only set on `task-notification` events.
   * Mirrors the `<task-notification>` XML's fields. Renderer reads this
   * (not `text`) for the structured banner so swapping languages /
   * formats stays a renderer-level concern.
   */
  task?: {
    taskId: string; /** `'exited'` (clean or non-zero) or `'killed'` (we issued SIGTERM). */
    status: 'exited' | 'killed';
    exitCode: number; /** Absolute path to the log file — banner renders an OSC 8 hyperlink to it. */
    outputPath: string; /** Original command, truncated for display by the renderer if long. */
    command: string; /** Wall-clock spawn → exit duration. */
    durationMs: number;
  };
  /**
   * Compaction metadata — only set on `compact-summary` events. Mirrors
   * the underlying `SessionContentBlock` shape but flattened to numbers
   * so the renderer doesn't need to import the agent's TurnUsage type.
   */
  compact?: {
    /** Number of turns the summary replaces (renderer shows "N turns compacted"). */replacedCount: number; /** Model used for the summary call. */
    model: string; /** Unix-ms when compaction completed. */
    compactedAt: number; /** Token usage from the summary call. Renderer formats for display. */
    inputTokens: number;
    outputTokens: number;
    cacheReadTokens: number;
    cacheCreationTokens: number;
  };
  /**
   * For `markdown` events: true while the block is still receiving deltas.
   * Renderers keep their markdown parser in streaming mode while this is
   * true (OpenTUI's `<markdown streaming>` leaves the trailing block
   * unstable to absorb tokens as they arrive). Flipped to false on
   * `turn:after`.
   */
  streaming?: boolean;
  /** Subagent that produced this event. Absent / 'parent' = top-level agent. */
  childId?: string;
  /** Nesting depth — 0 = parent, ≥ 1 = subagent (matches zidane's spawn depth). */
  depth?: number;
  /**
   * Canonical tool name for `tool` / `tool-result` events. Lets the renderer
   * filter by tool — e.g. hide the parent's `spawn` tool-result when
   * `hideSubagentOutput` is on, since the spawn-end marker already shows the
   * same stats. Absent for non-tool events.
   */
  tool?: string;
  /**
   * Matches `tool_use.id` on the assistant turn's `tool_call` block. Lets
   * out-of-process consumers (GUI, SDK, logger, replayer) pair `tool` ↔
   * `tool-result` events by id instead of falling back to fragile positional
   * heuristics within a tool-name bucket — the latter breaks visibly the
   * moment two parallel calls share a tool name (common for `read_file`,
   * `edit`, `multi_edit`).
   *
   * Always set when `kind` is `tool` / `tool-result` / `error` for a real
   * tool call; absent for non-tool events (`markdown`, `thinking`,
   * `user-prompt`, `separator`, `info`, `spawn-start`, `spawn-end`,
   * `compact-summary`) and for `error` events that don't have a callId
   * in scope (provider / session-level failures).
   */
  callId?: string;
  /**
   * Raw tool input arguments — only set on `tool` events. Carried so the
   * renderer can run per-tool formatters (e.g. `↳ Read src/foo.ts L10-25`)
   * without parsing JSON back out of `text`. Plain object, same shape the
   * model emitted; renderers must treat unexpected fields as optional.
   */
  input?: Record<string, unknown>;
  /**
   * Structured payload for `edit` / `multi_edit` / `write_file` tool calls,
   * extracted from the model's `input` and (for `write_file`) paired with
   * a pre-write snapshot read at hook time. Drives the `EditDiffBlock`
   * renderer when {@link Settings.showEditDiffs} is on; absent means
   * "render as a plain `↳ name(args)` line" (the unstructured fallback).
   */
  edit?: EditPayload;
  /**
   * Highlighted spans within `text`, in source order. Used by the renderer
   * to colorize completion references in submitted prompts (`user-prompt`
   * events). Provider-agnostic — the renderer only reads `start`, `end`,
   * `providerId`. Spans are half-open `[start, end)`; overlapping spans
   * are caller-resolved before reaching here. Field name matches
   * `CompletionReference.providerId` and `PromptSegment.providerId` so
   * the same identifier flows end-to-end.
   */
  refs?: readonly {
    start: number;
    end: number;
    providerId: string;
  }[];
  /**
   * Attachment metadata for `user-prompt` events — name, media-type, and
   * byte size of each file the user attached (image drag, multiline paste,
   * etc.). The full content travels in the `PromptPart[]` sent to the
   * model; only lightweight metadata is echoed to the transcript.
   */
  attachments?: readonly {
    name: string;
    mediaType: string;
    size: number;
  }[];
  /**
   * `SessionTurn.id` the event was emitted by — set both on historical
   * events synthesized in `eventsFromTurns` and on live events tagged by
   * the agent hooks (via `StreamHookContext.turnId` / `ToolHookContext.turnId`).
   * Drives the TUI's "select turn" mode: each turn is addressable as a
   * highlightable group of events.
   *
   * Absent on synthetic events that don't map to a turn (`spawn-start`,
   * `spawn-end`, `separator`, etc.) and on events emitted before the
   * first `turn:before` fires on a freshly-mounted session.
   */
  turnId?: string;
}
/**
 * Structured edit data attached to a `tool` event for `edit` /
 * `multi_edit` / `write_file`. Lets the renderer compute a unified diff
 * without re-parsing the JSON input string.
 *
 * - `edit` produces one hunk with `replaceAll` defaulting to false.
 * - `multi_edit` produces one hunk per `edits[]` step (in order).
 * - `write_file` produces a single hunk where `oldString` is the
 *   pre-write file content (empty for a fresh create) and `newString`
 *   is the new content; `replaceAll` is meaningless here and absent.
 *
 * Live capture happens in the TUI's `tool:before` hook (where the
 * pre-write snapshot can be read off disk before the tool runs).
 * Historical replay from persisted turns can only reconstruct the
 * edit/multi_edit shape — write_file historical events have no
 * pre-write content and render with `oldString: ''` (= every line is
 * an addition), matching git's "new file" diff convention.
 */
interface EditPayload {
  /** Tool name that produced this payload — drives the header glyph + result-suppression matrix. */
  tool: 'edit' | 'multi_edit' | 'write_file';
  /** Workspace-relative file path. Used as the diff header. */
  path: string;
  /** Ordered list of `(old, new)` pairs. Each rendered as one hunk. */
  hunks: readonly EditHunk[];
  /**
   * Per-hunk outcome of the approval / application pipeline, 1:1 with
   * {@link hunks}. Absent on a fresh tool call before the gate decides —
   * the renderer treats absence as "all applied" (the legacy code path
   * predates partial application).
   *
   * Surfaces in the transcript so a partially-approved `multi_edit` shows
   * the denied hunks alongside the applied ones, dimmed / badged. Live
   * approval populates this from the modal's per-edit decisions; replay
   * reconstructs it from the tool_result body (which the tool serializes
   * in a stable line shape — see `multi_edit`).
   */
  outcomes?: readonly EditOutcome[];
  /**
   * Pre-write file content snapshot, captured by the TUI's `tool:before`
   * hook for all three edit tools. Lets the diff renderer build a
   * contextual unified diff with real file line numbers (matching what
   * the user's editor shows) instead of synthetic `@@ -1,N +1,M @@`
   * headers anchored at line 1 within each hunk's snippet.
   *
   * Absent on historical replay (the field isn't persisted — payloads
   * are reconstructed from `input` alone in `eventsFromTurns`). The
   * renderer falls back to `buildUnifiedDiff` in that case.
   *
   * NOT serialized into session turns; lives only on the live in-memory
   * `StreamEvent` to avoid bloating session files with snapshots of
   * every edited file.
   */
  priorContent?: string;
}
interface EditHunk {
  oldString: string;
  newString: string;
  /** Mirrored from the tool's `replace_all` flag — surfaced as `× N` in the hunk header. */
  replaceAll?: boolean;
}
/**
 * Per-hunk lifecycle state surfaced in the diff/apply viewer. Mirrors the
 * states a `multi_edit` step can be in across the approve → apply pipeline:
 *
 *   - `applied`  — the gate let it through and the tool wrote it.
 *   - `denied`   — the user (or an auto-deny rule) refused this specific edit.
 *   - `skipped`  — the user explicitly stepped past this edit in the modal,
 *                  or an earlier edit failed and the batch unwound.
 *   - `failed`   — the gate let it through but the tool body rejected the
 *                  edit (`old_string` not found, ambiguous, etc.).
 *   - `pending`  — placeholder used while the modal is open. Never persisted.
 */
type EditOutcomeKind = 'applied' | 'denied' | 'skipped' | 'failed' | 'pending';
interface EditOutcome {
  kind: EditOutcomeKind;
  /** Short human-readable explanation surfaced in the transcript badge. */
  reason?: string;
}
interface Picked {
  provider: ProviderAuth;
  model: string;
  /**
   * Reasoning effort the user selected for `model`. Only meaningful when
   * the active model exposes reasoning (`ModelInfo.reasoning === true`);
   * `undefined` otherwise. Forwarded to `agent.run` as `thinking` so
   * providers route it to their per-model reasoning control.
   */
  effort?: ThinkingLevel;
  /**
   * Enabled model-specific options for `model`, keyed by option id (e.g.
   * `{ fast: true }`). Only meaningful for models that declare
   * `ModelInfo.options`; reset whenever the model changes so a toggle never
   * leaks onto a model that doesn't support it. Forwarded to `agent.run` as
   * `modelOptions`.
   */
  modelOptions?: Record<string, boolean>;
}
interface SessionMeta {
  id: string;
  title: string;
  /** Total turns (user + assistant + system). */
  turnCount: number;
  /** Count of `role: 'user'` turns — the volume of human messages. */
  userMessageCount: number;
  /** Top-level runs recorded against the session (one per `agent.run()` call). */
  runCount: number;
  /**
   * Project this session was created under (see {@link SessionData.projectRoot}).
   * Surfaced on the meta row so the sessions screen can show a
   * per-project label when `Settings.showAllProjects` is on. Absent
   * for legacy pre-tagging sessions.
   */
  projectRoot?: string;
  updatedAt: number;
}
/**
 * How `tool` events render in the transcript. Drives `Settings.toolCallDisplay`.
 *
 *   - `'hidden'` — `tool` events filtered out entirely (the matching
 *     `tool-result` block still shows unless its own toggle suppresses
 *     it). Use when the transcript should read like a chat log.
 *   - `'formatted'` (default) — each tool renders a clean per-tool
 *     summary line (e.g. `↳ Read src/foo.ts`, `↳ Shell git status`).
 *     Native tools have curated formatters; unknown / MCP tools fall
 *     back to a compact `↳ <name>` line.
 *   - `'full'` — debug view: `↳ <name>` header plus the entire raw
 *     `input` rendered as syntax-highlighted JSON. Useful for
 *     inspecting what the model actually sent.
 */
type ToolCallDisplay = 'hidden' | 'formatted' | 'full';
/**
 * How `edit` / `multi_edit` / `write_file` diffs render in the transcript.
 * Gated by {@link Settings.showEditDiffs}; when that's off, the raw `↳ name(args)`
 * line is used regardless of this setting.
 *
 *   - `'full'`    — current behavior: the entire unified diff body, with
 *     line numbers in the gutter. Uses real file line numbers when the
 *     pre-write snapshot is available (live calls), synthetic when not
 *     (historical replay).
 *   - `'compact'` — header line + one short summary line per hunk
 *     (`L42 · +2 −1 · old → new`). The full diff body is dropped from
 *     the transcript; users open the approval modal or scroll the file
 *     to see the changes in detail.
 */
type EditDiffDisplay = 'compact' | 'full';
/**
 * Chat-screen chrome density. Drives a small, opinionated set of UI
 * collapses without touching the runtime feature surface:
 *
 *   - `'full'` (default) — every shortcut the chat screen advertises is
 *     visible: bottom-bar hints (agent / model / skills / session /
 *     settings / sessions / update), the prompt-overlay shortcuts
 *     (`↵ send`, `shift+↵ newline`, `↑↓ history`, `ctrl+s messages`) +
 *     the `@ files` / `/ skills` triggers, and the `ctrl+x session`
 *     chip on the title meta.
 *   - `'minimal'` — strip the chat session UI to just the controls a
 *     user actually keeps hands on:
 *       Bottom bar (idle): `agent`, `model/effort`, `keybindings`.
 *         Sessions / settings / session-details / update chip are
 *         dropped; the full catalog stays one keystroke away via the
 *         keybindings panel (`ctrl+y`).
 *       Prompt-overlay hints (idle): only the `@ files` / `/ skills`
 *         triggers — the resting send/newline/history line is dropped.
 *       Title meta: drop the `ctrl+x session` chip. Cwd + message /
 *         turn stats stay.
 *
 * Busy / pending / select-turn / queue / approval states keep their
 * full hint sets in both modes — those affordances are load-bearing
 * (abort, navigate, approve) and not the noise the minimal mode is
 * targeting.
 */
type UiMode = 'full' | 'minimal';
/** Persisted, user-toggleable transcript filters and modes. */
interface Settings {
  showThinking: boolean;
  /**
   * Rendering mode for `tool` events. See {@link ToolCallDisplay}.
   * Replaces the legacy `showToolCalls: boolean` (TUI auto-migrates
   * old `state.json` files on first launch).
   */
  toolCallDisplay: ToolCallDisplay;
  showToolResults: boolean;
  /**
   * Safe mode — when enabled, every tool call requires explicit user
   * approval unless it's covered by the project safelist (`projects.json`)
   * or the implicit read-only allow-list. Default: on.
   */
  safeMode: boolean;
  /**
   * Hide a subagent's internal events (its streamed markdown, thinking,
   * tool calls, and tool results) from the transcript. The `🌱` start and
   * `✓` end markers stay visible so the user still sees that a subagent
   * is working and when it finished. Default: on — subagent runs are
   * usually noisy and the parent's final response already summarizes them.
   *
   * When off, those events are visible and wrapped in a dim bordered box
   * for clear visual hierarchy.
   */
  hideSubagentOutput: boolean;
  /**
   * Active theme id — looked up in `BUILTIN_THEMES` to resolve the palette,
   * select styling, syntax highlight tokens, etc. Default: `'default'`.
   * Unknown ids fall back to `DEFAULT_THEME` rather than throwing.
   */
  theme: string;
  /**
   * Show sessions from every project (and pre-tagging legacy ones)
   * in the sessions list, instead of filtering to the current
   * project. Off by default — each project shows only its own
   * conversations. Flip on to inspect / jump across projects without
   * leaving the TUI. Per-row project labels appear when on.
   */
  showAllProjects: boolean;
  /**
   * Auto-resume the previously-active session on launch. Default: on.
   * When off, the TUI always lands on the sessions list (or a fresh
   * session if the list is empty), regardless of what `state.json`
   * remembers as the last session id.
   *
   * The setting is read once on launch — toggling it mid-session
   * affects the NEXT start, not the current one. `state.lastSessionId`
   * is still tracked underneath so flipping the setting back on
   * restores the resume behavior without losing the pointer.
   */
  resumeLastSession: boolean;
  /**
   * Persist large `tool_result` outputs to disk and substitute a
   * `<persisted-output>` stub (preview + filesystem path) inline. Built-in
   * profiles default to 8 KiB threshold via `behavior.persistThreshold`;
   * this setting is the user-facing kill-switch. When off, the TUI
   * overrides `behavior.persistThreshold = 0` at agent build time so the
   * loop's persistence branch never fires.
   *
   * The setting takes effect on the next session activation — toggling
   * mid-stream doesn't tear down an in-flight tool call. Persisted blobs
   * already on disk stay readable via `read_file`; only future tool
   * results are affected by the flip.
   *
   * Default: on.
   */
  persistToolResults: boolean;
  /**
   * Fire {@link compactConversation} automatically when the latest turn's
   * input-token usage crosses {@link Settings.autoCompactThreshold} of the
   * model's effective context window (raw window minus a fixed output
   * reserve — see `OUTPUT_RESERVE_TOKENS` in `chat/providers.ts`).
   *
   * When the trigger fires, the TUI:
   * - Appends an `info` event to the transcript announcing the compaction.
   * - Runs `compactConversation` in the background — the prompt area stays
   *   visible so the user can compose the next message while it runs.
   * - The next prompt submission `await`s the in-flight compaction before
   *   calling `agent.run()`, so the new run sees the post-compaction
   *   message history (no race with the summary's `appendTurns`).
   * - Does NOT auto-continue the conversation after compaction completes
   *   — the user controls when the next turn fires.
   *
   * Default: on.
   */
  autoCompact: boolean;
  /**
   * Threshold for {@link Settings.autoCompact}, expressed as a fraction of
   * the effective context window. `0.8` means "fire when the latest turn
   * used 80% of the effective window". Values outside `(0, 1)` are
   * treated as misconfiguration and skip the trigger.
   *
   * The settings modal cycles through `0.6 / 0.7 / 0.8 / 0.9` — coarse
   * enough that users don't get lost, fine enough to tune for verbose
   * workflows (lower) versus context-heavy ones (higher).
   *
   * Default: `0.8`.
   */
  autoCompactThreshold: number;
  /**
   * Render `edit` / `multi_edit` / `write_file` tool calls as a clean
   * unified diff (red `-` / green `+` lines) instead of the raw
   * `↳ name({"path":"…","old_string":"…"})` JSON dump. When on, the
   * paired successful `tool-result` ("Edited path: …") is suppressed
   * too since the diff already conveys success; errors still show.
   *
   * Off falls back to the unstructured `↳ name(args)` line + the
   * regular `tool-result` block — same display the TUI had before
   * the diff feature shipped. Default: on.
   */
  showEditDiffs: boolean;
  /**
   * Density of the edit-diff rendering. See {@link EditDiffDisplay}.
   * Only consulted when {@link showEditDiffs} is on. Default: `'full'`.
   */
  editDiffDisplay: EditDiffDisplay;
  /**
   * Renderer target + max fps (the same value drives both — uncapping
   * `maxFps` past the target just burns CPU on a terminal that's
   * already painting at the target rate). The Settings modal cycles
   * through `30 / 60 / 120`:
   *
   *   - `30`  — easy on CPU + battery; matches OpenTUI's historical
   *     default. Streaming still feels fine; modal animations and
   *     cursor blink visibly stutter on fast input.
   *   - `60`  — recommended default. Matches the refresh rate of
   *     virtually every non-ProMotion display; sweet spot between
   *     smoothness and load.
   *   - `120` — only worth it on a 120Hz / ProMotion display + a
   *     modern terminal (Ghostty, Kitty, Alacritty, iTerm2 ≥ 3.5).
   *     Costs roughly 2× the CPU of 60 for marginal gains on most
   *     setups — sustained 120fps over SSH / nested tmux can add
   *     input latency from stdout backpressure.
   *
   * Applied live via `renderer.targetFps` / `renderer.maxFps` — flipping
   * the setting takes effect on the next frame, no restart needed.
   *
   * Default: `60`.
   */
  targetFps: number;
  /**
   * Expose interactive tools (`ask_user`, `present_plan`) to the agent.
   * When off, both tools are omitted from the agent's tool set at session
   * activation — the model can't pause for clarifying questions and can't
   * submit a structured plan, so it must proceed on its best guess and
   * narrate any plan inline in its final message instead.
   *
   * The system prompt also adapts: the `INTERACTION_GUIDANCE` block is
   * replaced with a short note explaining the lack of interactive tools,
   * and Plan-mode's "explore → ask → propose" loop becomes
   * "explore → propose-in-chat".
   *
   * Takes effect on the next session activation — flipping mid-stream
   * doesn't tear down an in-flight `ask_user` / `present_plan` call.
   * Default: on.
   */
  allowInteraction: boolean;
  /**
   * Drip-feed the assistant's text content character-by-character at a
   * smooth cadence (typewriter effect) instead of committing each
   * provider chunk in batches as it arrives. The display rate is capped
   * so fast LLMs don't blast a wall of text into the transcript; an
   * adaptive burst kicks in when the buffer falls behind, and turn
   * boundaries always flush any remaining buffered content immediately
   * — so the response never APPEARS slower to finish than it actually
   * did, just easier to read mid-flight.
   *
   * Off falls back to the legacy ~30Hz batched flush (every provider
   * delta lands in the transcript on the next tick, unsmoothed).
   *
   * Takes effect on the next provider delta — flipping mid-stream is
   * safe; the buffer reads the setting on every tick.
   *
   * Default: on.
   */
  smoothStreaming: boolean;
  /**
   * Show the subtle one-line "in progress todo" indicator above the
   * prompt input. Hidden entirely when there's no session, no active
   * run, or no `in_progress` item in the active run's `todowrite` list
   * — so flipping this off is a clean "I don't want even the bar".
   *
   * The dedicated modal (`ctrl+t` by default — see {@link KeyAction})
   * is unaffected: opening it is an explicit user gesture.
   *
   * Default: on.
   */
  showTodoIndicator: boolean;
  /**
   * Render the inline gradient throbber (cycling hex/symbol glyphs +
   * optional "Thinking…" label) at the tail of the transcript while a
   * run is streaming. The throbber is purely decorative — `busy` state
   * is also surfaced by the prompt's status row and the streaming
   * markdown's incremental updates, so users who find the animated
   * glyphs noisy / distracting can flip this off without losing the
   * "assistant is working" signal.
   *
   * Default: off — opt-in to keep the transcript calm by default.
   */
  showThrobber: boolean;
  /**
   * Quietly check the npm registry for a newer release of the host
   * package (`zidane-tui` for the shipped binary; any consumer can
   * pass their own via `<App auto-update>` / `useUpdateCheck`).
   *
   * Hot path is cache-driven: one HTTP roundtrip per 24 h, persisted
   * under `userDir/update-check-<pkg>@<channel>.json`. Boot is never
   * blocked — the check fires after first paint with a 3-second
   * timeout and degrades silently on network failure. CI runners
   * (env `CI`), the npm-ecosystem `NO_UPDATE_NOTIFIER`, and the
   * project-specific `ZIDANE_NO_UPDATE` env vars all force-disable
   * regardless of this setting.
   *
   * Surfaces in the footer as a passive `↑ vX.Y.Z` chip (renderable
   * by `useUpdateCheck` and `buildUpdateHint`). The explicit
   * `zidane upgrade` subcommand bypasses both this toggle and the
   * env opt-outs (it's an explicit user gesture).
   *
   * Default: on.
   */
  checkForUpdates: boolean;
  /**
   * Chat-screen chrome density. See {@link UiMode} for the contract —
   * `'full'` keeps every advertised shortcut visible, `'minimal'`
   * collapses the chat session UI to agent / model / keybindings + the
   * `@` / `/` triggers, with the rest one keystroke away via `ctrl+y`.
   * Default: `'full'`.
   */
  uiMode: UiMode;
  /**
   * Which scope(s) of personal instructions to inject into the system
   * prompt at session activation. Discovered files:
   *
   *   - `'none'`    — skip discovery entirely. No `AGENTS.md` / `CLAUDE.md`
   *                   files are loaded. Useful for reproducible reviews,
   *                   demos, debugging the base system prompt, or running
   *                   zidane against a repo whose committed `AGENTS.md`
   *                   you don't trust yet.
   *   - `'user'`    — `~/.agents/AGENTS.md`, `~/.{prefix}/AGENTS.md`,
   *                   `~/.claude/CLAUDE.md`. Cross-project preferences
   *                   (coding style, "always run lint before commit", …).
   *   - `'project'` — `<root>/AGENTS.md`, `<root>/.agents/AGENTS.md`,
   *                   `<root>/.{prefix}/AGENTS.md`, `<root>/CLAUDE.md`.
   *                   Repo-local context ("use pnpm, not npm").
   *   - `'both'`    — union; user files render first (broader), project
   *                   second (closer to the conversation, narrower
   *                   context wins in the model's eyes).
   *
   * Within each enabled scope, EVERY matching file is loaded — no
   * shadowing. Empty / whitespace-only files are skipped.
   *
   * Takes effect on the next session activation. Default: `'both'`.
   */
  userInstructionsScope: 'both' | 'none' | 'project' | 'user';
  /**
   * Allowlist of skill names to expose to the agent + slash-command picker.
   * `undefined` means "every discovered skill" (default). An empty array
   * fully disables the skills subsystem (no scan, no tool injection).
   */
  enabledSkills?: readonly string[];
  /**
   * Allowlist of MCP server names (from `.{prefix}/mcps.json`). Same
   * semantics as `enabledSkills`: `undefined` enables every discovered
   * server; `[]` disables all.
   */
  enabledMcps?: readonly string[];
  /**
   * Per-server **deny-list** of tool names to hide from the model. Maps
   * 1:1 onto `McpServerConfig.disabledTools` at agent-construction
   * time. Polarity is intentionally opposite to `enabledMcps`:
   *
   *   - missing entry (or `undefined`)  → every advertised tool enabled
   *   - `[]`                            → every advertised tool enabled
   *   - `['big_tool']`                  → drop `big_tool`; keep the rest
   *
   * The deny-list semantics mean a server that ships a new tool in a
   * later release auto-enables for users who haven't explicitly opted
   * it out — opposite preference from server-level toggles (where a
   * new server should NOT silently appear). The TUI's MCP settings
   * panel writes here when the user unchecks a tool checkbox.
   *
   * Composes with `McpServerConfig.disabledTools` on the JSON-side via
   * union — a tool listed in either is dropped. JSON-side wins on
   * conflict (you can't re-enable a JSON-pinned tool from the UI).
   */
  disabledMcpTools?: Record<string, readonly string[]>;
  /**
   * Master switch for the prompt textarea's paste-as-attachment
   * pipeline. When `true` (default), the host intercepts paste events
   * and folds:
   *
   *   - single-line pastes that resolve to a real file on disk
   *     (drag-and-drop or `file://` URLs) into a binary attachment
   *     tagged with the matching MIME type, and
   *   - multiline pastes into a `paste.txt` document attachment so
   *     the prompt textarea doesn't balloon vertically.
   *
   * Flip to `false` to opt out entirely — every paste lands inline in
   * the textarea verbatim, regardless of size, shape, or whether the
   * content matches a file path on disk. Useful for users who paste
   * short snippets often and find the auto-attach behavior breaks
   * their composing flow.
   *
   * Doesn't affect the rest of the multimodal pipeline — embedders
   * that build `PromptPart[]` directly (image messages from a GUI,
   * dropped files via a custom handler) still flow through unchanged.
   * Default: on.
   */
  attachmentsEnabled: boolean;
  /**
   * What happens when the user presses Ctrl+C.
   *
   *   - `'quit'`           — exit immediately (default, legacy behavior).
   *   - `'quit-confirm'`   — show a confirmation modal; Enter / y confirms,
   *                          Escape / n cancels. A second Ctrl+C while the
   *                          modal is open also confirms.
   *   - `'stop-then-quit'` — first press aborts the active run or clears the
   *                          input; second consecutive press (within 1.5 s)
   *                          exits. If there's nothing to stop and the input
   *                          is empty, the first press exits directly.
   */
  ctrlCBehavior: 'quit' | 'quit-confirm' | 'stop-then-quit';
}
//#endregion
//#region src/chat/turn-selection.d.ts
/** Tools whose `tool-result` event is suppressed when `showEditDiffs` is on. */
declare const EDIT_TOOL_NAMES: ReadonlySet<string>;
/**
 * Recognize a tool-result body as carrying NON-success information so the
 * renderer doesn't suppress it under `showEditDiffs`. Three categories:
 *
 *   - `edit` → "Edit error: …"
 *   - `write_file` permission errors wrapped by the loop → "Tool failed: …"
 *   - `multi_edit` → legacy single-line error `multi_edit error: …`, OR
 *     a result carrying an `<edit-outcomes>…</edit-outcomes>` annotation
 *     block. The TUI only appends the annotation when at least one hunk
 *     was NOT applied, so its mere presence is the signal — the result
 *     body needs to stay visible next to the diff so the user can read
 *     denial / skip / failure reasons longer than the per-hunk badge.
 *   - Fully-denied gate emit (`[fully denied] <edit-outcomes>…`) likewise
 *     stays visible.
 *
 * Exported for unit-testability of the visibility matrix.
 */
declare function isEditErrorResult(text: string): boolean;
/**
 * Per-event visibility — filters honor user toggles and the
 * `hideSubagentOutput` setting. When subagent output is hidden:
 *   - Child-agent events are filtered down to the `spawn-start` /
 *     `spawn-end` markers so the user still sees "🌱 working… 🌳 done".
 *   - The parent's `tool-result` for `spawn` is hidden too. Its body
 *     duplicates `spawn-end`'s stats line *and* the parent's next
 *     markdown turn; showing it again produces an extra
 *     `┃ [sub-agent child-1] Completed …` block users just want gone.
 *
 * Renderer-agnostic — returns plain `boolean` so TUI / GUI consumers
 * can filter events identically.
 */
declare function isVisible(event: StreamEvent, settings: Settings): boolean;
/**
 * Build the `resultTurnId → owningAssistantTurnId` map used by the select-
 * turn mode to coalesce a tool-call's surrounding turns into ONE navigation
 * stop.
 *
 * Protocol shape: every `tool_call` block in an assistant turn is closed by
 * a matching `tool_result` block in the *next* user turn (the agent loop's
 * history validator depends on this). When the next user turn's only events
 * are `tool-result`s — i.e. it's pure plumbing for the prior assistant
 * turn — we map it back to that assistant turn here. The select-turn nav
 * index ({@link selectableTurnIds}) skips owned turns, and the renderer's
 * highlight gate ({@link isTurnHighlighted}) extends the selection accent
 * from the assistant turn to the events of any turn it owns. Net effect:
 *
 *   - Navigation never lands the cursor on a result-only turn whose own
 *     events may be hidden by `showToolResults: false` — the cursor
 *     wouldn't be visible.
 *   - Selecting an assistant turn highlights the call AND its result as
 *     one unit, matching the user's mental model of "one message".
 *
 * Owner-lookup is conservative: result-only turns with no matching prior
 * assistant turn (orphaned — usually because the parent was deleted)
 * stay selectable so the user can act on them via the turn-details modal.
 *
 * Subagent (`childId` set) events are ignored — they live in a separate
 * conversation tree.
 */
declare function turnSelectionOwnership(events: readonly StreamEvent[]): Map<string, string>;
/**
 * Render-time check: should `event` paint with the selection accent?
 *
 * `true` when the event's own turn is selected, OR when the selected turn
 * `owns` the event's turn via {@link turnSelectionOwnership} (the call and
 * its tool-result rows highlight together). `false` when nothing is
 * selected or the relationship doesn't apply.
 *
 * Pure. Renderer-agnostic — the TUI's `<Transcript>` uses it; a GUI's
 * equivalent walks the same rule.
 */
declare function isTurnHighlighted(event: Pick<StreamEvent, 'turnId'>, selectedTurnId: string | null, ownership: ReadonlyMap<string, string>): boolean;
/**
 * Deduplicated, in-order list of **parent-conversation** turn ids that appear
 * in a rendered transcript — the navigation index for the TUI's select-turn
 * mode. Three classes of turns are deliberately skipped:
 *
 *   - **Subagent turns** (`childId` set). Nested execution detail; the
 *     user's mental model of a "message" is the conversational exchange,
 *     not each spawn turn. Also filtered out by `isVisible` under
 *     `hideSubagentOutput: true` — selecting them would highlight nothing.
 *   - **Result-only turns** — see {@link turnSelectionOwnership}. These get
 *     coalesced into the assistant turn that emitted their tool_calls.
 *   - **Settings-hidden turns** (when `settings` is supplied). A turn whose
 *     every event fails {@link isVisible} would render no rows — landing
 *     the cursor there hides it from the user entirely. The check is opt-
 *     in so SDK callers without a Settings object keep the legacy
 *     "everything visible" behavior.
 *
 * Synthetic events (separator, spawn-start, spawn-end) have no `turnId` and
 * are skipped naturally.
 */
declare function selectableTurnIds(events: readonly StreamEvent[], settings?: Settings): string[];
//#endregion
//#region src/chat/safe-mode-context.d.ts
/**
 * Outcome of an approval prompt. Four bulk decisions match the original
 * single-edit modal contract; `partial` is the multi-edit branch — the
 * user accepted a subset of an `edit` / `multi_edit` / `write_file` call.
 *
 * `partial.mask` is 1:1 with the call's hunks (in input order): `true`
 * means apply, `false` means deny. The gate handler walks the mask to
 * build per-hunk outcomes; the TUI rebinds `ctx.input.edits` to the
 * approved subset and the renderer reconstructs the full per-hunk view
 * from the `<edit-outcomes>` annotation appended to the tool result.
 */
type ApprovalDecision = 'accept-once' | 'accept-session' | 'accept-safelist' | 'deny' | {
  kind: 'partial';
  mask: readonly boolean[];
};
/**
 * Identifies the caller behind an approval prompt — the parent agent or
 * a specific subagent (`child-N`). The modal's right-side title pins the
 * label so the user knows which agent issued the call when subagents
 * bubble their gates up through the parent's hook bus.
 */
type ApprovalOriginator = {
  kind: 'parent';
} | {
  kind: 'child';
  label: string;
};
interface ApprovalRequest {
  id: string;
  tool: string;
  input: Record<string, unknown>;
  resolve: (decision: ApprovalDecision) => void;
  /** Caller attribution. Absent ≡ `{ kind: 'parent' }`. */
  originator?: ApprovalOriginator;
}
/** Function signature consumed by `tool:gate` handlers + the child-tool wrap. */
type RequestApproval = (tool: string, input: Record<string, unknown>, originator?: ApprovalOriginator) => Promise<ApprovalDecision>;
interface SafeModeActions {
  /** Request a decision; resolves once the user picks. */
  requestApproval: RequestApproval;
  /** Resolve the head and shift the queue forward. */
  resolveHead: (decision: ApprovalDecision) => void;
  /** Resolve all pending with `deny`. Used on abort / hard exit. */
  denyAll: () => void;
}
/**
 * Owns the queue + actions. Splits the value across two contexts so a queue
 * change doesn't invalidate every callback memo that closes over the actions.
 */
declare function SafeModeProvider({
  children
}: {
  children: ReactNode;
}): import("react/jsx-runtime").JSX.Element;
declare function useSafeModeQueue(): readonly ApprovalRequest[];
declare function useSafeModeActions(): SafeModeActions;
//#endregion
//#region src/chat/prompt-segments.d.ts
/**
 * Pure string + reference math for rendering a submitted prompt with chip
 * pills around completion references. Renderer-agnostic — the TUI walks
 * the segments into OpenTUI `<text>` nodes, a GUI walks them into JSX
 * spans + `<span class="chip">` pills. No layout engine assumptions.
 */
/**
 * Highlight span — half-open `[start, end)` over the source string.
 *
 * Offsets are JS string indices (UTF-16 code units) — the same convention used
 * everywhere else in the chat layer (`text[i]`, `m.index`, `String.slice`).
 * Surrogate pairs that straddle a span boundary would render malformed, but
 * realistic prompts in this surface (terminal text + ASCII triggers) keep that
 * risk theoretical; callers feeding emoji-heavy content should normalize to
 * codepoint walks upstream.
 */
interface PromptSegmentRef {
  start: number;
  end: number;
  /** Provider id tagging this span (`'skills'`, `'files'`, …). Free-form. */
  providerId: string;
}
/**
 * One atomic unit emitted by {@link splitPromptSegments}. Plain segments
 * are word-sized so wraps land cleanly between words; chip segments
 * carry the source `providerId` (`'skills'`, `'files'`, …) so renderers
 * can pick per-kind colors without re-walking the ref list.
 */
type PromptSegment = {
  kind: 'plain';
  text: string;
} | {
  kind: 'chip';
  text: string;
  providerId: string;
};
/**
 * Split a prompt buffer into word-sized atomic segments suitable for a
 * flex-row + flex-wrap renderer (TUI) or a `display: inline` flow with
 * inline-block chips (GUI). Each chip becomes one segment (atomic —
 * never broken across rows); each plain run is split into "word +
 * trailing space" units so wraps land at clean word boundaries.
 *
 * Robust to:
 *   - Overlapping refs — sorted by start; later refs that overlap are
 *     dropped via the first-wins rule.
 *   - Out-of-bounds refs — dropped entirely when `end > text.length` or
 *     `start >= text.length`. Partial clipping would silently truncate
 *     a chip's label; the caller is in a better position to surface the
 *     mismatch (typically a stale `refs` array referencing a previous text).
 *   - Whitespace-only plain runs — emitted as their own plain segment
 *     so chip-adjacent-to-chip cases keep the original spacing.
 *
 * Word splitter rationale: `\S+\s*` keeps trailing whitespace attached
 * to its preceding word so wrap boundaries land between words (cleanly).
 * A leading-whitespace-only segment is captured by `\s+` so we don't
 * drop it entirely when the plain run starts with a space.
 */
declare function splitPromptSegments(text: string, refs: readonly PromptSegmentRef[]): PromptSegment[];
//#endregion
//#region src/chat/tool-formatters.d.ts
/**
 * Per-tool display metadata + one-line formatters consumed by any
 * surface that renders a `tool` event in `'formatted'` mode (see
 * `Settings.toolCallDisplay`).
 *
 * Each native tool gets a curated entry — a `displayName` verb that
 * reads in sentence case (e.g. "Read", "Shell") and a `format` callback
 * that pulls the most informative bits out of the model's raw input to
 * a single scannable line. Unknown tools (MCP servers, host-added
 * tools, future zidane additions) fall back to {@link formatToolCall}
 * returning `null` — the renderer then shows a minimal `↳ <name>` line.
 *
 * Renderer-agnostic: returns plain data (`{ target, meta }`) so the
 * TUI's React/OpenTUI surface and any future GUI consumer can paint
 * the same shape in their own style. Lives in `zidane/chat` because
 * it has no rendering concerns; the TUI just consumes it.
 */
interface ToolFormatLine {
  /**
   * Primary target — typically a path, command, pattern, or
   * task description. Renderer paints this in the model accent color
   * so the eye lands on it first.
   */
  target?: string;
  /**
   * Secondary annotations rendered after the target, joined with
   * ` · ` separators. Use for line ranges (`L10-25`), limits, flags,
   * or any short suffix that adds context without bloating the line.
   */
  meta?: readonly string[];
}
interface ToolDisplayMeta {
  /**
   * Title-case display verb (e.g. `Read`, `Shell`, `Edit`). When the
   * label depends on an input field (e.g. `skills_use`'s
   * `mode: 'activate' | 'deactivate'` → `Enable` / `Disable`), supply
   * a function instead and read the field defensively. The function
   * form is called whenever {@link displayNameFor} has the call's
   * input in scope; callers without input fall back to the function's
   * `input: undefined` branch, which should return a stable default.
   */
  displayName: string | ((input: Record<string, unknown> | undefined) => string);
  /**
   * Pull a {@link ToolFormatLine} out of the raw model input. Returns
   * `null` when the shape isn't what the tool expects (typed defensively
   * — we never want a malformed call to crash the transcript).
   */
  format: (input: Record<string, unknown>) => ToolFormatLine | null;
}
declare const TOOL_DISPLAY: Readonly<Record<string, ToolDisplayMeta>>;
/**
 * Resolve the display verb for a tool. Native tools use their curated
 * entry from {@link TOOL_DISPLAY}; everything else gets a sentence-case
 * version of the raw name (`my_host_tool` → `My host tool`) so an MCP /
 * host tool still reads cleanly in the transcript without shouting
 * Title Case at every word.
 *
 * MCP convention: every tool surfaced by `mcp/connectMcpServers` is
 * namespaced as `mcp_<server>_<tool>` (see `src/mcp/index.ts`). The
 * `mcp_` prefix is plumbing — strip it before casing so the label
 * reads as `Github create issue` instead of `Mcp github create issue`.
 * The server name leads, which doubles as a free visual grouping
 * affordance ("everything starting with `Github` came from the github
 * MCP server").
 */
declare function displayNameFor(name: string, input?: Record<string, unknown>): string;
/**
 * Run a tool's curated formatter and return the result, or `null` when
 * no formatter is registered / the input shape doesn't match. Renderer
 * decides what to do with `null` — typically: show `↳ <displayName>`
 * with no target / meta tail.
 */
declare function formatToolCall(name: string, input: Record<string, unknown>): ToolFormatLine | null;
//#endregion
export { anthropicDescriptor as $, SessionMeta as A, collectReferences as B, EditHunk as C, Owner as D, EditPayload as E, CompletionContext as F, ProviderKey as G, mergeReferences as H, CompletionItem as I, CustomField as J, detectAuth as K, CompletionProvider as L, StreamEvent as M, ToolCallDisplay as N, Picked as O, ActiveTrigger as P, ProviderDescriptor as Q, CompletionReference as R, EditDiffDisplay as S, EditOutcomeKind as T, AuthMethod as U, findActiveTrigger as V, ProviderAuth as W, ModelOption as X, ModelInfo as Y, OUTPUT_RESERVE_TOKENS as Z, isEditErrorResult as _, formatToolCall as a, getModelInfo as at, selectableTurnIds as b, splitPromptSegments as c, modelSupportsReasoning as ct, RequestApproval as d, openrouterDescriptor as dt, cerebrasDescriptor as et, SafeModeActions as f, piIdOf as ft, EDIT_TOOL_NAMES as g, useSafeModeQueue as h, displayNameFor as i, getContextWindow as it, Settings as j, Screen as k, ApprovalDecision as l, modelsForDescriptor as lt, useSafeModeActions as m, ToolDisplayMeta as n, effectiveContextWindow as nt, PromptSegment as o, localDescriptor as ot, SafeModeProvider as p, restoreModelOptions as pt, BUILTIN_PROVIDERS as q, ToolFormatLine as r, enabledModelOptions as rt, PromptSegmentRef as s, modelOptionsFor as st, TOOL_DISPLAY as t, credKeyOf as tt, ApprovalRequest as u, openaiDescriptor as ut, isTurnHighlighted as v, EditOutcome as w, turnSelectionOwnership as x, isVisible as y, applyInsert as z };
//# sourceMappingURL=tool-formatters-D_fX6FGl.d.ts.map