import { F as SessionData, O as SkillConfig, P as Session, sn as McpToolSchema, wn as ThinkingLevel, xn as SessionTurn } from "./agent-BHkvYIH9.js";
import { At as marginTopFor, D as InteractionRequest, Gt as KeyBindings, O as InteractionResponse, S as splitMarkdownCodeBlocks, an as Hint, cn as truncateTrailing, dn as AgentRegistry, dt as ChatOptions, et as EnabledAllowlistKey, g as SessionExportFormat, it as DiscoveryError, l as Theme, n as computeTurnAnchors, nn as CompletionState, on as clipHintsToWidth, pt as ResolvedConfig, rt as DiscoveredMcp, sn as hintsLength, t as TranscriptItem, u as ThemeColors, vn as accentColor, x as MarkdownSegment } from "./transcript-anchors-DA6XawEU.js";
import { A as SessionMeta, G as ProviderKey, L as CompletionProvider, M as StreamEvent, R as CompletionReference, W as ProviderAuth, X as ModelOption, Y as ModelInfo, _ as isEditErrorResult, a as formatToolCall, b as selectableTurnIds, c as splitPromptSegments, i as displayNameFor, j as Settings, l as ApprovalDecision, n as ToolDisplayMeta, o as PromptSegment, r as ToolFormatLine, s as PromptSegmentRef, t as TOOL_DISPLAY, u as ApprovalRequest, v as isTurnHighlighted, x as turnSelectionOwnership, y as isVisible } from "./tool-formatters-D_fX6FGl.js";
import { Buffer } from "node:buffer";
import { ReactNode } from "react";
import { RGBA, SyntaxStyle } from "@opentui/core";

//#region src/tui/agent-picker.d.ts
/**
 * Modal that lists the registered {@link AgentProfile}s and lets the user
 * pick one. Rows show: `● selected · label  description`.
 *
 * The accent column is intentionally compact (single-char marker) — the
 * profile's `accent` color is read from the active theme so Build and Plan
 * stand apart at a glance without taking horizontal space.
 *
 * Used by `App` (Ctrl+A binding) and exported for hosts that want to drive
 * agent switching from elsewhere in their own composition.
 */
declare function AgentPickerModal({
  agents,
  currentAgentId,
  onPick
}: {
  agents: AgentRegistry;
  currentAgentId: string;
  onPick: (id: string) => void;
}): import("react").ReactNode;
//#endregion
//#region src/tui/app.d.ts
/**
 * Top-level TUI component. Accepts a fully-resolved `ResolvedConfig` and wires
 * everything (settings, modal layer, screens, footer) underneath it.
 *
 * Hosts can either drive this via `runTui()` for the standard bootstrap or
 * mount `<App config={resolveConfig(...)} />` themselves inside a renderer
 * they already own.
 */
declare function App({
  config
}: {
  config: ResolvedConfig;
}): import("react").ReactNode;
//#endregion
//#region src/tui/completion-popup.d.ts
/**
 * Popover above the textarea showing the active provider's items. Provider-
 * agnostic — reads `label` + `description` off each `CompletionItem`. The
 * TUI hosts can pass any `CompletionState<TItem>`; the popup never needs
 * to know what `TItem` is.
 *
 * Geometry: 1 row of chrome + min(N, visibleRows) item rows + 1 hint row.
 * `flexShrink: 0` pins the height so a long transcript can't squeeze it.
 *
 * The popup is invisible (`null`-rendered) when `state.active` is null or
 * `state.items` is empty — the prompt block keeps its layout calm.
 *
 * Solid `backgroundColor: SURFACE.modal` is load-bearing: in `PromptBlock`
 * the popup floats over the transcript via `position: absolute`, and a
 * transparent fill would let the transcript text bleed through. Pairs
 * with the modal panel surface so floating UI shares one visual identity.
 *
 * Title overlays are painted on the top border (same trick as
 * `PromptHints`): provider label on the left in the chip-id's accent
 * color, match count on the right in dim text. Both ride absolute
 * positions so they take no flow space; the popup's height comes
 * entirely from the bordered body. The accent reuses
 * `resolveChipColor(...).bg` (foreground only, no background pill) so
 * the picker title still reads as "this is the X provider" without
 * carrying the chip pill's heavy visual weight.
 */
declare function CompletionPopup({
  state,
  /** Cap on visible rows. 6 keeps the popover compact even with long catalogs. */visibleRows
}: {
  state: CompletionState<unknown>;
  visibleRows?: number;
}): ReactNode;
//#endregion
//#region src/tui/components.d.ts
/**
 * `@opentui/react` extends `React.JSX.IntrinsicElements`, so `onSubmit` on `<input>`
 * gets intersected with the DOM `SubmitEvent` shape and demands an unhelpful overload.
 * The OpenTUI input runtime fires `(value: string) => void`; this helper isolates
 * the cast so each call site stays readable.
 */
declare function onInputSubmit(handler: (value: string) => void): never;
interface ContextUsage {
  used: number;
  max: number;
}
/**
 * Footer status bar. Renders as a single row when the terminal is wide
 * enough, otherwise stacks the context indicator beneath the hint row.
 * Width tiering is driven by plain-text length estimates — close enough
 * since the segments are ASCII-heavy.
 */
declare function Footer({
  hints,
  context,
  cost,
  status
}: {
  hints: Hint[];
  context: ContextUsage | null;
  /**
   * Cumulative USD cost for the current session, summed across runs.
   * Hidden when `null` or `0` — most providers don't populate cost today
   * (only OpenRouter does), and "$0.00" would read as a lie on the rest.
   */
  cost?: number | null;
  /**
   * Active run status, surfaced as a small glyph at the right edge of
   * the hints (left) section. Replaces the spinner that used to live in
   * the title overlay. `null` hides it entirely. Priority order matches
   * the previous title-overlay logic: asking > compacting > busy.
   */
  status?: 'busy' | 'compacting' | 'asking' | null;
}): ReactNode;
/**
 * Pure renderer for a list of {@link Hint}s as colored spans —
 * `<key1> <label1> · <key2> <label2> · …` with warn/dim/mute colors.
 *
 * Returns spans only (no enclosing `<text>`, no leading / trailing
 * whitespace) so the caller can wrap it in whichever container fits
 * their surface: the bottom-bar footer uses a single-row `<text>`, the
 * prompt-box overlay wraps it with leading + trailing spaces so the
 * outermost cells punch through the border like a native title would.
 *
 * Pattern matches `renderRefSpans` below — pure function over an opaque
 * `ThemeColors`, no internal hook calls so it composes inside any
 * `<text>` regardless of where the parent grabbed its palette.
 */
declare function renderHintSpans(hints: readonly Hint[], COLOR: ThemeColors): ReactNode;
/**
 * One run of styled text in a {@link TitleOverlay} meta segment array.
 * `color` defaults to `COLOR.dim` (the standard secondary tone for
 * meta info); supply an explicit color to accent a value — typical use
 * is a `warn` tint on a primary stat (turn count, session count) while
 * surrounding separators stay `mute`.
 */
interface MetaSegment {
  text: string;
  /** Foreground color. Defaults to `COLOR.dim`. */
  color?: string;
}
/**
 * Colored title for a full-screen bordered surface. The `title` slot
 * rides `titleColor` (defaults to a per-character gradient across the
 * theme's throbber pair, fallback brand→accent) on the LEFT of the top
 * border; the optional `meta` slot rides on the RIGHT.
 *
 * `meta` accepts either:
 *   - a plain `string` → rendered entirely in `COLOR.dim` (the simple
 *     "single dim label" case, e.g. `"5 sessions"`).
 *   - a {@link MetaSegment} array → rendered concatenated with each
 *     segment's own color (lets a stat like turn count stand out from
 *     the surrounding separators).
 *
 * Responsive behavior (driven by `useTerminalDimensions`):
 *   - Both fit → render both with a 2-cell visual gap between them.
 *   - Meta doesn't fit → drop meta; title takes the full budget.
 *   - Title doesn't fit → truncate with a trailing `…`.
 *   - Terminal is degenerately narrow → render nothing.
 *
 * The width math assumes the immediate parent fills the screen's
 * content area (the standard `flexGrow: 1` flex column). Hosts running
 * the bordered box inside a narrower container should pass `parentWidth`
 * to override the terminal-width assumption.
 *
 * @example
 * ```tsx
 * <box style={{ flexDirection: 'column', flexGrow: 1 }}>
 *   <box style={{ border: true, flexGrow: 1 }}>...</box>
 *   <TitleOverlay
 *     title="my session"
 *     meta={[
 *       { text: '#abcd' },
 *       { text: ' · ', color: COLOR.mute },
 *       { text: '5 turns', color: COLOR.warn },
 *     ]}
 *   />
 * </box>
 * ```
 */
declare function TitleOverlay({
  title,
  meta,
  titleColor,
  parentWidth,
  statusIcon,
  statusIconCells
}: {
  title: string; /** Optional right-aligned secondary segment. Dropped first when space runs out. */
  meta?: string | readonly MetaSegment[] | null; /** Defaults to `COLOR.brand`. */
  titleColor?: string;
  /**
   * Override for the parent container's width in columns. Defaults to
   * `terminalWidth - 2` to match the standard screen layout that has
   * 1-cell padding on each side.
   */
  parentWidth?: number;
  /**
   * Optional single-cell glyph rendered immediately AFTER the title
   * text. Used by the chat screen to surface a streaming / compacting
   * indicator next to the session name — keeps the affordance close to
   * what it qualifies (the conversation) without claiming a full row
   * above the prompt input.
   *
   * Sized as `statusIconCells` cells + one leading space when present,
   * so the title budget reserves the right number of extra columns.
   * Pass a {@link StatusSpinner} or any other 1-cell `<span>`; `null`
   * (the default) takes no space.
   *
   * Render order is `title <space> icon`. The leading title slot reads
   * naturally — eye lands on the name first, then catches the activity
   * indicator — and avoids the visual jitter that came with the icon
   * pulsing on the leftmost position.
   */
  statusIcon?: ReactNode;
  /**
   * Cell width of `statusIcon`. Defaults to `1` for the standard
   * single-cell spinners; pass `2` when the icon is a double-width
   * emoji glyph (e.g. `❓`) so the title-budget math keeps the meta
   * segment from being clipped a column early.
   */
  statusIconCells?: 1 | 2;
}): ReactNode;
declare function Spinner({
  label
}: {
  label?: string;
}): ReactNode;
/**
 * Glyph-only spinner painted in a caller-chosen color. Used as the
 * status icon prefixed to the chat screen's session title — a single
 * cell wide so it slots in front of the title text without disturbing
 * the existing `TITLE_OVERLAY_WRAP` budget math.
 */
declare function StatusSpinner({
  color
}: {
  color: string;
}): ReactNode;
declare function Transcript({
  events,
  settings,
  selectedTurnId,
  busy
}: {
  events: StreamEvent[];
  settings: Settings;
  /**
   * Turn id whose events should render with the selection accent bar. `null`
   * leaves every event unhighlighted (normal mode). Events without a turnId
   * (synthetic markers) are never highlighted.
   */
  selectedTurnId?: string | null;
  /**
   * True while a run is streaming. Drives the inline working throbber that
   * appears at the tail of the transcript while the assistant is preparing
   * a reply (thinking, or otherwise busy before content/tool calls land).
   * Matches Crush's `AssistantMessageItem.isSpinning()` semantics: the
   * throbber hides as soon as the active turn produces visible output.
   */
  busy?: boolean;
}): ReactNode;
//#endregion
//#region src/tui/effort-picker.d.ts
declare function EffortPickerModal({
  current,
  supportsAdaptive,
  onPick
}: {
  current: ThinkingLevel | undefined;
  supportsAdaptive: boolean;
  onPick: (effort: ThinkingLevel) => void;
}): import("react").ReactNode;
//#endregion
//#region src/tui/interaction-block.d.ts
/**
 * InteractionBlock — picker UI for `present_plan` and `ask_user` tool calls.
 *
 * Sits in the chat screen's prompt slot whenever the head of the
 * interactions queue is non-empty (live or resumed — the App layer
 * doesn't differentiate at this layer). Two surfaces driven off the
 * request's discriminator:
 *
 *   - `plan` → renders the title + markdown body + (optional) step list,
 *     then a 3-option decision picker. Reject and revise can flow into a
 *     comment textarea for free-text feedback before final submit.
 *
 *   - `question` → renders the question text, then a choice picker
 *     (when the model provided choices) or a free-text answer textarea
 *     (otherwise).
 *
 * Esc aborts the entire run via the parent keyboard handler; per-call
 * accept/deny only happens through the picker below — same convention
 * as `ApprovalBlock`.
 */
declare function InteractionBlock({
  request,
  onResolve
}: {
  request: InteractionRequest; /** Submit the user's response to the head of the queue. */
  onResolve: (response: InteractionResponse) => void;
}): ReactNode;
//#endregion
//#region src/tui/mcps-settings.d.ts
/**
 * MCP server picker. Hierarchical: each server is one row, and an
 * expanded server reveals one row per advertised tool. The full surface:
 *
 *     ▼ [✓] linear             stdio · npx -y mcp-remote …    (5/7) ✓ authed
 *           [✓] create_issue   Create a new Linear issue
 *           [✓] list_issues    Query issues by status / assignee
 *           [ ] update_issue   Update fields on an existing issue
 *     ▶ [✓] chrome-devtools    stdio · npx -y chrome-devtools-mcp@latest
 *
 * Keybindings:
 *
 *   ↑ / ↓ / k / j      navigate (over servers AND visible tool rows)
 *   → / tab            expand the focused server
 *   ← / shift+tab      collapse — on a tool row, also snaps the cursor
 *                      back to the parent server
 *   enter / space      toggle — server toggles `enabledMcps`; tool toggles
 *                      `disabledMcpTools[serverName]`
 *   l                  login (server rows only — needs-auth / error)
 *   o                  logout (server rows only — authed / error /
 *                      authorizing)
 *   r                  refresh discovery
 *   esc                close — also cancels an in-flight `authorizing`
 *                      row
 *
 * Status badge legend (server row only):
 *
 *   ✓ authed         tokens stored + bootstrap connected
 *   ! needs login    bootstrap needs OAuth, no tokens
 *   … authorizing    interactive login in flight
 *   ✗ <error>        login attempt failed
 *
 * The (N/M) chip on a server header counts ENABLED / TOTAL advertised
 * tools, drawn only when the tool catalog has loaded. Without a catalog
 * (server never bootstrapped this profile, cache cold) the chip is
 * suppressed and the empty-state row under the expanded server explains
 * how to populate.
 *
 * Errors (`DiscoveryError[]`) surface in a warn-colored preamble so a
 * broken `mcps.json` is loud rather than invisible.
 */
declare function McpsSettingsModal({
  catalog,
  errors,
  toolsByServer,
  onLogin,
  onLogout,
  onCancelLogin,
  onRefresh
}: {
  catalog: readonly DiscoveredMcp[];
  errors?: readonly DiscoveryError[];
  /**
   * Per-server full tool catalog from the on-disk cache (see
   * `chat/mcp-tools-cache.ts`). Missing key → "no catalog yet"
   * empty-state in the expanded view. The host owns loading
   * + change-subscription so the modal stays renderer-only.
   */
  toolsByServer: Record<string, readonly McpToolSchema[]>;
  onLogin: (name: string) => Promise<void> | void;
  onLogout: (name: string) => void;
  onCancelLogin: (name: string) => void;
  /**
   * Optional force-rescan hook bound to `r` on this standalone modal.
   * Hosts wire it to `discoverProjectMcps` (or the future async
   * registry-aware variant). Unlike `SettingsModal`, here we use
   * plain `r` because there's no search input to swallow the key —
   * matches the single-letter convention of `l` / `o`.
   */
  onRefresh?: () => Promise<void> | void;
}): import("react").ReactNode;
//#endregion
//#region src/tui/modal.d.ts
interface ModalApi {
  open: (node: ReactNode) => void;
  close: () => void;
  /** Signal "modal-like UI is on screen" without rendering an overlay. */
  lock: () => void;
  unlock: () => void;
  isOpen: boolean;
}
declare function ModalRoot({
  children
}: {
  children: ReactNode;
}): ReactNode;
declare function useModal(): ModalApi;
/**
 * Focus computed against the modal layer.
 *
 * Pass a component's preferred focus state and this returns `false` whenever a
 * modal is open — so focused inputs (textarea, selects) release their focus and
 * stop intercepting keys behind the overlay. Pair with `focusable={false}` on
 * "passive" focusables (scrollbox) so the renderer doesn't cycle focus into
 * them when the primary input blurs.
 */
declare function useModalAwareFocus(preferred?: boolean): boolean;
interface ModalProps {
  title?: string;
  /**
   * Secondary label rendered on the bottom border, right-aligned. Use it
   * for status / counter info that complements the top title (e.g.
   * "3 before · 6 after" on the turn-details modal). Kept short — long
   * strings collide with the bottom-right corner glyph.
   */
  bottomTitle?: string;
  /**
   * Right-aligned overlay rendered on the **top** border, next to the
   * title slot. Use it for live counters / status badges (e.g.
   * `"3 in progress · 2 completed"` on the todos modal,
   * `"src/foo.ts · child-2"` on the file-edit modal). Same scissor-rect
   * trick as `TitleOverlay` / `CompletionPopup` — painted as an
   * absolutely-positioned sibling so OpenTUI's scissor doesn't clip
   * the border row.
   */
  rightTitle?: ReactNode;
  /** Called when the user presses esc. Defaults to `useModal().close()` if available. */
  onClose?: () => void;
  /**
   * When true, Modal's built-in Esc-to-close handler is suppressed for
   * this render. The child stays free to register its own `useKeyboard`
   * and handle Esc however it wants (cancel a pending confirmation,
   * block dismissal mid-async, …). `@opentui/react`'s `useKeyboard`
   * registers independent listeners with no propagation, so a child's
   * `return` inside its own handler can't stop this Modal's default
   * handler from firing on the same keystroke — `disableEscape` is the
   * declarative escape hatch that closes that race.
   */
  disableEscape?: boolean;
  children: ReactNode;
  /** Preferred width in columns. Modal grows to this when the terminal allows. */
  maxWidth?: number;
  /** Floor on the width, so content stays minimally legible. */
  minWidth?: number;
  /**
   * Hard cap on the modal's height in rows. When set, the modal grows
   * to this size when the terminal allows and shrinks down to fit on
   * smaller screens. Without it, the modal grows to fit its children
   * (uncapped) — fine for picker-style modals, problematic for
   * unbounded content (long turn previews, etc.) where the modal could
   * exceed the viewport and bury its esc-hint footer.
   */
  maxHeight?: number;
  /** Columns of breathing room kept on each side of the modal. */
  horizontalMargin?: number;
  /** Rows of breathing room kept above + below the modal when `maxHeight` is set. */
  verticalMargin?: number;
}
/**
 * Responsive modal — picks a width (and optionally a height) based on the
 * live terminal size.
 *
 * - On a wide terminal, the modal grows to `maxWidth` so descriptions sit on
 *   one line and don't wrap.
 * - On a narrow terminal, the modal shrinks down to `minWidth`, keeping a
 *   small horizontal margin from the screen edges. Text inside wraps naturally.
 * - When `maxHeight` is set, the same tier logic applies on the vertical
 *   axis — anything beyond is the consumer's job (typically a `scrollbox`
 *   child for long content).
 *
 * Uses `useTerminalDimensions()` so it reflows on `SIGWINCH` without remount.
 */
declare function Modal({
  title,
  bottomTitle,
  rightTitle,
  onClose,
  disableEscape,
  children,
  maxWidth,
  minWidth,
  maxHeight,
  horizontalMargin,
  verticalMargin
}: ModalProps): ReactNode;
//#endregion
//#region src/tui/model-options-picker.d.ts
declare function ModelOptionsPickerModal({
  supportsReasoning,
  supportsAdaptive,
  currentEffort,
  options,
  enabled,
  onPickEffort,
  onToggleOption
}: {
  /** Whether to render the reasoning-effort section. */supportsReasoning: boolean; /** Whether to include the Anthropic-only `adaptive` level. */
  supportsAdaptive: boolean; /** Currently-selected effort (for the `●` marker). */
  currentEffort: ThinkingLevel | undefined; /** Feature options the model declares. */
  options: readonly ModelOption[]; /** Enabled option ids. */
  enabled: Record<string, boolean> | undefined;
  onPickEffort: (effort: ThinkingLevel) => void;
  onToggleOption: (optionId: string) => void;
}): import("react").ReactNode;
//#endregion
//#region src/tui/model-picker.d.ts
interface PickedModel {
  providerKey: ProviderKey;
  modelId: string;
}
declare function ModelPickerModal({
  providers,
  modelsFor,
  current,
  onPick
}: {
  /** Authed providers — the catalog is unioned across these. */providers: readonly ProviderAuth[]; /** Resolver from `ResolvedConfig.modelsFor`. */
  modelsFor: (key: ProviderKey) => readonly ModelInfo[]; /** Currently active selection. Promoted to top of its provider section + pre-highlighted. */
  current: PickedModel; /** Called on commit with the chosen `{ providerKey, modelId }`. */
  onPick: (picked: PickedModel) => void;
}): import("react").ReactNode;
//#endregion
//#region src/tui/screens.d.ts
/**
 * Prompt-level attachment captured by the textarea's paste pipeline.
 * Lives only in the TUI layer — converted to {@link PromptPart}s at
 * `agent.run()` time. Internal buffer-based shape is more convenient
 * than the wire format (which is base64 for binary and raw text for
 * text/plain).
 */
interface Attachment {
  name: string;
  content: Buffer;
  mediaType: string;
}
declare function AuthScreen({
  onPick,
  initialConfigureKey
}: {
  onPick: (p: ProviderAuth) => void;
  /**
   * Open straight into a SPECIFIC provider's wizard (skip the auth list and
   * the wizard's provider picker). Set when the user picked a provider row in
   * Settings → Authentication to (re)configure it.
   */
  initialConfigureKey?: string;
}): ReactNode;
declare function SessionsScreen({
  sessions,
  currentId,
  focusedSessionId,
  onPick,
  onCreate,
  onFocusChange,
  showAllProjects,
  currentProjectRoot
}: {
  sessions: SessionMeta[];
  /**
   * Identity of the row the parent considers focused. Drives the row
   * cursor so the highlight follows the SESSION (not its slot) when the
   * list reorders. `null` lands the cursor on the first session row, or
   * on "+ new" if there are no sessions yet.
   */
  focusedSessionId: string | null;
  currentId: string | null;
  onPick: (id: string) => void;
  onCreate: () => void;
  /**
   * Notified as the user navigates between rows. Receives the session id
   * of the focused row, or `null` when "+ new" is focused (no session to
   * act on). Parents use this to wire `ctrl+x` against the live focus.
   */
  onFocusChange?: (id: string | null) => void;
  /**
   * When `true`, render each session's project label under the title.
   * Off by default — when the list is already filtered to one project
   * the label would just repeat that project on every row.
   */
  showAllProjects?: boolean;
  /**
   * Current project root, used to label "this project" on rows that
   * belong to it when `showAllProjects` is on. Untagged (legacy)
   * sessions render as "untagged".
   */
  currentProjectRoot?: string;
}): ReactNode;
declare function ChatScreen({
  cwd,
  events,
  busy,
  compacting,
  queuedMessages,
  queueSelectionIndex,
  queueShortcuts,
  onEnterQueueFromEmptyPrompt,
  settings,
  onSubmit,
  session,
  pending,
  onApproval,
  pendingInteraction,
  onInteraction,
  completionProviders,
  onPopupOpenChange,
  selectedTurnId,
  promptTriggerHints,
  liveSession
}: {
  cwd: string;
  events: StreamEvent[];
  busy: boolean;
  /**
   * `true` while a background auto-compaction is in flight. Drives the
   * same title-overlay spinner as {@link busy} but in a different color
   * — the conversation is "active" without anything streaming visibly.
   */
  compacting?: boolean;
  /**
   * User prompts submitted while a run was already in flight. Rendered
   * stacked in a small box above the prompt input until the active
   * `agent.run()` finishes; each entry is then popped (FIFO) and pushed
   * into the transcript as a user message. Empty by default.
   */
  queuedMessages?: readonly QueuedPreview[];
  /**
   * Highlighted index inside {@link queuedMessages}, or `null` when the
   * prompt textarea has focus. When non-null, the textarea blurs (same
   * pattern as `selectMode`) so up / down / push / drop keys reach the
   * App's keyboard handler instead of being eaten by the input.
   */
  queueSelectionIndex?: number | null;
  /**
   * Resolved keybinding specs (`"ctrl+return"`, `"backspace"` — and
   * whatever the user has configured) threaded into the queue UI so
   * the title hint + per-row action hints track `keybindings.json`
   * overrides. Required when the queue is wired — the binding strings
   * are only ever read here.
   */
  queueShortcuts?: QueueShortcuts;
  /**
   * Called from `PromptBlock` when the user presses `↑` on an empty
   * prompt. Returning `true` parks focus on the last queued message
   * (the one closest to the prompt) and tells the caller to suppress
   * the default history-cycle behavior; `false` means "no queue, do
   * your normal thing" and history-cycling proceeds.
   */
  onEnterQueueFromEmptyPrompt?: () => boolean;
  settings: Settings;
  /**
   * Submit handler — receives the raw prompt text, the parsed references
   * (skills, files, …) so the App can act on them (e.g. activate the
   * referenced skill before `agent.run()`), and any attachments captured
   * by the textarea's paste pipeline.
   */
  onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => void;
  session: SessionMeta | null; /** Head of the safe-mode approval queue, or `null` when nothing is pending. */
  pending: ApprovalRequest | null; /** Resolve the active prompt with the user's pick. */
  onApproval: (decision: ApprovalDecision) => void;
  /**
   * Head of the interactions queue (plan approval / Q&A). When set, the
   * prompt slot renders {@link InteractionBlock} instead of the textarea
   * so the user resolves the request before continuing typing. Works the
   * same for live tool calls and resumed-session pending interactions —
   * the App layer only swaps the resolver behind {@link onInteraction}.
   */
  pendingInteraction: InteractionRequest | null; /** Submit the user's response to the head of the interactions queue. */
  onInteraction: (response: InteractionResponse) => void;
  /**
   * Optional autocomplete providers. When passed, `PromptBlock` shows a
   * popup above the textarea and intercepts navigation keys while it's
   * visible. Providers are typed by their item payload (e.g.
   * `CompletionProvider<SkillConfig>`).
   */
  completionProviders?: readonly CompletionProvider<unknown>[];
  /**
   * Notified whenever the popup opens / closes. The App uses this to gate
   * the esc-abort handler so esc dismisses the popup instead of aborting
   * the run when both could fire.
   */
  onPopupOpenChange?: (open: boolean) => void;
  /**
   * Active turn id when the user is in select-turn mode. `null` = normal
   * mode. Drives the transcript highlight and unfocuses the textarea so
   * up/down/return reach the parent's keyboard handler instead of being
   * consumed by the input.
   */
  selectedTurnId?: string | null;
  /**
   * Optional trigger affordances (e.g. `@ files`, `/ skills`) appended
   * to the prompt-box overlay's hint row when there's room. Caller
   * filters by provider availability (don't include `/ skills` if the
   * skills catalog is empty, etc.) — the prompt overlay drops the
   * triggers entirely on narrow terminals.
   */
  promptTriggerHints?: readonly Hint[];
  /**
   * Live `Session` reference for surfaces that read metadata directly
   * (currently {@link TodoIndicator}'s `useActiveTodos` slice). The
   * reference itself is stable across activations of the same session
   * — re-renders are driven by the `events` prop, which already
   * cascades on every tool-result. `null` while the screen is shown
   * without an attached session (the indicator hides itself in that
   * case, same as it does for "no in_progress item").
   *
   * Kept distinct from {@link session} (the lightweight
   * {@link SessionMeta} snapshot) so consumers that don't need live
   * data don't accidentally re-read mutable fields off of it.
   */
  liveSession?: Session | null;
}): ReactNode;
/**
 * One entry in the queued-messages preview list shown above the prompt
 * input. Only the displayable text + the user-provided ref spans are
 * surfaced — the `references` carry-through happens entirely in the
 * App layer (see `messageQueueRef`); this view doesn't need to know
 * about them.
 */
interface QueuedPreview {
  /** Raw prompt text the user typed (no `❯` prefix). */
  text: string;
  /**
   * Optional chip spans from the original prompt (`@files`, `/skills`)
   * — passed through so the queued entry highlights skill / file names
   * the same way the echoed user-prompt event does once it lands in
   * the transcript.
   */
  refs?: readonly {
    start: number;
    end: number;
    providerId: string;
  }[];
}
/**
 * Resolved keybinding specs the queue UI surfaces to the user. Each
 * field is the literal string from `keybindings.json` ("ctrl+return",
 * "backspace" — or whatever the user has configured) formatted for
 * display via {@link formatBindingForDisplay} at render time so any
 * customization (e.g. `"cmd+up"`, `"delete"`) shows the actual key.
 */
interface QueueShortcuts {
  enter: string;
  push: string;
  drop: string;
}
//#endregion
//#region src/tui/session-details-modal.d.ts
/** Result handed back by the host's export handler — drives the success footer. */
interface SessionExportResult {
  /** Absolute path of the written file. */
  filepath: string;
  /** Format that was written — used to label the success banner. */
  format: SessionExportFormat;
}
interface SessionDetailsModalActions {
  /** Delete the entire session from the store. Parent handles teardown / navigation. */
  onDelete: (sessionId: string) => void | Promise<void>;
  /**
   * Generate a title for the session via the active provider/model.
   * Resolves to the new title (already persisted by the parent); reject
   * with an Error to surface a failure message in the modal footer.
   *
   * Omit when title generation isn't wired (no provider picked yet, or
   * the host opted out) — the modal hides the `g` shortcut.
   *
   * `signal` is provided so a parent unmount (modal closed, session
   * switched) can abort the in-flight provider stream. Implementations
   * should forward it to whatever transport they use; an aborted
   * generation should reject (e.g. with the signal's reason).
   */
  onGenerateTitle?: (sessionId: string, signal: AbortSignal) => Promise<string>;
  /**
   * Export the session to disk in the requested format. The host
   * decides where the file lands (typically `.{prefix}/sessions/`) and
   * returns the absolute path so the modal can show a confirmation
   * footer. Reject with an Error to surface a failure message.
   *
   * Omit when export isn't wired — the modal hides the `e` / `j`
   * shortcuts.
   */
  onExport?: (sessionId: string, format: SessionExportFormat) => Promise<SessionExportResult>;
  /**
   * Compact older turns via an LLM-generated summary. The host calls
   * `compactConversation` and appends the synthetic `compact-summary`
   * marker turn so older turns stop reaching the provider. The model
   * sees everything from the marker forward verbatim; the user can
   * still scroll back to see the original turns.
   *
   * Resolves to a result envelope used by the modal to flash the
   * "compacted N turns" success banner. Reject with an Error to
   * surface a failure message.
   *
   * `signal` is provided so a parent unmount (modal closed, session
   * switched) can abort the in-flight summary call.
   *
   * Omit when compaction isn't wired (no provider picked yet, or the
   * host opted out) — the modal hides the `k` shortcut.
   */
  onCompact?: (sessionId: string, signal: AbortSignal) => Promise<SessionCompactResult>;
}
/** What `onCompact` returns. Drives the modal's success banner. */
interface SessionCompactResult {
  /** Number of turns absorbed into the summary. */
  replacedCount: number;
  /**
   * Number of turns the PTL-retry path had to drop without summarizing.
   * Zero on first-try success. When non-zero, the modal warns the user
   * that some context was lost — those turns stay in the conversation
   * history but the new summary doesn't describe them.
   */
  droppedCount: number;
  /**
   * Number of files re-injected as synthetic `read_file` results after
   * the compaction marker. Zero when the session had no active reads or
   * the host opted out of restoration.
   */
  restoredFiles: number;
  /**
   * Number of skills re-injected as synthetic `skills_use` results
   * after the compaction marker.
   */
  restoredSkills: number;
  /** Model used for the summary call. */
  model: string;
  /** Total input tokens billed (including cache reads + creations). */
  inputTokens: number;
  /** Output tokens of the summary itself. */
  outputTokens: number;
  /**
   * Estimated wire-level context size the NEXT turn will start from.
   * Sum of the summary's tokens plus the restoration content's
   * byte-based estimate — captures what the model will actually see on
   * its next call, modulo the system prompt and any preserved tail
   * turns whose precise token count we don't know without re-asking
   * the provider.
   *
   * Drives both the post-compact success banner ("→ N effective") and
   * the chat footer's `ctx` indicator — `onCompactSession` writes this
   * value into `lastInputTokens` so the user sees the drop immediately
   * instead of waiting for the next real turn's `usage` update.
   */
  effectiveTokens: number;
}
declare function SessionDetailsModal({
  session,
  title,
  isCurrent,
  actions,
  keybindings
}: {
  session: SessionData;
  /**
   * Display title for the session. Falls back to the first user turn's
   * preview when omitted — the parent usually has a cached `SessionMeta`
   * title and threads it through so the modal header matches what the
   * sessions list / chat title bar are showing.
   */
  title?: string; /** True when this is the actively-mounted session — gates the "active" tag in the header. */
  isCurrent: boolean;
  actions: SessionDetailsModalActions; /** Effective keybindings — the modal-internal letter actions resolve through this. */
  keybindings: KeyBindings;
}): import("react").ReactNode;
//#endregion
//#region src/tui/settings-modal.d.ts
interface SettingsActions {
  /**
   * Generic "open the auth flow" — only used as a FALLBACK action row on the
   * General tab for embedders that don't wire the dedicated Authentication
   * tab (`authentication` prop unset). The standard TUI shows the tab instead.
   */
  onReauth?: () => void;
  /**
   * Open the setup wizard for a specific provider — `↵` on any row of the
   * Authentication tab. The tab is config-only now (switching the *active*
   * provider is handled by the cross-provider model picker), so every row,
   * configured or not, lands in that provider's credential editor. Closes
   * the modal as a side effect.
   */
  onConfigureProvider?: (provider: ProviderAuth) => void;
  /** Open `~/.zidane/keybindings.json` in `$EDITOR`. */
  onOpenKeybindings?: () => void;
  /** Start an MCP OAuth flow. Resolves once tokens land or the flow aborts. */
  onLoginMcp?: (name: string) => Promise<void> | void;
  /** Wipe stored MCP tokens. */
  onLogoutMcp?: (name: string) => void;
  /** Abort an in-flight MCP OAuth flow. */
  onCancelLoginMcp?: (name: string) => void;
  /**
   * Force a fresh skills rescan, bypassing the background SWR throttle.
   * Fired by `ctrl+R` on the Skills tab. When wired the host should
   * call `discoverProjectSkills` directly and replace the catalog —
   * an in-flight refresh promise resolves into the modal automatically
   * via the catalog prop.
   */
  onRefreshSkills?: () => Promise<void> | void;
  /**
   * Re-parse the project's `mcps.json` files. Fired by `ctrl+R` on
   * the MCPs tab. Sync-shaped under the hood but typed as async so
   * future discovery paths (registry lookups, etc.) can stay
   * non-blocking without changing the contract.
   */
  onRefreshMcps?: () => Promise<void> | void;
  /**
   * Refresh the per-server tool catalog by opening transient MCP
   * connections, capturing `tools/list` payloads via the agent's
   * `mcp:tools:list` hook, and writing the result to the on-disk
   * cache. Fired automatically when the MCPs tab activates so the
   * panel always shows the freshest tool list. Hosts that don't
   * want the auto-refresh (no live agent yet, no MCP credentials
   * wired) can omit the action — the panel then degrades to
   * whatever the on-disk cache last captured.
   */
  onRefreshMcpTools?: () => Promise<void> | void;
}
/**
 * Snapshot of the host's auth state for the Authentication tab.
 * Drives the "Current" header (active provider + model, informational) and a
 * selectable list of every detected provider. `↵` on any row delegates to
 * `actions.onConfigureProvider`, which opens that provider's setup wizard
 * (the multi-step wizard is too involved to embed in the modal). Switching the
 * active model is handled separately by the cross-provider model picker.
 */
interface AuthenticationView {
  /** Active provider's display label (e.g. "Anthropic"). */
  currentProviderLabel?: string;
  /** Active provider's machine key (e.g. "anthropic"). */
  currentProviderKey?: string;
  /** Active model id (e.g. "claude-sonnet-4-5"). */
  currentModel?: string;
  /** Detected providers from `detectAuth` — used to render the list with credential badges. */
  providers?: readonly ProviderAuth[];
}
declare function SettingsModal({
  skillsCatalog: skillsCatalogProp,
  mcpsCatalog: mcpsCatalogProp,
  mcpsErrors: mcpsErrorsProp,
  keybindings,
  keybindingsPath,
  authentication,
  actions
}?: {
  /**
   * Embedder fallback — discovered skills when no `<DiscoveryProvider>`
   * is mounted. In the in-app flow this is ignored; the modal reads
   * live state from `useDiscovery()` so a `ctrl+R` rescan surfaces
   * immediately even with the modal already open.
   */
  skillsCatalog?: readonly SkillConfig[]; /** Embedder fallback — discovered MCP servers; superseded by context when present. */
  mcpsCatalog?: readonly DiscoveredMcp[]; /** Embedder fallback — `mcps.json` parse errors; superseded by context when present. */
  mcpsErrors?: readonly DiscoveryError[];
  /**
   * Merged keybinding map. When provided, a "Keybindings" tab appears
   * with the full action catalog; `↵` opens `keybindings.json` via
   * `actions.onOpenKeybindings`. The UI-tab action row for the same
   * affordance is dropped when this is set (no duplicate entry points).
   */
  keybindings?: KeyBindings; /** Absolute path to `keybindings.json`. Surfaced on the pinned edit-file button. */
  keybindingsPath?: string;
  /**
   * Auth state snapshot. When provided, an "Authentication" tab
   * appears with the current provider/model header + a navigable list of
   * detected providers. `↵` on any row calls `actions.onConfigureProvider`
   * to open that provider's setup wizard. The General-tab fallback auth
   * action row is dropped when this is set.
   */
  authentication?: AuthenticationView;
  actions?: SettingsActions;
}): ReactNode;
//#endregion
//#region src/tui/skills-settings.d.ts
/**
 * List + toggle modal for discovered skills. State machine + keyboard +
 * row geometry live in `<ToggleListModal>`; this file just supplies the
 * skill-specific column (description) and the empty-state hint.
 *
 * Pass `onRefresh` to expose `ctrl+R` (force re-scan) on the list. The
 * standalone modal is for embedders — the in-app flow goes through
 * `<SettingsModal>` which exposes the same affordance via its own
 * `SettingsActions.onRefreshSkills` plumbing.
 */
declare function SkillsSettingsModal({
  catalog,
  onRefresh
}: {
  catalog: readonly SkillConfig[];
  onRefresh?: () => Promise<void> | void;
}): import("react").ReactNode;
//#endregion
//#region src/tui/theme.d.ts
interface StyleEntry {
  fg?: RGBA;
  bg?: RGBA;
  bold?: boolean;
  italic?: boolean;
  underline?: boolean;
  dim?: boolean;
}
/**
 * Convert the renderer-agnostic `Theme.syntax` map (hex strings + plain
 * booleans) into an OpenTUI `SyntaxStyle`. Used both for the markdown
 * structural captures (`markup.heading`, `markup.bold`, …) and the
 * embedded Tree-sitter language tokens (`keyword`, `string`, `function`,
 * …) — OpenTUI's `<markdown>` re-uses the same `SyntaxStyle` for the
 * fenced-code renderable, so one table drives both surfaces.
 *
 * `overrides`, when set, replaces individual entries by token name —
 * each override is merged INTO the resolved entry (shallow). Used by
 * the "on-selection" variant to swap `markup.raw.bg` for the row's
 * selection surface so inline code chips blend into the highlight
 * rather than reading as "punched out" rectangles. Top-level keys
 * absent from the base theme become brand-new entries.
 */
declare function buildMdStyle(theme: Theme, overrides?: Record<string, Partial<StyleEntry>>,
/**
 * When set, every token's `fg` is forced to this color and bold/underline
 * are dropped. Used for the "dimmed" variant (agent reasoning) so the whole
 * markdown block reads as muted secondary text — without it the syntax
 * colors (headings, bold, code) paint at full brightness and the block
 * doesn't look dim at all. Italic is preserved (it carries no color).
 */

flattenFg?: RGBA): SyntaxStyle;
/**
 * Active markdown / syntax-highlighting style. Returns a single shared
 * `SyntaxStyle` instance for the active theme — built once at provider
 * mount, re-built on theme switch. A `Settings.theme` flip re-paints every
 * `<markdown>` that reads this hook.
 *
 * Pass `{ selected: true }` to get the on-selection variant where inline
 * code chips' background matches the row's selection surface (so the
 * chips blend into the highlight rather than reading as punched-out
 * rectangles).
 *
 * Throws if used outside `<MdStyleProvider>` so a missing wiring shows up
 * loudly in development rather than silently rendering plain text.
 */
declare function useMdStyle(opts?: {
  selected?: boolean;
  dimmed?: boolean;
}): SyntaxStyle;
//#endregion
//#region src/tui/toggle-list-modal.d.ts
/**
 * Generic list-with-checkboxes modal. Powers both the Skills and MCP
 * server pickers — same state machine, same keyboard map, same row
 * geometry. Per-feature variance flows in through props:
 *
 *   - `keyOf` — extract the persisted identity (skill name, server name).
 *   - `settingKey` — `'enabledSkills'` | `'enabledMcps'`.
 *   - `renderDetail` — appended to each row in mute color (descriptions,
 *     transports, …). Optional.
 *   - `emptyState` — replacement content when `catalog` is empty.
 *
 * Renderer-agnostic state machine lives in `useEnabledToggleSet`
 * (chat layer) — a GUI shell can build its own toggle list against the
 * same hook without pulling OpenTUI.
 */
declare function ToggleListModal<T>({
  catalog,
  keyOf,
  settingKey,
  title,
  renderDetail,
  emptyState,
  preamble,
  onRefresh
}: {
  catalog: readonly T[];
  keyOf: (entry: T) => string;
  settingKey: EnabledAllowlistKey; /** Modal title stem — rendered as `${title} · N / M enabled` in non-empty mode. */
  title: string; /** Mute-colored trailing copy for each row (description, transport, …). */
  renderDetail?: (entry: T) => ReactNode; /** What to render when `catalog` is empty (hint about where to drop config). */
  emptyState: ReactNode;
  /**
   * Optional content rendered ABOVE the list (or the empty state). Used to
   * surface non-fatal warnings — e.g. discovered files that failed to parse,
   * so the user can fix them without quitting the TUI. Distinct from
   * `emptyState`, which only renders when there's nothing to show; this
   * renders even when the catalog has entries.
   */
  preamble?: ReactNode;
  /**
   * Optional force-rescan hook bound to `ctrl+R`. Hosts wire it to a
   * discovery rerun (`discoverProjectSkills`, `discoverProjectMcps`,
   * …); the modal just exposes the keybind and footer hint.
   */
  onRefresh?: () => Promise<void> | void;
}): ReactNode;
//#endregion
//#region src/tui/turn-details-modal.d.ts
interface TurnDetailsModalActions {
  /** Fork the session at this turn — new session keeps history up to here. */
  onFork: (turnId: string) => void;
  /** Delete this turn (with cleanup of orphan tool blocks in neighbors). */
  onDelete: (turnId: string) => void;
  /** Edit the text content of this turn. */
  onEdit: (turnId: string, newText: string) => void;
}
declare function TurnDetailsModal({
  turn,
  index,
  total,
  actions,
  keybindings
}: {
  turn: SessionTurn; /** 1-based position of this turn in the session, for the header. */
  index: number; /** Total turn count, for the `n / N` header. */
  total: number;
  actions: TurnDetailsModalActions; /** Effective keybindings — the modal-internal letter actions resolve through this. */
  keybindings: KeyBindings;
}): import("react").ReactNode;
//#endregion
//#region src/tui/index.d.ts
/**
 * Boot a full chat TUI with sensible defaults. **Does not return** under
 * normal use — it terminates the host process via `process.exit(0)` once
 * the user dismisses the renderer (Ctrl+C / Esc on the auth screen / a
 * non-zero exit code is mapped via the `catch` path below).
 *
 * **One-shot:** this function may only be invoked once per process. The
 * underlying OpenTUI renderer wires up global terminal state on init.
 *
 * **Why it exits the process:** after the renderer's `destroy()` restores
 * the terminal, React's reconciler and OpenTUI's internal listeners can
 * keep the Node/Bun event loop open indefinitely — the script appears to
 * hang in `bun run`, and under `bun --watch run` the watcher waits forever
 * for the child to exit. Forcing a clean exit here is the contract that
 * makes `runTui` a true one-shot launcher.
 *
 * Hosts that need post-renderer cleanup should mount `<App config={...} />`
 * against their own `createCliRenderer()` instead of calling `runTui()`.
 *
 * Env-var overrides (handy when launching from CI or restricted shells):
 *   - `ZIDANE_STORAGE_DIR` — sets `storageDir`
 *   - `ZIDANE_PREFIX` — sets `prefix`
 *   - `ZIDANE_DEBUG` — enables `gatherStats` on the renderer and dumps the
 *     final fps / frametime distribution to stderr on exit. Useful when
 *     tuning the renderer fps setting or comparing terminal emulators.
 *
 * Renderer fps lives in Settings (`targetFps`, cycle `30 / 60 / 120`);
 * the on-disk value seeds the renderer at boot and `AppShell` mirrors
 * subsequent flips onto the live renderer.
 *
 * Hosts building on top of `zidane/tui` typically want their own env vars
 * (e.g. `MYAPP_STORAGE_DIR`); read them in your launch script and forward
 * to `runTui({ storageDir, prefix })`.
 *
 * ```ts
 * import { BUILTIN_AGENTS, BUILTIN_PROVIDERS } from 'zidane/chat'
 * import { runTui } from 'zidane/tui'
 * import { createRemoteStore } from 'zidane/session'  // for the `store` option
 *
 * await runTui()                                       // ~/.zidane/sessions.db + state.json
 * await runTui({ prefix: '.myapp' })                   // ~/.myapp/...
 * await runTui({ storageDir: '/data', prefix: 'myapp' })
 * await runTui({ providers: { ...BUILTIN_PROVIDERS, mine: myDescriptor } })
 * await runTui({ store: createRemoteStore({ url: '…' }) })
 * await runTui({ agents: { ...BUILTIN_AGENTS, debug: myDebugProfile } })
 * ```
 */
declare function runTui(options?: Partial<ChatOptions>): Promise<never>;
//#endregion
export { AgentPickerModal, App, AuthScreen, ChatScreen, CompletionPopup, type ContextUsage, EffortPickerModal, Footer, type Hint, InteractionBlock, type MarkdownSegment, McpsSettingsModal, type MetaSegment, Modal, type ModalProps, ModalRoot, ModelOptionsPickerModal, ModelPickerModal, type PickedModel, type PromptSegment, type PromptSegmentRef, type QueueShortcuts, type QueuedPreview, type SessionCompactResult, SessionDetailsModal, type SessionDetailsModalActions, type SessionExportResult, SessionsScreen, type SettingsActions, SettingsModal, SkillsSettingsModal, Spinner, StatusSpinner, TOOL_DISPLAY, TitleOverlay, ToggleListModal, type ToolDisplayMeta, type ToolFormatLine, Transcript, type TranscriptItem, TurnDetailsModal, type TurnDetailsModalActions, accentColor, buildMdStyle, clipHintsToWidth, computeTurnAnchors, displayNameFor, formatToolCall, hintsLength, isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, onInputSubmit, renderHintSpans, runTui, selectableTurnIds, splitMarkdownCodeBlocks, splitPromptSegments, truncateTrailing, turnSelectionOwnership, useMdStyle, useModal, useModalAwareFocus };
//# sourceMappingURL=tui.d.ts.map