zidane 5.6.3 → 5.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tui.js","names":["EmptyState","VISIBLE_ROWS","debugLog","CountsBadge","anchorIdFor","ActionRow","canLogin","canLogout","ActionRow","displayPath","MAX_MODAL_HEIGHT"],"sources":["../src/tui/modal.tsx","../src/tui/agent-picker.tsx","../src/tui/cancel-tool-modal.tsx","../src/tui/clipboard.ts","../src/tui/crush-throbber.tsx","../src/tui/theme.ts","../src/tui/components.tsx","../src/tui/cwd-picker.tsx","../src/tui/discovery-shell.tsx","../src/tui/effort-picker.tsx","../src/tui/keybindings-modal.tsx","../src/tui/model-picker.tsx","../src/tui/completion-popup.tsx","../src/tui/file-edit-approval-modal.tsx","../src/tui/interaction-block.tsx","../src/tui/oauth-url-block.tsx","../src/tui/oauth-auth-block.tsx","../src/tui/todo-indicator.tsx","../src/tui/screens.tsx","../src/tui/session-details-modal.tsx","../src/tui/settings-modal.tsx","../src/tui/todos-modal.tsx","../src/tui/turn-details-modal.tsx","../src/tui/app.tsx","../src/tui/tree-sitter.ts","../src/tui/mcps-settings.tsx","../src/tui/toggle-list-modal.tsx","../src/tui/skills-settings.tsx","../src/tui/index.tsx"],"sourcesContent":["/** @jsxImportSource @opentui/react */\nimport type { ReactNode } from 'react'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { createContext, useContext, useMemo, useState } from 'react'\nimport { useColors, useSurfaces } from '../chat/theme-context'\n\n// ---------------------------------------------------------------------------\n// Modal layer\n//\n// A single, app-wide overlay slot. `ModalRoot` renders the active node on top\n// of its children via OpenTUI's `position: 'absolute'` so the host UI stays\n// mounted underneath (state preserved, no remount on close).\n//\n// Components anywhere in the tree call `useModal().open(...)` to push a node\n// and `.close()` to dismiss. Escape is captured by `Modal` itself when it's\n// open, so the App's top-level keyboard handler doesn't fight it.\n// ---------------------------------------------------------------------------\n\ninterface ModalApi {\n open: (node: ReactNode) => void\n close: () => void\n /** Signal \"modal-like UI is on screen\" without rendering an overlay. */\n lock: () => void\n unlock: () => void\n isOpen: boolean\n}\n\nconst ModalContext = createContext<ModalApi | null>(null)\n\nexport function ModalRoot({ children }: { children: ReactNode }) {\n const [active, setActive] = useState<ReactNode | null>(null)\n const [lockCount, setLockCount] = useState(0)\n\n const api = useMemo<ModalApi>(() => ({\n open: node => setActive(node),\n close: () => setActive(null),\n lock: () => setLockCount(c => c + 1),\n unlock: () => setLockCount(c => Math.max(0, c - 1)),\n get isOpen() { return active !== null || lockCount > 0 },\n }), [active, lockCount])\n\n return (\n <ModalContext.Provider value={api}>\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n {children}\n </box>\n {active && (\n // Transparent backdrop — host UI remains visible behind. The modal is\n // opaque (see <Modal/>) and sits centered; the dimming the eye perceives\n // comes from the modal's solid panel against the surrounding content,\n // not from a backdrop fill (OpenTUI cells have no alpha to blend with).\n <box\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 100,\n }}\n >\n {active}\n </box>\n )}\n </ModalContext.Provider>\n )\n}\n\nexport function useModal(): ModalApi {\n const ctx = useContext(ModalContext)\n if (!ctx)\n throw new Error('useModal must be used inside <ModalRoot>')\n return ctx\n}\n\n/**\n * Focus computed against the modal layer.\n *\n * Pass a component's preferred focus state and this returns `false` whenever a\n * modal is open — so focused inputs (textarea, selects) release their focus and\n * stop intercepting keys behind the overlay. Pair with `focusable={false}` on\n * \"passive\" focusables (scrollbox) so the renderer doesn't cycle focus into\n * them when the primary input blurs.\n */\nexport function useModalAwareFocus(preferred: boolean = true): boolean {\n const { isOpen } = useModal()\n return preferred && !isOpen\n}\n\n// ---------------------------------------------------------------------------\n// Modal — bordered content panel with built-in esc-to-close keyboard handling.\n//\n// Consumers pass their own content (forms, selects, lists). The modal owns:\n// - border + title + minimal padding\n// - sizing constraints (width caps at a comfortable column width)\n// - escape key → onClose\n// ---------------------------------------------------------------------------\n\nexport interface ModalProps {\n title?: string\n /**\n * Secondary label rendered on the bottom border, right-aligned. Use it\n * for status / counter info that complements the top title (e.g.\n * \"3 before · 6 after\" on the turn-details modal). Kept short — long\n * strings collide with the bottom-right corner glyph.\n */\n bottomTitle?: string\n /**\n * Right-aligned overlay rendered on the **top** border, next to the\n * title slot. Use it for live counters / status badges (e.g.\n * `\"3 in progress · 2 completed\"` on the todos modal,\n * `\"src/foo.ts · child-2\"` on the file-edit modal). Same scissor-rect\n * trick as `TitleOverlay` / `CompletionPopup` — painted as an\n * absolutely-positioned sibling so OpenTUI's scissor doesn't clip\n * the border row.\n */\n rightTitle?: ReactNode\n /** Called when the user presses esc. Defaults to `useModal().close()` if available. */\n onClose?: () => void\n /**\n * When true, Modal's built-in Esc-to-close handler is suppressed for\n * this render. The child stays free to register its own `useKeyboard`\n * and handle Esc however it wants (cancel a pending confirmation,\n * block dismissal mid-async, …). `@opentui/react`'s `useKeyboard`\n * registers independent listeners with no propagation, so a child's\n * `return` inside its own handler can't stop this Modal's default\n * handler from firing on the same keystroke — `disableEscape` is the\n * declarative escape hatch that closes that race.\n */\n disableEscape?: boolean\n children: ReactNode\n /** Preferred width in columns. Modal grows to this when the terminal allows. */\n maxWidth?: number\n /** Floor on the width, so content stays minimally legible. */\n minWidth?: number\n /**\n * Hard cap on the modal's height in rows. When set, the modal grows\n * to this size when the terminal allows and shrinks down to fit on\n * smaller screens. Without it, the modal grows to fit its children\n * (uncapped) — fine for picker-style modals, problematic for\n * unbounded content (long turn previews, etc.) where the modal could\n * exceed the viewport and bury its esc-hint footer.\n */\n maxHeight?: number\n /** Columns of breathing room kept on each side of the modal. */\n horizontalMargin?: number\n /** Rows of breathing room kept above + below the modal when `maxHeight` is set. */\n verticalMargin?: number\n}\n\n/**\n * Responsive modal — picks a width (and optionally a height) based on the\n * live terminal size.\n *\n * - On a wide terminal, the modal grows to `maxWidth` so descriptions sit on\n * one line and don't wrap.\n * - On a narrow terminal, the modal shrinks down to `minWidth`, keeping a\n * small horizontal margin from the screen edges. Text inside wraps naturally.\n * - When `maxHeight` is set, the same tier logic applies on the vertical\n * axis — anything beyond is the consumer's job (typically a `scrollbox`\n * child for long content).\n *\n * Uses `useTerminalDimensions()` so it reflows on `SIGWINCH` without remount.\n */\nexport function Modal({\n title,\n bottomTitle,\n rightTitle,\n onClose,\n disableEscape = false,\n children,\n maxWidth = 92,\n minWidth = 44,\n maxHeight,\n horizontalMargin = 4,\n verticalMargin = 2,\n}: ModalProps) {\n const ctx = useContext(ModalContext)\n const dismiss = onClose ?? ctx?.close\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n\n useKeyboard((key) => {\n // `disableEscape` is the declarative gate the child uses to take over\n // Esc handling for a transient state (pending confirmation, in-flight\n // async). Without it the Modal's listener fires alongside the child's\n // own `useKeyboard` — they're sibling listeners, not a propagation\n // chain — and the user's Esc keystroke would clear the pending state\n // AND close the modal in the same tick.\n if (key.name === 'escape' && !disableEscape)\n dismiss?.()\n })\n\n const { width: termWidth, height: termHeight } = useTerminalDimensions()\n const width = Math.max(minWidth, Math.min(maxWidth, termWidth - horizontalMargin * 2))\n const height = maxHeight === undefined\n ? undefined\n : Math.min(maxHeight, Math.max(0, termHeight - verticalMargin * 2))\n\n const panel = (\n <box\n title={title ? ` ${title} ` : undefined}\n bottomTitle={bottomTitle ? ` ${bottomTitle} ` : undefined}\n bottomTitleAlignment=\"right\"\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n backgroundColor: SURFACE.modal,\n paddingTop: 1,\n paddingBottom: 1,\n paddingLeft: 2,\n paddingRight: 2,\n width,\n ...(height !== undefined ? { height } : {}),\n flexDirection: 'column',\n gap: 1,\n }}\n >\n {children}\n </box>\n )\n\n // No `rightTitle` → emit the bordered panel as-is so existing\n // consumers see no tree change. With `rightTitle` → wrap the panel\n // so the overlay can paint as an absolutely-positioned sibling\n // (a child of the bordered box can't ride the top border —\n // OpenTUI's scissor rect excludes it; a sibling outside the panel\n // can). Same trick as TitleOverlay / CompletionPopup.\n if (rightTitle == null)\n return panel\n return (\n <box style={{ width, flexDirection: 'column' }}>\n {panel}\n <box style={{ position: 'absolute', top: 0, right: 1 }}>\n {rightTitle}\n </box>\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { AgentProfile, AgentRegistry } from '../chat/agents'\nimport { useMemo } from 'react'\nimport { useColors, useSelectStyle } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/** Cap the scroll window — a long custom registry shouldn't push the modal off-screen. */\nconst VISIBLE_ROW_CAP = 10\n\n/**\n * Modal that lists the registered {@link AgentProfile}s and lets the user\n * pick one. Rows show: `● selected · label description`.\n *\n * The accent column is intentionally compact (single-char marker) — the\n * profile's `accent` color is read from the active theme so Build and Plan\n * stand apart at a glance without taking horizontal space.\n *\n * Used by `App` (Ctrl+A binding) and exported for hosts that want to drive\n * agent switching from elsewhere in their own composition.\n */\nexport function AgentPickerModal({\n agents,\n currentAgentId,\n onPick,\n}: {\n agents: AgentRegistry\n currentAgentId: string\n onPick: (id: string) => void\n}) {\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n\n const profiles = useMemo(() => Object.values(agents), [agents])\n\n const initialIndex = useMemo(\n () => profiles.findIndex(p => p.id === currentAgentId),\n [profiles, currentAgentId],\n )\n\n const options = useMemo(\n () => profiles.map(p => ({\n name: `${p.id === currentAgentId ? '● ' : ' '}${p.label}`,\n description: p.description,\n value: p.id,\n })),\n [profiles, currentAgentId],\n )\n\n if (profiles.length === 0)\n return <EmptyState />\n\n const visibleRows = Math.min(options.length, VISIBLE_ROW_CAP)\n const currentMissing = initialIndex < 0\n // `<select>` requires a non-negative index; clamp here and surface the\n // mismatch via a banner so the user knows their persisted profile is gone.\n const safeIndex = currentMissing ? 0 : initialIndex\n\n return (\n <Modal title=\"select agent\">\n {currentMissing && (\n <text fg={COLOR.warn}>\n {`Current agent \"${currentAgentId}\" is not in this registry — pick one below.`}\n </text>\n )}\n <select\n {...SELECT_THEME}\n focused\n options={options}\n wrapSelection\n selectedIndex={safeIndex}\n showScrollIndicator={options.length > visibleRows}\n style={{ height: visibleRows }}\n onSelect={(_idx, option) => {\n if (option)\n onPick(option.value as string)\n }}\n />\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n\nfunction EmptyState() {\n const COLOR = useColors()\n return (\n <Modal title=\"select agent\">\n <text fg={COLOR.dim}>No agents registered.</text>\n <text fg={COLOR.mute}>\n Pass an\n <span fg={COLOR.model}>{' agents '}</span>\n registry to\n <span fg={COLOR.model}>{' runTui({ agents }) '}</span>\n to populate this list.\n </text>\n </Modal>\n )\n}\n\n// `accentColor` now lives in `src/chat/agents.ts` (renderer-agnostic).\n// Re-exported below for back-compat with TUI consumers that imported it\n// from `zidane/tui`; new code should import from `zidane/chat` directly.\nexport { accentColor } from '../chat/agents'\n\n/** Re-export so consumers don't need to import the type from `zidane/chat` separately. */\nexport type { AgentProfile }\n","/** @jsxImportSource @opentui/react */\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useEffect, useState } from 'react'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/**\n * Modal that lists currently-dispatching tool calls and lets the user\n * cancel one (or all) without aborting the entire run. Mirrors the\n * `EffortPickerModal` / `AgentPicker` shape — vertical row list, arrow\n * keys navigate, `↵` commits the focused row, `esc` dismisses.\n *\n * Why a dedicated modal rather than re-using the existing turn selection?\n *\n * - Tools fire in parallel inside a single assistant turn. The user\n * needs to *target one specific call* by `callId`, not \"the most\n * recent tool row\". A list picker maps to the underlying data\n * (Map<callId, …>) directly.\n * - The set is highly transient — calls can finish between keystrokes.\n * Building the picker around a fresh snapshot on each mount means\n * each open is consistent with the live state at that moment.\n *\n * `inFlight` is consumed by reference; the modal does NOT subscribe to\n * further updates while open. That's intentional — re-rendering rows\n * out from under the keyboard cursor would surprise the user. The\n * caller closes + reopens the modal if it wants a fresh snapshot\n * (or — more typically — fires the cancel and lets the next open\n * reflect the post-cancel state).\n */\n\n/** One cancellable entry, as the picker sees it. */\nexport interface InFlightToolCall {\n /**\n * `'tool'` — a mid-dispatch tool call addressable via\n * `agent.cancelTool(callId)`. `id` is the `callId`.\n *\n * `'task'` — a running background task (spawned via\n * `shell({ run_in_background: true })`) addressable via\n * `agent.killBackgroundTask(taskId)`. `id` is the `taskId`.\n *\n * The two cancel paths are different primitives — per-call abort\n * signal flip vs SIGTERM-the-process-group — so the picker carries\n * the discriminator and the dispatch callback routes based on it.\n * Optional + defaulting to `'tool'` so older callers stay\n * source-compatible.\n */\n kind?: 'tool' | 'task'\n /** `callId` for tool calls, `taskId` for background tasks. */\n callId: string\n /** Display label — tool name for tool calls, command preview for tasks. */\n tool: string\n /** `Date.now()` when the entry started. */\n startedAt: number\n /** Subagent label (`child-N`) when the call belongs to a subagent, else undefined. */\n childId?: string\n}\n\ninterface Props {\n /** Snapshot of the live in-flight map at modal open time. */\n inFlight: readonly InFlightToolCall[]\n /**\n * Cancel one entry. Returns `true` when the cancel was dispatched\n * (matches `agent.cancelTool`'s contract; for tasks the boolean is\n * synthesized after the async kill settles). The picker passes the\n * full entry so the host can route to the right primitive without\n * a second lookup.\n */\n onCancel: (entry: InFlightToolCall, reason?: string) => boolean | Promise<boolean>\n /** Cancel every entry in the snapshot. */\n onCancelAll: () => void\n /** Dismiss the modal without cancelling anything. */\n onClose: () => void\n}\n\nexport function CancelToolModal({ inFlight, onCancel, onCancelAll, onClose }: Props) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const { width: termWidth } = useTerminalDimensions()\n const [selectedIdx, setSelectedIdx] = useState(0)\n\n // Snapshot the list at mount so a tool finishing mid-pick doesn't\n // shift indices under the keyboard cursor. The `key` deps below tie\n // re-snapshotting to caller-driven changes (close + reopen), which\n // is the intended path.\n const rows = inFlight\n const empty = rows.length === 0\n\n // Auto-close once the last row clears. Without this the user is left\n // staring at an empty list with no obvious next step. Trigger it via\n // an effect so the modal renders the \"no calls\" state for at least\n // one frame — keeps the UX legible if a call finishes precisely when\n // the modal mounts.\n useEffect(() => {\n if (!empty)\n return\n const t = setTimeout(onClose, 500)\n return () => clearTimeout(t)\n }, [empty, onClose])\n\n const safeIndex = empty ? 0 : Math.min(selectedIdx, rows.length - 1)\n\n useKeyboard((key) => {\n if (empty)\n return\n if (key.name === 'up') {\n setSelectedIdx(i => ((i - 1) % rows.length + rows.length) % rows.length)\n return\n }\n if (key.name === 'down') {\n setSelectedIdx(i => (i + 1) % rows.length)\n return\n }\n if (key.name === 'return') {\n const row = rows[safeIndex]\n if (row) {\n // `onCancel` may be async for background tasks (the kill awaits\n // SIGTERM + stream flush). Fire-and-forget — the modal closes\n // immediately so the user isn't held up by the round-trip;\n // the `background:exit` listener in `app.tsx` paints the\n // banner once the kill lands.\n const result = onCancel(row, 'user-clicked-cancel')\n if (result instanceof Promise)\n result.catch(() => { /* errors surface via the next background:exit */ })\n }\n onClose()\n return\n }\n // `a` cancels every call in the snapshot. Cheap escape hatch when\n // multiple tools are wedged at once; the modal closes immediately\n // after so the user can verify the cancellations took.\n if (key.name === 'a') {\n onCancelAll()\n onClose()\n }\n })\n\n // Width budget for the elapsed column — fixed so rows align even when\n // tool names vary wildly.\n const elapsedColWidth = 8\n const callIdColWidth = 14\n const childColWidth = 10\n\n return (\n <Modal\n title=\"cancel tool call\"\n bottomTitle={empty ? 'no calls in flight' : `${rows.length} in flight`}\n maxWidth={Math.min(96, Math.max(64, termWidth - 8))}\n minWidth={56}\n onClose={onClose}\n >\n {empty\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>no tool calls are currently in flight — </span>\n <span fg={COLOR.dim}>nothing to cancel.</span>\n </text>\n )\n : (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {rows.map((row, i) => (\n <CancelToolRow\n key={row.callId}\n row={row}\n isFocused={i === safeIndex}\n highlightBg={SURFACE.selection}\n elapsedColWidth={elapsedColWidth}\n callIdColWidth={callIdColWidth}\n childColWidth={childColWidth}\n />\n ))}\n </box>\n )}\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' cancel selected · '}\n <span fg={COLOR.warn}>a</span>\n {' cancel all · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n\nfunction CancelToolRow({\n row,\n isFocused,\n highlightBg,\n elapsedColWidth,\n callIdColWidth,\n childColWidth,\n}: {\n row: InFlightToolCall\n isFocused: boolean\n highlightBg: string\n elapsedColWidth: number\n callIdColWidth: number\n childColWidth: number\n}) {\n const COLOR = useColors()\n const elapsed = formatElapsed(Date.now() - row.startedAt).padStart(elapsedColWidth, ' ')\n const idLabel = truncate(row.callId, callIdColWidth).padEnd(callIdColWidth, ' ')\n const childLabel = (row.childId ? `· ${row.childId}` : '').padEnd(childColWidth, ' ')\n\n // Glyph differentiates a mid-dispatch tool call from a backgrounded\n // task: `⌁` matches the task-notification banner so the user reads\n // \"this row is the same kind of thing as that banner\".\n const kindGlyph = row.kind === 'task' ? '⌁' : '·'\n\n return (\n <box\n style={{\n height: 1,\n paddingLeft: 1,\n paddingRight: 1,\n flexShrink: 0,\n backgroundColor: isFocused ? highlightBg : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isFocused ? COLOR.brand : COLOR.mute}>{isFocused ? '›' : ' '}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isFocused ? COLOR.warn : COLOR.mute}>{kindGlyph}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isFocused ? COLOR.brand : COLOR.dim}>{row.tool}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.mute}>{idLabel}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.mute}>{childLabel}</span>\n <span fg={COLOR.warn}>{elapsed}</span>\n </text>\n </box>\n )\n}\n\n/**\n * Format a sub-minute duration as `0.3s` / `7.4s`; minute-plus as `2m12s`.\n * Tight + monospace-friendly so the column stays right-aligned.\n */\nfunction formatElapsed(ms: number): string {\n if (ms < 0)\n return '0.0s'\n if (ms < 60_000) {\n const seconds = ms / 1000\n return `${seconds.toFixed(1)}s`\n }\n const totalSeconds = Math.floor(ms / 1000)\n const minutes = Math.floor(totalSeconds / 60)\n const seconds = totalSeconds % 60\n return `${minutes}m${String(seconds).padStart(2, '0')}s`\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max)\n return s\n if (max <= 1)\n return s.slice(0, max)\n return `${s.slice(0, max - 1)}…`\n}\n","/**\n * Two-pronged clipboard write.\n *\n * 1. **OSC 52** — `\\x1b]52;c;<base64>\\x07`. Zero deps, works over SSH\n * (lands on the user's LOCAL machine clipboard, which is the whole\n * point on a remote box). Honored by recent macOS Terminal, iTerm2,\n * kitty, alacritty, wezterm, Ghostty (when `clipboard-write = allow`),\n * tmux / screen with passthrough enabled. Older / stricter setups\n * silently drop it — we have no way to detect that in-band.\n * 2. **Native helper** — `pbcopy` (macOS), `wl-copy` (Wayland Linux),\n * `xclip` (X11 Linux), `clip.exe` (Windows / WSL). Reliable on a\n * LOCAL session regardless of the terminal emulator's clipboard\n * policy, but useless under SSH — they target the remote box's\n * clipboard, not the user's.\n *\n * We try both. OSC 52 is the only thing that matters over SSH; the\n * native helper is the safety net for local sessions where the user's\n * terminal denies OSC 52 access (Ghostty's default `clipboard-write =\n * false` is the common case on macOS). Either landing the text in the\n * OS clipboard means `cmd+v` Just Works™ in the next app, without the\n * user ever having to think about `cmd+c`.\n *\n * Returns `true` if any path appeared to succeed, `false` if every\n * available path failed. Callers can reflect the result in UX (toast /\n * inline confirmation) but should not treat `false` as catastrophic —\n * the user's terminal may still have honored OSC 52 silently.\n */\n\nimport { Buffer } from 'node:buffer'\nimport { spawn } from 'node:child_process'\n\n/**\n * Pick a platform-appropriate native clipboard helper. Returns `null`\n * when no helper is plausibly available (e.g. headless Linux without\n * X11 or Wayland) — the caller still gets the OSC 52 path.\n *\n * We pick the FIRST plausible helper rather than probing each binary\n * with `which` so the hot path stays sync. `spawn` itself emits an\n * `error` event when the binary is missing; we listen for it and\n * swallow, so a wrong guess is harmless.\n */\nfunction pickHelper(): { cmd: string, args: readonly string[] } | null {\n if (process.platform === 'darwin')\n return { cmd: 'pbcopy', args: [] }\n if (process.platform === 'win32')\n return { cmd: 'clip', args: [] }\n // Linux / *BSD — prefer Wayland when present, fall back to X11.\n if (process.env.WAYLAND_DISPLAY)\n return { cmd: 'wl-copy', args: [] }\n if (process.env.DISPLAY)\n return { cmd: 'xclip', args: ['-selection', 'clipboard'] }\n return null\n}\n\n/**\n * Spawn the helper and pipe `text` over stdin. Fire-and-forget: we\n * don't await the child's exit because the OS clipboard is populated\n * synchronously by `pbcopy` / `clip.exe` / `wl-copy` / `xclip` once\n * stdin closes, and a typical drag-select is human-scale latency we\n * don't need to gate the next frame on.\n *\n * Returns `true` if the spawn looked plausibly successful, `false`\n * if we couldn't even attach to the child's stdin (binary missing,\n * permission denied, etc.). An `error` event after spawn flips the\n * returned value to `false` retroactively — but by then the caller\n * has already seen `true` and moved on. We rely on the boolean only\n * for the synchronous \"should we report progress to the user?\" hint,\n * not as a hard contract.\n */\nfunction spawnHelper(text: string): boolean {\n const helper = pickHelper()\n if (!helper)\n return false\n try {\n const child = spawn(helper.cmd, [...helper.args], {\n stdio: ['pipe', 'ignore', 'ignore'],\n })\n // Eat `ENOENT` etc. so an absent helper (xclip not installed) is\n // just a silent fallback to OSC 52, not a process-wide crash.\n child.on('error', () => {})\n child.stdin?.on('error', () => {})\n child.stdin?.end(text, 'utf8')\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * Emit the OSC 52 sequence on stdout. Skips silently when stdout\n * isn't a TTY so `bun run app | tee log` doesn't dump literal escape\n * bytes into a pipe.\n */\nfunction writeOsc52(text: string): boolean {\n if (typeof process === 'undefined' || !process.stdout?.write)\n return false\n if (!process.stdout.isTTY)\n return false\n try {\n // OSC 52 caps payloads at the terminal's discretion. Most honor at\n // least 8 KB; some legacy emulators cut at ~1 KB. We don't enforce\n // a limit ourselves — if a terminal truncates, the user pastes a\n // truncated string and notices, which is preferable to silently\n // refusing to copy large outputs.\n const encoded = Buffer.from(text, 'utf8').toString('base64')\n process.stdout.write(`\\x1B]52;c;${encoded}\\x07`)\n return true\n }\n catch {\n return false\n }\n}\n\nexport function writeToClipboard(text: string): boolean {\n // Always try OSC 52 first — it's free, doesn't fork a process, and\n // it's the only thing that lands on the user's clipboard over SSH.\n const osc = writeOsc52(text)\n // Then also poke the native helper — belt-and-suspenders for local\n // sessions where the user's terminal has OSC 52 disabled. On SSH\n // this writes to the remote box's clipboard (harmless, just unused).\n const helper = spawnHelper(text)\n return osc || helper\n}\n","/** @jsxImportSource @opentui/react */\n// ---------------------------------------------------------------------------\n// CrushThrobber — port of Charm's Crush `internal/ui/anim/Anim`.\n//\n// A row of `size` cells that cycle a random hex/symbol rune per cell per\n// frame, each painted from a left-drifting HSL gradient ramp blended\n// A→B→A→B. Cells \"wake\" individually after a staggered birth offset\n// (0–1 s); until then they show `.` so the throbber appears to materialize\n// rather than pop in. Once every cell is alive, an animated ellipsis\n// (`\"\"` → `\".\"` → `\"..\"` → `\"...\"`, advancing every 8 frames) cycles after\n// the label.\n//\n// Differences from the Go original:\n// - We blend in HSL, not HCL (no `go-colorful` equivalent in our deps).\n// Saturated palette pairs (e.g. Crush's Charple→Dolly) stay vivid;\n// more disparate pairs may pass through slightly different hues.\n// - Rune randomization happens in `render` instead of being prebaked\n// into frame arrays. Cheap enough at 20 fps for ~15 cells and avoids\n// a fixed loop period.\n// ---------------------------------------------------------------------------\n\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { blendHsl } from '../chat/color-gradient'\nimport { useColors } from '../chat/theme-context'\n\n// Character set the throbber cycles through. Verbatim from\n// `charmbracelet/crush/internal/ui/anim/anim.go`'s `availableRunes`.\nconst CRUSH_RUNES = '0123456789abcdefABCDEF~!@#$£€%^&*()+=_'\n// 20 fps — matches Crush's `fps` constant.\nconst TICK_MS = 50\n// Ellipsis cycle order. Note the empty string at the end: Crush walks\n// \".\" → \"..\" → \"...\" → \"\" so there's a beat of \"no dots\" between cycles.\nconst ELLIPSIS_FRAMES = ['.', '..', '...', '']\n// How many ticks per ellipsis frame. 8 * 50 ms = 400 ms per step,\n// matching Crush's `ellipsisAnimSpeed`.\nconst ELLIPSIS_TICKS = 8\n// Upper bound on staggered birth — every cell wakes within this window.\nconst BIRTH_MAX_MS = 1000\n\ninterface CrushThrobberProps {\n /** Trailing label text, painted in `labelColor`. Omit for glyphs only. */\n label?: string\n /**\n * Cycling-character width. Default 15 — Crush's `AssistantMessageItem`\n * size. Sub-1 values are clamped to 1.\n */\n size?: number\n /** Gradient start color (hex). */\n from: string\n /** Gradient end color (hex). */\n to: string\n /**\n * Label / ellipsis tint. Defaults to the theme's body-dim color so the\n * label reads as secondary against the rainbow cycling chars.\n */\n labelColor?: string\n}\n\n/**\n * Build a gradient ramp of length `n` traversing the keys `from → to →\n * from → to` (matching Crush's `CycleColors=true` mode, which prerenders\n * `width*3` stops across four anchor points so the drift can loop without\n * a visible seam).\n */\nfunction buildCycleRamp(from: string, to: string, n: number): string[] {\n // Crush passes four stops `[A, B, A, B]` to `makeGradientRamp`, which\n // splits the requested length across three equal segments. We mirror\n // that arithmetic so the visual cadence lines up.\n const segLen = Math.floor(n / 3)\n const ramp: string[] = []\n for (let i = 0; i < segLen; i++)\n ramp.push(blendHsl(from, to, i / segLen))\n for (let i = 0; i < segLen; i++)\n ramp.push(blendHsl(to, from, i / segLen))\n const tail = n - 2 * segLen\n for (let i = 0; i < tail; i++)\n ramp.push(blendHsl(from, to, i / Math.max(1, tail)))\n return ramp\n}\n\nexport function CrushThrobber({\n label,\n size = 15,\n from,\n to,\n labelColor,\n}: CrushThrobberProps) {\n const COLOR = useColors()\n const cells = Math.max(1, size)\n\n // Frame counter ticks at 20 fps. We rerender every tick so the random\n // runes refresh and the gradient offset shifts left by one slot.\n const [frame, setFrame] = useState(0)\n useEffect(() => {\n const id = setInterval(() => setFrame(f => f + 1), TICK_MS)\n return () => clearInterval(id)\n }, [])\n\n // Birth offsets per cell, randomized on mount and on size change.\n // Each cell stays as `.` until `elapsed` passes its offset, giving the\n // staggered \"fade-in\" Crush opens with.\n const birthOffsets = useMemo(\n () => Array.from({ length: cells }, () => Math.random() * BIRTH_MAX_MS),\n [cells],\n )\n\n // Wall-clock anchor for the birth animation. Kept in a ref so it\n // survives renders without retriggering the gradient memo.\n const startRef = useRef(0)\n if (startRef.current === 0)\n startRef.current = Date.now()\n\n // Precompute the gradient ramp. `cells * 3` stops match Crush's\n // `width*3` ramp size for cycle mode.\n const ramp = useMemo(() => buildCycleRamp(from, to, cells * 3), [from, to, cells])\n\n const elapsed = Date.now() - startRef.current\n const initialized = elapsed >= BIRTH_MAX_MS\n\n // Sliding window into the ramp — `frame % ramp.length` is the left\n // edge; each cell reads `ramp[(frame + i) % ramp.length]`. This is the\n // same \"shift left by one each frame\" effect Crush gets from its\n // `offset++` loop when prebaking cycle frames.\n const rampLen = ramp.length\n const offset = ((frame % rampLen) + rampLen) % rampLen\n\n const labelTint = labelColor ?? COLOR.dim\n\n const ellipsis = initialized && label\n ? ELLIPSIS_FRAMES[Math.floor(frame / ELLIPSIS_TICKS) % ELLIPSIS_FRAMES.length]\n : ''\n\n return (\n <text>\n {Array.from({ length: cells }, (_, i) => {\n const alive = initialized || elapsed >= birthOffsets[i]\n const ch = alive\n ? CRUSH_RUNES[Math.floor(Math.random() * CRUSH_RUNES.length)]\n : '.'\n const fg = ramp[(offset + i) % rampLen]\n return <span key={i} fg={fg}>{ch}</span>\n })}\n {label !== undefined && (\n <>\n <span fg={labelTint}>{` ${label}`}</span>\n <span fg={labelTint}>{ellipsis}</span>\n </>\n )}\n </text>\n )\n}\n","import type { TextareaRenderable } from '@opentui/core'\nimport type { ReactNode, RefObject } from 'react'\nimport type { Theme } from '../chat/theme'\nimport { RGBA, SyntaxStyle } from '@opentui/core'\nimport { createContext, createElement, useContext, useEffect, useMemo } from 'react'\nimport { useTheme } from '../chat/theme-context'\n\ninterface StyleEntry {\n fg?: RGBA\n bg?: RGBA\n bold?: boolean\n italic?: boolean\n underline?: boolean\n dim?: boolean\n}\n\n/**\n * Convert the renderer-agnostic `Theme.syntax` map (hex strings + plain\n * booleans) into an OpenTUI `SyntaxStyle`. Used both for the markdown\n * structural captures (`markup.heading`, `markup.bold`, …) and the\n * embedded Tree-sitter language tokens (`keyword`, `string`, `function`,\n * …) — OpenTUI's `<markdown>` re-uses the same `SyntaxStyle` for the\n * fenced-code renderable, so one table drives both surfaces.\n *\n * `overrides`, when set, replaces individual entries by token name —\n * each override is merged INTO the resolved entry (shallow). Used by\n * the \"on-selection\" variant to swap `markup.raw.bg` for the row's\n * selection surface so inline code chips blend into the highlight\n * rather than reading as \"punched out\" rectangles. Top-level keys\n * absent from the base theme become brand-new entries.\n */\nexport function buildMdStyle(theme: Theme, overrides?: Record<string, Partial<StyleEntry>>): SyntaxStyle {\n const styles: Record<string, StyleEntry> = {}\n for (const [token, style] of Object.entries(theme.syntax)) {\n const out: StyleEntry = {}\n if (style.fg)\n out.fg = RGBA.fromHex(style.fg)\n if (style.bg)\n out.bg = RGBA.fromHex(style.bg)\n if (style.bold)\n out.bold = true\n if (style.italic)\n out.italic = true\n if (style.underline)\n out.underline = true\n if (style.dim)\n out.dim = true\n styles[token] = out\n }\n if (overrides) {\n for (const [token, patch] of Object.entries(overrides))\n styles[token] = { ...(styles[token] ?? {}), ...patch }\n }\n return SyntaxStyle.fromStyles(styles)\n}\n\n// ---------------------------------------------------------------------------\n// MdStyleProvider\n//\n// `SyntaxStyle.fromStyles(...)` allocates a sizable, pure-function-of-theme\n// object that every `<markdown>` instance can share. Building it inside a\n// per-instance `useMemo` (the previous approach) re-ran the conversion N\n// times on a theme switch — N being the number of markdown blocks in the\n// active transcript, easily 50+ on a long session.\n//\n// This context computes it once per theme and broadcasts the result to\n// every `useMdStyle()` consumer. Mounted by `ThemedShell` in app.tsx,\n// directly underneath `ThemeProvider` so the upstream theme is in scope.\n// ---------------------------------------------------------------------------\n\ninterface MdStyleBundle {\n /** Style used by every non-selected markdown row. */\n regular: SyntaxStyle\n /**\n * Variant painted when the enclosing row carries the selection\n * background. Inline-code chips (`markup.raw`) and fenced-code\n * blocks (`markup.raw.block`) get their `bg` rewritten to the row's\n * selection surface so they blend into the highlight instead of\n * reading as \"punched out\" rectangles against the selection.\n */\n selected: SyntaxStyle\n}\n\nconst MdStyleContext = createContext<MdStyleBundle | null>(null)\n\nexport function MdStyleProvider({ children }: { children: ReactNode }) {\n const theme = useTheme()\n const bundle = useMemo<MdStyleBundle>(() => {\n const selectionBg = RGBA.fromHex(theme.surfaces.selection)\n return {\n regular: buildMdStyle(theme),\n selected: buildMdStyle(theme, {\n 'markup.raw': { bg: selectionBg },\n 'markup.raw.block': { bg: selectionBg },\n }),\n }\n }, [theme])\n // Authored with `createElement` rather than JSX so this module stays a\n // plain `.ts` file — keeps the OpenTUI `jsxImportSource` pragma noise\n // out of a one-element provider definition.\n return createElement(MdStyleContext.Provider, { value: bundle }, children)\n}\n\n/**\n * Active markdown / syntax-highlighting style. Returns a single shared\n * `SyntaxStyle` instance for the active theme — built once at provider\n * mount, re-built on theme switch. A `Settings.theme` flip re-paints every\n * `<markdown>` that reads this hook.\n *\n * Pass `{ selected: true }` to get the on-selection variant where inline\n * code chips' background matches the row's selection surface (so the\n * chips blend into the highlight rather than reading as punched-out\n * rectangles).\n *\n * Throws if used outside `<MdStyleProvider>` so a missing wiring shows up\n * loudly in development rather than silently rendering plain text.\n */\nexport function useMdStyle(opts: { selected?: boolean } = {}): SyntaxStyle {\n const bundle = useContext(MdStyleContext)\n if (!bundle)\n throw new Error('useMdStyle must be used inside <MdStyleProvider>')\n return opts.selected ? bundle.selected : bundle.regular\n}\n\n// ---------------------------------------------------------------------------\n// Chip style — bg/fg for in-textarea completion references.\n//\n// The submitted echo paints chip backgrounds via flex-row `<text bg=…>`\n// segments (see `UserPromptBlock`), but the live `<textarea>` is one\n// `EditBufferRenderable` that can't host per-span React children. OpenTUI\n// exposes per-range styling for edit buffers via `SyntaxStyle` +\n// `addHighlightByCharRange`, so we register one styleId per chip kind in\n// the theme's `surfaces.chips` map and resolve the right one per ref at\n// highlight time. `PromptBlock` pulls the styleId via `resolveChipStyleId`\n// on every `completion.references` change.\n// ---------------------------------------------------------------------------\n\nconst CHIP_TOKEN_PREFIX = 'completion.reference'\n\n/** Per-kind token name in the chip `SyntaxStyle` — e.g. `completion.reference.skills`. */\nexport function chipTokenFor(providerId: string): string {\n return `${CHIP_TOKEN_PREFIX}.${providerId}`\n}\n\n/** Fallback token registered for every theme — always resolves to a styleId. */\nexport const CHIP_TOKEN_DEFAULT = chipTokenFor('default')\n\nexport function buildChipStyle(theme: Theme): SyntaxStyle {\n const styles: Record<string, { fg: RGBA, bg: RGBA }> = {}\n // `theme.surfaces.chips` is `{ default: ChipColor } & Partial<…>`; the\n // partial half means `Object.entries` can theoretically surface `undefined`\n // values (an `enabledKey: undefined` slot). Skip those defensively so the\n // RGBA conversion never trips on a missing pair.\n for (const [providerId, chip] of Object.entries(theme.surfaces.chips)) {\n if (!chip)\n continue\n styles[chipTokenFor(providerId)] = {\n fg: RGBA.fromHex(chip.fg),\n bg: RGBA.fromHex(chip.bg),\n }\n }\n return SyntaxStyle.fromStyles(styles)\n}\n\n/**\n * Resolve the styleId for a chip of the given provider id, falling back\n * to {@link CHIP_TOKEN_DEFAULT} when the theme has no kind-specific\n * entry. Returns `null` only when the style was built from a malformed\n * theme (missing `default`) — every built-in theme satisfies the\n * contract, so callers can treat `null` as \"skip highlight\".\n */\nexport function resolveChipStyleId(style: SyntaxStyle, providerId: string): number | null {\n return style.getStyleId(chipTokenFor(providerId)) ?? style.getStyleId(CHIP_TOKEN_DEFAULT)\n}\n\n/**\n * Convert a JS string offset in `text` to the column offset expected by\n * OpenTUI's `addHighlightByCharRange`. The native API takes display-\n * column offsets that EXCLUDE newlines (each `\\n` consumes zero\n * columns) — mirroring the convention documented in `@opentui/core`'s\n * extmark wrapper. Skipping the conversion paints chips at the wrong\n * column once the prompt spans multiple lines, drifting one column\n * further left per preceding newline.\n *\n * Single-cell text covers every chip kind the built-in providers emit\n * (slash-commands + `@`-prefixed file paths). Wide-cell graphemes\n * (emoji, CJK) would need `stringWidth` accounting; left as a TODO\n * until a chip kind actually carries them.\n */\nexport function offsetToHighlightColumn(text: string, offset: number): number {\n const clamped = Math.max(0, Math.min(offset, text.length))\n let newlines = 0\n for (let i = 0; i < clamped; i++) {\n // `charCodeAt` over indexOf-in-loop: avoids substring allocations\n // when refs sit deep in a long buffer.\n if (text.charCodeAt(i) === 10)\n newlines++\n }\n return clamped - newlines\n}\n\nconst ChipStyleContext = createContext<SyntaxStyle | null>(null)\n\nexport function ChipStyleProvider({ children }: { children: ReactNode }) {\n const theme = useTheme()\n const style = useMemo(() => buildChipStyle(theme), [theme])\n return createElement(ChipStyleContext.Provider, { value: style }, children)\n}\n\n/**\n * Active chip-highlight style for the prompt textarea. Single shared\n * instance per theme so the underlying buffer style table is re-allocated\n * only on a theme switch.\n */\nexport function useChipStyle(): SyntaxStyle {\n const style = useContext(ChipStyleContext)\n if (!style)\n throw new Error('useChipStyle must be used inside <ChipStyleProvider>')\n return style\n}\n\n/**\n * Minimal ref shape consumed by {@link useChipHighlights} — `start`/`end`\n * are JS string offsets into the textarea's `plainText`, `providerId`\n * picks the chip color via {@link resolveChipStyleId}. Structurally\n * assignable from `CompletionReference<unknown>` so the prompt block can\n * pass `completion.references` verbatim.\n */\nexport interface ChipHighlightRef {\n start: number\n end: number\n providerId: string\n}\n\n/**\n * Sync per-range chip highlights onto a textarea on every `references`\n * change. Encapsulates the OpenTUI plumbing — `clearAllHighlights` +\n * one `addHighlightByCharRange` per ref, with JS→column-offset\n * translation — so the prompt block stays focused on UX state.\n *\n * The hook owns the contract documented in\n * {@link offsetToHighlightColumn}: refs carry JS string offsets that\n * include newlines; the edit buffer's highlight API takes display\n * columns that exclude them. Skipping this conversion is the difference\n * between stable multi-line chips and the one-column-per-newline drift\n * we shipped in the first cut.\n */\nexport function useChipHighlights(\n textareaRef: RefObject<TextareaRenderable | null>,\n references: readonly ChipHighlightRef[],\n chipStyle: SyntaxStyle,\n): void {\n useEffect(() => {\n const ta = textareaRef.current\n if (!ta)\n return\n ta.clearAllHighlights()\n const text = ta.plainText\n for (const ref of references) {\n const styleId = resolveChipStyleId(chipStyle, ref.providerId)\n if (styleId == null)\n continue\n ta.addHighlightByCharRange({\n start: offsetToHighlightColumn(text, ref.start),\n end: offsetToHighlightColumn(text, ref.end),\n styleId,\n })\n }\n }, [textareaRef, references, chipStyle])\n}\n","/** @jsxImportSource @opentui/react */\nimport type { CliRenderer, RenderNodeContext, ScrollBoxRenderable } from '@opentui/core'\nimport type { Token } from 'marked'\nimport type { MutableRefObject, ReactNode } from 'react'\nimport type { Hint } from '../chat/hints'\nimport type { ThemeColors, ThemeSurfaces } from '../chat/theme'\nimport type { TranscriptItem } from '../chat/transcript-anchors'\nimport type { EditOutcome, EditPayload, Settings, StreamEvent } from '../chat/types'\nimport { BoxRenderable, CodeRenderable, getTreeSitterClient, TextRenderable } from '@opentui/core'\nimport { useRenderer, useTerminalDimensions } from '@opentui/react'\nimport { memo, useEffect, useMemo, useRef, useState } from 'react'\nimport { buildLinearRamp } from '../chat/color-gradient'\nimport { summarizeOutcomes } from '../chat/edit-approval'\nimport { buildContextualDiff, buildUnifiedDiff, filetypeFromPath, summarizeEditPayload } from '../chat/edit-diff'\nimport { compactPath, fmtTokens, formatDuration, formatTaskStatus } from '../chat/format'\nimport { clipHintsToWidth, hintsLength, truncateTrailing } from '../chat/hints'\nimport { splitPromptSegments } from '../chat/prompt-segments'\nimport { useSettings } from '../chat/settings-context'\nimport { isTurnHighlighted, isVisible, marginTopFor, turnSelectionOwnership } from '../chat/store'\nimport { resolveChipColor } from '../chat/theme'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { TODO_STATUS_GLYPHS, TODOWRITE_TOOL } from '../chat/todos'\nimport { displayNameFor, formatToolCall } from '../chat/tool-formatters'\nimport { computeTurnAnchors } from '../chat/transcript-anchors'\nimport { writeToClipboard } from './clipboard'\nimport { CrushThrobber } from './crush-throbber'\nimport { useMdStyle } from './theme'\n\n/**\n * Memoized so a flush that mutates only the trailing event doesn't force the\n * entire transcript to re-render. Each event holds a stable reference until\n * its content changes (we only ever recreate the streaming-markdown tail).\n *\n * The outer wrapper handles top-margin per kind (and per neighbor) so spacing\n * is the single source of truth for inter-event breathing room. Selected\n * rows fill with `surfaces.selection` and absorb their `marginTop` as\n * `paddingTop` — that keeps the gap above colored too so consecutive\n * same-turn events read as one continuous highlighted block instead of a\n * striped list.\n */\nconst EventLine = memo(\n ({ event, previous, depthOffset = 0, selected = false, anchorId, hideChildLabel = false }: {\n event: StreamEvent\n previous?: StreamEvent\n /**\n * Subtract from the event's depth before computing left indent. The\n * subagent box already provides one level of visual offset via its\n * border + padding, so events rendered inside it pass `depthOffset: 1`\n * to avoid double-indenting.\n */\n depthOffset?: number\n /** Fills the row with the theme's selection background. */\n selected?: boolean\n /**\n * Stable render id targeted by `ScrollBoxRenderable.scrollChildIntoView`.\n * Only set on the first event of each turn so the scroll target is\n * unambiguous — see `computeTurnAnchors`.\n */\n anchorId?: string\n /**\n * Suppress the `child-N` label on subagent `spawn-start` / `spawn-end`\n * rows. The enclosing `SubagentBlock` already shows the same label in\n * its border title; repeating it on every inner row is noise.\n */\n hideChildLabel?: boolean\n }) => {\n const SURFACE = useSurfaces()\n const gap = marginTopFor(event, previous)\n return (\n <box\n id={anchorId}\n style={{\n // Selection trick: when highlighted, convert the inter-event gap\n // from outer margin to inner padding so the bg fill extends\n // through it. With marginTop the gap is OUTSIDE the box and\n // stays uncolored, producing visible stripes between consecutive\n // same-turn events.\n marginTop: selected ? 0 : gap,\n paddingTop: selected ? gap : 0,\n backgroundColor: selected ? SURFACE.selection : undefined,\n // Pin width to the scrollbox content area + lock the row against\n // shrinking. Without these, a streamed markdown growing taller can\n // make Yoga renegotiate widths for *every* sibling row, which is the\n // visible \"the text jiggles a column to the left, then back\" effect.\n alignSelf: 'stretch',\n flexShrink: 0,\n flexDirection: 'column',\n }}\n >\n <EventLineImpl event={event} depthOffset={depthOffset} hideChildLabel={hideChildLabel} selected={selected} />\n </box>\n )\n },\n)\n\n/**\n * `@opentui/react` extends `React.JSX.IntrinsicElements`, so `onSubmit` on `<input>`\n * gets intersected with the DOM `SubmitEvent` shape and demands an unhelpful overload.\n * The OpenTUI input runtime fires `(value: string) => void`; this helper isolates\n * the cast so each call site stays readable.\n */\nexport function onInputSubmit(handler: (value: string) => void): never {\n return handler as unknown as never\n}\n\n// ---------------------------------------------------------------------------\n// Footer — single status bar: shortcut hints on the left, context-window\n// indicator on the right. Hint labels carry their own color so the model\n// id (next to `ctrl+m`) and agent label (next to `shift+tab`) read as\n// distinct values rather than generic hint text — no separate badges\n// needed.\n// ---------------------------------------------------------------------------\n\nexport interface ContextUsage {\n used: number\n max: number\n}\n\n/**\n * Footer status bar. Renders as a single row when the terminal is wide\n * enough, otherwise stacks the context indicator beneath the hint row.\n * Width tiering is driven by plain-text length estimates — close enough\n * since the segments are ASCII-heavy.\n */\nexport function Footer({\n hints,\n context,\n cost = null,\n status = null,\n}: {\n hints: Hint[]\n context: ContextUsage | null\n /**\n * Cumulative USD cost for the current session, summed across runs.\n * Hidden when `null` or `0` — most providers don't populate cost today\n * (only OpenRouter does), and \"$0.00\" would read as a lie on the rest.\n */\n cost?: number | null\n /**\n * Active run status, surfaced as a small glyph at the right edge of\n * the hints (left) section. Replaces the spinner that used to live in\n * the title overlay. `null` hides it entirely. Priority order matches\n * the previous title-overlay logic: asking > compacting > busy.\n */\n status?: 'busy' | 'compacting' | 'asking' | null\n}) {\n const { width } = useTerminalDimensions()\n\n const showCost = typeof cost === 'number' && cost > 0\n\n // 1ch padding on each side. Plus 1ch breathing room between hints and\n // context in single-row layout (the spacer's minimum width).\n const inner = Math.max(0, width - 2)\n const hW = hintsLength(hints)\n const ctxW = context ? contextIndicatorLength(context) : 0\n const costW = showCost ? costIndicatorLength(cost) : 0\n // \"$<cost> · ctx …\" — 3 cells for the · separator when both render.\n const rightW = costW + ctxW + (costW > 0 && ctxW > 0 ? 3 : 0)\n // Status icon reserves 1 leading-gap cell + 1 glyph cell (2 for the\n // ❓ asking-question emoji, which is double-width on most terminals).\n const sW = status === 'asking' ? 3 : status ? 2 : 0\n\n const oneRowFits = hW + sW + (rightW > 0 ? rightW + 1 : 0) <= inner\n\n if (oneRowFits) {\n return (\n <box style={{ flexDirection: 'row', height: 1, paddingLeft: 1, paddingRight: 1 }}>\n <HintsText hints={hints} />\n <FooterStatusIcon status={status} />\n <box style={{ flexGrow: 1 }} />\n <RightStatus cost={showCost ? cost : null} context={context} />\n </box>\n )\n }\n\n // Stacked layout — give the hints their own row, then the context\n // indicator below. Clip the hint list to the row's renderable width\n // so a still-too-long primary set doesn't wrap into a second row and\n // collide with the context indicator below it.\n const stackedHints = clipHintsToWidth(hints, inner)\n return (\n <box style={{ flexDirection: 'column', paddingLeft: 1, paddingRight: 1 }}>\n {(stackedHints.length > 0 || status) && (\n <box style={{ flexDirection: 'row', height: 1 }}>\n <HintsText hints={stackedHints} />\n <FooterStatusIcon status={status} />\n </box>\n )}\n {rightW > 0 && (\n <box style={{ flexDirection: 'row', height: 1 }}>\n <box style={{ flexGrow: 1 }} />\n <RightStatus cost={showCost ? cost : null} context={context} />\n </box>\n )}\n </box>\n )\n}\n\nfunction RightStatus({ cost, context }: { cost: number | null, context: ContextUsage | null }) {\n const COLOR = useColors()\n if (cost == null && !context)\n return null\n return (\n <text>\n {cost != null && <CostIndicator cost={cost} />}\n {cost != null && context && <span fg={COLOR.mute}>{' · '}</span>}\n {context && <ContextIndicator context={context} />}\n </text>\n )\n}\n\n/**\n * Right-edge status glyph in the Footer's left section. Renders a single\n * cell preceded by a 1ch gap when active; nothing at all when `status` is\n * null. Color / glyph mapping mirrors the old title-overlay logic so the\n * cue reads the same just relocated to the bottom bar.\n */\nfunction FooterStatusIcon({ status }: { status: 'busy' | 'compacting' | 'asking' | null }) {\n const COLOR = useColors()\n if (!status)\n return null\n const glyph = status === 'asking'\n ? <span>❓</span>\n : status === 'busy'\n ? <StatusSpinner color={COLOR.warn} />\n : <StatusSpinner color={COLOR.accent} />\n return (\n <text>\n <span>{' '}</span>\n {glyph}\n </text>\n )\n}\n\nfunction HintsText({ hints }: { hints: readonly Hint[] }) {\n const COLOR = useColors()\n return <text fg={COLOR.dim}>{renderHintSpans(hints, COLOR)}</text>\n}\n\n/**\n * Pure renderer for a list of {@link Hint}s as colored spans —\n * `<key1> <label1> · <key2> <label2> · …` with warn/dim/mute colors.\n *\n * Returns spans only (no enclosing `<text>`, no leading / trailing\n * whitespace) so the caller can wrap it in whichever container fits\n * their surface: the bottom-bar footer uses a single-row `<text>`, the\n * prompt-box overlay wraps it with leading + trailing spaces so the\n * outermost cells punch through the border like a native title would.\n *\n * Pattern matches `renderRefSpans` below — pure function over an opaque\n * `ThemeColors`, no internal hook calls so it composes inside any\n * `<text>` regardless of where the parent grabbed its palette.\n */\nexport function renderHintSpans(hints: readonly Hint[], COLOR: ThemeColors): ReactNode {\n return hints.map((h, i) => (\n <span key={i}>\n {i > 0 && <span fg={COLOR.mute}> · </span>}\n <span fg={h.keyColor ?? COLOR.warn}>{h.key}</span>\n {h.extra && <span fg={h.extra.keyColor ?? COLOR.mute}>{h.extra.key}</span>}\n <span fg={h.labelColor ?? COLOR.dim}>{` ${h.label}`}</span>\n {h.extra && <span fg={h.extra.labelColor ?? COLOR.dim}>{` ${h.extra.label}`}</span>}\n </span>\n ))\n}\n\nfunction ContextIndicator({ context }: { context: ContextUsage }) {\n const COLOR = useColors()\n const ratio = context.max > 0 ? context.used / context.max : 0\n const pct = Math.round(ratio * 100)\n const color = ratio >= 0.85 ? COLOR.error : ratio >= 0.6 ? COLOR.warn : COLOR.dim\n return (\n <span>\n <span fg={COLOR.mute}>ctx </span>\n <span fg={color}>{fmtTokens(context.used)}</span>\n <span fg={COLOR.mute}>{` / ${fmtTokens(context.max)} `}</span>\n <span fg={color}>{`(${pct}%)`}</span>\n </span>\n )\n}\n\nfunction CostIndicator({ cost }: { cost: number }) {\n const COLOR = useColors()\n const fg = COLOR.money ?? COLOR.warn\n return (\n <span>\n <span fg={COLOR.mute}>$</span>\n <span fg={fg}>{formatCost(cost)}</span>\n </span>\n )\n}\n\n// ---------------------------------------------------------------------------\n// TitleOverlay — colored title (left) + optional meta (right) painted over\n// a bordered box's top border. Replaces the native `title=` prop where\n// per-segment color is needed (the native title is single fg).\n//\n// Render contract: must be a SIBLING of the bordered box, declared AFTER\n// it inside a shared parent so the renderer paints the overlay on top of\n// the border row. Bordered boxes clip their own absolute children via a\n// scissor rect that excludes the border, so the overlay can't live\n// inside the bordered box itself.\n// ---------------------------------------------------------------------------\n\n/**\n * Width budget reservations applied to the responsive math in\n * {@link TitleOverlay}. Each title / meta segment owns a leading +\n * trailing mute space (the cells that punch through the underlying\n * border `─`); a 2-cell `GAP` keeps title and meta visually distinct\n * when both render in the same row.\n */\nconst TITLE_OVERLAY_WRAP = 2\nconst TITLE_OVERLAY_META_WRAP = 2\nconst TITLE_OVERLAY_GAP = 2\n\n/**\n * One run of styled text in a {@link TitleOverlay} meta segment array.\n * `color` defaults to `COLOR.dim` (the standard secondary tone for\n * meta info); supply an explicit color to accent a value — typical use\n * is a `warn` tint on a primary stat (turn count, session count) while\n * surrounding separators stay `mute`.\n */\nexport interface MetaSegment {\n text: string\n /** Foreground color. Defaults to `COLOR.dim`. */\n color?: string\n}\n\n/**\n * Render text as a per-character HSL gradient between two hex colors.\n * Used by the title overlay so the chat header sweeps the theme's\n * throbber pair (Charple→Dolly for Crush) instead of sitting flat on\n * the brand color.\n */\nfunction renderGradientText(text: string, from: string, to: string) {\n // The ramp matches the text length so every character gets a stop;\n // single-character strings still produce a stable midpoint color.\n const ramp = buildLinearRamp(from, to, text.length)\n return text.split('').map((ch, i) => (\n <span key={i} fg={ramp[i]}>{ch}</span>\n ))\n}\n\n/**\n * Colored title for a full-screen bordered surface. The `title` slot\n * rides `titleColor` (defaults to a per-character gradient across the\n * theme's throbber pair, fallback brand→accent) on the LEFT of the top\n * border; the optional `meta` slot rides on the RIGHT.\n *\n * `meta` accepts either:\n * - a plain `string` → rendered entirely in `COLOR.dim` (the simple\n * \"single dim label\" case, e.g. `\"5 sessions\"`).\n * - a {@link MetaSegment} array → rendered concatenated with each\n * segment's own color (lets a stat like turn count stand out from\n * the surrounding separators).\n *\n * Responsive behavior (driven by `useTerminalDimensions`):\n * - Both fit → render both with a 2-cell visual gap between them.\n * - Meta doesn't fit → drop meta; title takes the full budget.\n * - Title doesn't fit → truncate with a trailing `…`.\n * - Terminal is degenerately narrow → render nothing.\n *\n * The width math assumes the immediate parent fills the screen's\n * content area (the standard `flexGrow: 1` flex column). Hosts running\n * the bordered box inside a narrower container should pass `parentWidth`\n * to override the terminal-width assumption.\n *\n * @example\n * ```tsx\n * <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n * <box style={{ border: true, flexGrow: 1 }}>...</box>\n * <TitleOverlay\n * title=\"my session\"\n * meta={[\n * { text: '#abcd' },\n * { text: ' · ', color: COLOR.mute },\n * { text: '5 turns', color: COLOR.warn },\n * ]}\n * />\n * </box>\n * ```\n */\nexport function TitleOverlay({\n title,\n meta = null,\n titleColor,\n parentWidth,\n statusIcon = null,\n statusIconCells = 1,\n}: {\n title: string\n /** Optional right-aligned secondary segment. Dropped first when space runs out. */\n meta?: string | readonly MetaSegment[] | null\n /** Defaults to `COLOR.brand`. */\n titleColor?: string\n /**\n * Override for the parent container's width in columns. Defaults to\n * `terminalWidth - 2` to match the standard screen layout that has\n * 1-cell padding on each side.\n */\n parentWidth?: number\n /**\n * Optional single-cell glyph rendered immediately AFTER the title\n * text. Used by the chat screen to surface a streaming / compacting\n * indicator next to the session name — keeps the affordance close to\n * what it qualifies (the conversation) without claiming a full row\n * above the prompt input.\n *\n * Sized as `statusIconCells` cells + one leading space when present,\n * so the title budget reserves the right number of extra columns.\n * Pass a {@link StatusSpinner} or any other 1-cell `<span>`; `null`\n * (the default) takes no space.\n *\n * Render order is `title <space> icon`. The leading title slot reads\n * naturally — eye lands on the name first, then catches the activity\n * indicator — and avoids the visual jitter that came with the icon\n * pulsing on the leftmost position.\n */\n statusIcon?: ReactNode\n /**\n * Cell width of `statusIcon`. Defaults to `1` for the standard\n * single-cell spinners; pass `2` when the icon is a double-width\n * emoji glyph (e.g. `❓`) so the title-budget math keeps the meta\n * segment from being clipped a column early.\n */\n statusIconCells?: 1 | 2\n}) {\n const COLOR = useColors()\n const { width: termWidth } = useTerminalDimensions()\n // When `titleColor` is omitted, paint the title as a per-character\n // gradient using the theme's throbber pair (Charple→Dolly for Crush;\n // brand→accent for everything else by fallback). When the caller\n // explicitly hands us a color (e.g. the wizard's solid-accent title)\n // we honor it and skip the gradient.\n const useGradient = titleColor === undefined\n // Title sweeps `throbber.to → throbber.from` so the gradient flows\n // *toward* the brand anchor on the right side of the title. For\n // Crush this reads Dolly (pink) → Charple (purple) left-to-right.\n const gradientFrom = COLOR.throbber?.to ?? COLOR.accent\n const gradientTo = COLOR.throbber?.from ?? COLOR.brand\n const fg = titleColor ?? COLOR.brand\n\n // Available decorable cells along the top border = parent width\n // minus the two corner glyphs. Corners are off-limits for overlays.\n const W = Math.max(0, parentWidth ?? termWidth - 2)\n const inner = Math.max(0, W - 2)\n\n // The status icon, when set, eats `statusIconCells` glyph cells + 1\n // leading space in the title slot. Reserve those out of every budget\n // calculation so the glyph never pushes the title (or the meta)\n // off-screen — double-width emoji icons need `statusIconCells: 2`.\n const iconReserve = statusIcon ? 1 + statusIconCells : 0\n const metaLen = metaSegmentsLength(meta)\n const showMeta = meta != null && metaLen > 0\n && title.length + iconReserve + TITLE_OVERLAY_WRAP + TITLE_OVERLAY_GAP + metaLen + TITLE_OVERLAY_META_WRAP <= inner\n const titleBudget = (showMeta\n ? inner - (metaLen + TITLE_OVERLAY_META_WRAP) - TITLE_OVERLAY_GAP - TITLE_OVERLAY_WRAP\n : inner - TITLE_OVERLAY_WRAP) - iconReserve\n const visibleTitle = titleBudget <= 0 ? '' : truncateTrailing(title, titleBudget)\n\n return (\n <>\n {(visibleTitle || statusIcon) && (\n <text style={{ position: 'absolute', top: 0, left: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n {useGradient && visibleTitle.length > 0\n ? renderGradientText(visibleTitle, gradientFrom, gradientTo)\n : <span fg={fg}>{visibleTitle}</span>}\n {statusIcon && <span fg={COLOR.mute}>{' '}</span>}\n {statusIcon}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n )}\n {showMeta && meta && (\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n {typeof meta === 'string'\n ? <span fg={COLOR.dim}>{meta}</span>\n : meta.map((seg, i) => (\n <span key={i} fg={seg.color ?? COLOR.dim}>{seg.text}</span>\n ))}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n )}\n </>\n )\n}\n\n/** Total printed-character length of a {@link TitleOverlay} `meta` value. */\nfunction metaSegmentsLength(meta: string | readonly MetaSegment[] | null | undefined): number {\n if (meta == null)\n return 0\n if (typeof meta === 'string')\n return meta.length\n return meta.reduce((sum, seg) => sum + seg.text.length, 0)\n}\n\nfunction contextIndicatorLength(context: ContextUsage): number {\n const ratio = context.max > 0 ? context.used / context.max : 0\n const pct = Math.round(ratio * 100)\n // \"ctx <used> / <max> (<pct>%)\"\n return 4 + fmtTokens(context.used).length + 3 + fmtTokens(context.max).length\n + 2 + String(pct).length + 2\n}\n\n// Mirrors `session-details-modal.tsx:469` — 4 digits of precision under a\n// cent, 2 above. Single source of truth would be nicer; one shared helper\n// can wait until a third surface needs it.\nfunction formatCost(cost: number): string {\n return cost.toFixed(cost < 0.01 ? 4 : 2)\n}\n\nfunction costIndicatorLength(cost: number): number {\n return 1 + formatCost(cost).length // \"$\" + digits\n}\n\n// ---------------------------------------------------------------------------\n// Spinner — animated braille used while a run is streaming.\n// ---------------------------------------------------------------------------\n\nconst SPINNER_FRAMES = ['▰▱▱▱▱', '▰▰▱▱▱', '▰▰▰▱▱', '▰▰▰▰▱', '▰▰▰▰▰', '▱▰▰▰▰', '▱▱▰▰▰', '▱▱▱▰▰', '▱▱▱▱▰', '▱▱▱▱▱']\nconst SPINNER_INTERVAL_MS = 80\n\n/**\n * Tick a frame index every {@link SPINNER_INTERVAL_MS}. Pulled out of the\n * default `Spinner` body so the title-overlay status icon and any other\n * tiny single-glyph spinner can share the same animation cadence without\n * each instance running its own `setInterval`.\n */\nfunction useSpinnerFrame(): string {\n const [frame, setFrame] = useState(0)\n useEffect(() => {\n const id = setInterval(() => setFrame(f => (f + 1) % SPINNER_FRAMES.length), SPINNER_INTERVAL_MS)\n return () => clearInterval(id)\n }, [])\n return SPINNER_FRAMES[frame]!\n}\n\nexport function Spinner({ label }: { label?: string }) {\n const COLOR = useColors()\n const glyph = useSpinnerFrame()\n return (\n <text fg={COLOR.warn}>\n {glyph}\n {label !== undefined && <span fg={COLOR.dim}>{` ${label}`}</span>}\n </text>\n )\n}\n\n/**\n * Glyph-only spinner painted in a caller-chosen color. Used as the\n * status icon prefixed to the chat screen's session title — a single\n * cell wide so it slots in front of the title text without disturbing\n * the existing `TITLE_OVERLAY_WRAP` budget math.\n */\nexport function StatusSpinner({ color }: { color: string }) {\n const glyph = useSpinnerFrame()\n return <span fg={color}>{glyph}</span>\n}\n\n// ---------------------------------------------------------------------------\n// Transcript — scrollbox with sticky-bottom and structured event rendering.\n// ---------------------------------------------------------------------------\n\nexport function Transcript({\n events,\n settings,\n selectedTurnId = null,\n busy = false,\n}: {\n events: StreamEvent[]\n settings: Settings\n /**\n * Turn id whose events should render with the selection accent bar. `null`\n * leaves every event unhighlighted (normal mode). Events without a turnId\n * (synthetic markers) are never highlighted.\n */\n selectedTurnId?: string | null\n /**\n * True while a run is streaming. Drives the inline working throbber that\n * appears at the tail of the transcript while the assistant is preparing\n * a reply (thinking, or otherwise busy before content/tool calls land).\n * Matches Crush's `AssistantMessageItem.isSpinning()` semantics: the\n * throbber hides as soon as the active turn produces visible output.\n */\n busy?: boolean\n}) {\n const COLOR = useColors()\n const items = useMemo(() => partitionTranscript(events, settings), [events, settings])\n\n // `busy` is true for the entire `agent.run` lifecycle (set on submit /\n // interaction-resume, cleared in the run's `finally`), so this directly\n // mirrors \"the assistant is still working on this turn\". The throbber\n // stays put under the streaming content as a persistent \"still active\"\n // cue — disappears the moment the run resolves. Gated on\n // `Settings.showThrobber` (off by default) so users who find the\n // animated glyphs noisy can drop them without losing the run's\n // markdown / status-row signals.\n const showThrobber = busy && settings.showThrobber\n // Label only when the run is currently emitting thinking tokens —\n // matches Crush, where `AssistantMessageItem.renderSpinning()` sets\n // the label to \"Thinking\" / \"Summarizing\" only for those states and\n // otherwise renders the bare gradient.\n const throbberLabel = events.length > 0 && events[events.length - 1].kind === 'thinking'\n ? 'Thinking'\n : undefined\n // Ownership map for the select-turn coalesce rule: result-only user\n // turns map to the assistant turn whose `tool_call`s they answer. The\n // highlight gate ({@link isTurnHighlighted}) and the snap-to-bottom\n // branch below both consult it so the selection accent extends from\n // an assistant turn onto its tool-result rows as ONE unit.\n const ownership = useMemo(() => turnSelectionOwnership(events), [events])\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Anchor ids the scrollbox can target for auto-scroll. We tag ONLY the\n // first rendered event of each turn so `scrollChildIntoView` has an\n // unambiguous target; later events of the same turn render without an\n // id. Walk in the same order as the render below to keep the index\n // mapping in lock-step with the actual JSX tree.\n const anchors = useMemo(() => computeTurnAnchors(items), [items])\n\n // Auto-scroll the selected turn into view. Fires on every selection\n // change (and on items mutation while a selection is held — e.g. a\n // streamed turn growing taller pushes prior turns up).\n //\n // Behavior:\n // - Most turns: `scrollChildIntoView` brings the FIRST event of the\n // selected turn into view. It's a no-op when the row already fits,\n // so we don't fight the user's manual scroll position when they're\n // already on the right turn.\n // - Last turn: snap to the absolute bottom of the scrollbox instead.\n // Anchoring on the first event would scroll UPWARD if the turn is\n // taller than the viewport, leaving the assistant's tail off-screen\n // — which felt wrong when navigating back into the most recent\n // message. Snapping to bottom also re-engages sticky-bottom for any\n // follow-up streaming once select mode exits.\n //\n // Deferred to the next frame via `requestAnimationFrame` because the\n // effect fires synchronously after React commit but BEFORE OpenTUI's\n // layout pass updates the scrollbar's `scrollSize`. Reading\n // `scrollHeight` (or letting `scrollChildIntoView` read the child's\n // measured `y` / `height`) immediately can use stale values; deferring\n // one frame gives Yoga + the scrollbar time to settle.\n useEffect(() => {\n if (!selectedTurnId)\n return\n const scrollbox = scrollboxRef.current\n if (!scrollbox)\n return\n const handle = requestAnimationFrame(() => {\n // Snap to absolute bottom when the selected turn is the last\n // rendered turn — OR when it OWNS the last rendered turn (i.e.\n // the trailing turn is a result-only one coalesced into our\n // selection). Without the ownership branch, navigating to the\n // most recent assistant turn would scroll its anchor into view\n // but leave the matching tool-result row peeking off the bottom\n // edge of the viewport.\n const ownsLast = anchors.lastTurnId !== undefined\n && ownership.get(anchors.lastTurnId) === selectedTurnId\n if (selectedTurnId === anchors.lastTurnId || ownsLast) {\n // `scrollSize - viewportSize` is the max scroll position. Pass a\n // value at least that large; the scrollbar clamps internally so we\n // don't have to subtract the viewport height ourselves.\n scrollbox.scrollTop = scrollbox.scrollHeight\n return\n }\n const id = anchors.idByTurn.get(selectedTurnId)\n if (id)\n scrollbox.scrollChildIntoView(id)\n })\n return () => cancelAnimationFrame(handle)\n }, [selectedTurnId, anchors, ownership])\n\n // Empty + idle → show the welcome card. Empty + busy (rare; the\n // submission usually appends a `user-prompt` event synchronously)\n // falls through so the throbber still has somewhere to land.\n if (items.length === 0 && !showThrobber)\n return <EmptyState />\n\n return (\n <scrollbox\n ref={scrollboxRef}\n // Never claim keyboard focus: the textarea is the chat's primary input,\n // so up/down/page-up/page-down should stay with it (or with the modal\n // when one is open). Mouse-wheel scrolling still works.\n focusable={false}\n style={{ flexGrow: 1, paddingLeft: 1, paddingRight: 1 }}\n stickyScroll\n stickyStart=\"bottom\"\n // Slim, theme-tinted scrollbar. Defaults are loud — a 2-cell-wide\n // bar with a dark #252527 track painted top-to-bottom (visible\n // as a stripe down the right edge even when the conversation\n // barely overflows) and a foreign #9a9ea3 thumb that doesn't\n // match any theme palette. We keep just the thumb and paint it\n // in `mute`, which already rides the active theme.\n verticalScrollbarOptions={{\n width: 1,\n trackOptions: {\n backgroundColor: 'transparent',\n foregroundColor: COLOR.mute,\n },\n }}\n >\n {/*\n Index-based keying is intentional. The visible items list is\n derived from an append-only event stream filtered through\n `partitionTranscript`; the same `event` reference always occupies\n the same slot during a live stream. Filter toggles (e.g. \"show\n thinking\") re-derive the list, but `EventLine` is keyed by index\n and memoized on its props — `event` identity drives the actual\n re-render decision, so a shifted slot reusing a memo entry is\n functionally correct (every `EventLineImpl` is stateless).\n Switching to a derived stable key would require threading a\n per-event id through the stream buffer + persistence layer,\n which isn't worth the churn for the same observable behavior.\n */}\n {items.map((item, i) => (\n item.kind === 'event'\n ? (\n <EventLine\n key={i}\n event={item.event}\n previous={item.previous}\n selected={isTurnHighlighted(item.event, selectedTurnId, ownership)}\n anchorId={anchors.ids[i][0]}\n />\n )\n : (\n <SubagentBlock\n key={i}\n events={item.events}\n previous={item.previous}\n selectedTurnId={selectedTurnId}\n anchorIds={anchors.ids[i]}\n />\n )\n ))}\n {showThrobber && (\n <box style={{ marginTop: items.length > 0 ? 1 : 0 }}>\n <CrushThrobber\n label={throbberLabel}\n from={COLOR.throbber?.from ?? COLOR.brand}\n to={COLOR.throbber?.to ?? COLOR.accent}\n />\n </box>\n )}\n </scrollbox>\n )\n}\n\n// `isVisible` / `isEditErrorResult` / `marginTopFor` now live in\n// `src/chat/store.ts`; `computeTurnAnchors` + `TranscriptItem` now live\n// in `src/chat/transcript-anchors.ts`. See the re-exports at the bottom\n// of this file for back-compat with TUI consumers; new code should\n// import directly from `zidane/chat`.\n\n/**\n * Walk the visible-event list once and group consecutive child events\n * (`depth > 0`) into runs so we can wrap each run in a single bordered\n * subagent box.\n *\n * When `hideSubagentOutput` is on, `isVisible` already filters most child\n * events out; the surviving `spawn-start` / `spawn-end` markers render as\n * plain entries (no boxing) — there's nothing meaningful to box.\n */\nfunction partitionTranscript(events: StreamEvent[], settings: Settings): TranscriptItem[] {\n const visible = events.filter(e => isVisible(e, settings))\n if (visible.length === 0)\n return []\n\n // Hide-mode: spawn-start/end are the only child events left. Don't box\n // them — they're already standalone \"subagent N is working/done\" lines.\n if (settings.hideSubagentOutput) {\n return visible.map((event, i) => ({ kind: 'event', event, previous: visible[i - 1] }))\n }\n\n const items: TranscriptItem[] = []\n let run: StreamEvent[] = []\n let runPrevious: StreamEvent | undefined\n\n const flush = () => {\n if (run.length > 0) {\n items.push({ kind: 'child-run', events: run, previous: runPrevious })\n run = []\n runPrevious = undefined\n }\n }\n\n for (let i = 0; i < visible.length; i++) {\n const event = visible[i]\n if (isChild(event)) {\n if (run.length === 0)\n runPrevious = visible[i - 1]\n run.push(event)\n }\n else {\n flush()\n items.push({ kind: 'event', event, previous: visible[i - 1] })\n }\n }\n flush()\n return items\n}\n\n/**\n * Bordered container for one run of subagent events. The box's border +\n * left padding give the visual \"this is a subagent\" affordance, so events\n * inside render with `depthOffset: 1` — a direct child of the parent\n * (depth 1) sits flush against the box's inner padding rather than being\n * indented twice. Grandchildren (depth ≥ 2) still indent further, so\n * nested subagents remain visually distinct.\n */\nfunction SubagentBlock({\n events,\n previous,\n selectedTurnId = null,\n anchorIds,\n}: {\n events: StreamEvent[]\n previous?: StreamEvent\n selectedTurnId?: string | null\n /**\n * Per-inner-event scroll anchor ids, parallel to `events`. Entries are\n * `undefined` for events that aren't a turn's first occurrence — see\n * `computeTurnAnchors`.\n */\n anchorIds?: readonly (string | undefined)[]\n}) {\n const COLOR = useColors()\n const childIds = useMemo(() => {\n const set = new Set<string>()\n for (const e of events) {\n if (e.childId)\n set.add(e.childId)\n }\n return Array.from(set)\n }, [events])\n\n const title = childIds.length === 0\n ? ' subagent '\n : childIds.length === 1\n ? ` ${childIds[0]} `\n : ` subagents · ${childIds.join(', ')} `\n\n // Keep the same vertical breathing room a markdown/spawn-start event would\n // get standalone — without this the box looks glued to the parent's tool\n // call above it.\n const marginTop = previous ? 1 : 0\n\n // Single-subagent runs hoist the `child-N` label to the box title and drop\n // it from every inner line — the label would otherwise repeat verbatim on\n // every spawn-start / spawn-end row. Multi-subagent runs keep the per-line\n // tag so the reader can still disambiguate interleaved events.\n const hideChildLabel = childIds.length === 1\n\n return (\n <box\n title={title}\n style={{\n border: true,\n // Brand-tinted frame is the cheapest way to colorize the title — the\n // box's title shares its color with `borderColor`. Subagents now read\n // visually closer to `task-notification` banners (brand-anchored\n // identity) while still looking calmer than a focused interactive\n // surface (which uses `borderActive`).\n borderColor: COLOR.brand,\n paddingLeft: 1,\n paddingRight: 1,\n paddingTop: 0,\n paddingBottom: 0,\n marginTop,\n flexDirection: 'column',\n flexShrink: 0,\n alignSelf: 'stretch',\n }}\n >\n {events.map((evt, i) => (\n <EventLine\n key={i}\n event={evt}\n previous={events[i - 1]}\n depthOffset={1}\n selected={selectedTurnId !== null && evt.turnId === selectedTurnId}\n anchorId={anchorIds?.[i]}\n hideChildLabel={hideChildLabel}\n />\n ))}\n </box>\n )\n}\n\nfunction EmptyState() {\n const COLOR = useColors()\n return (\n <box style={{ flexGrow: 1, alignItems: 'center', justifyContent: 'center' }}>\n <text fg={COLOR.mute}>no messages yet — type below to start</text>\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EventLine — dispatches per kind. Subagent events render via indented dim\n// blocks; the `depth` field drives the left indent.\n// ---------------------------------------------------------------------------\n\n/** Left-pad applied per depth level (in columns). */\nconst INDENT_PER_DEPTH = 2\n\nfunction indentFor(depth: number | undefined): number {\n return depth && depth > 0 ? depth * INDENT_PER_DEPTH : 0\n}\n\nfunction isChild(event: StreamEvent): boolean {\n return (event.depth ?? 0) > 0\n}\n\n/**\n * Shared row geometry for every transcript event.\n *\n * `alignSelf: 'stretch'` + `flexShrink: 0` together pin each row to the\n * scrollbox's content width and prevent flex re-negotiation when neighboring\n * rows grow or shrink (streaming markdown, late-arriving tool results, etc.).\n * Without this, Yoga is free to re-compute widths every render and the\n * visible text appears to \"wiggle\" between columns as the stream advances.\n */\nfunction rowStyle(paddingLeft: number) {\n return {\n paddingLeft,\n flexDirection: 'column' as const,\n flexShrink: 0,\n alignSelf: 'stretch' as const,\n }\n}\n\n// `marginTopFor` now lives in `src/chat/store.ts`; see re-export below.\n\nfunction EventLineImpl({ event, depthOffset = 0, hideChildLabel = false, selected = false }: {\n event: StreamEvent\n depthOffset?: number\n hideChildLabel?: boolean\n /**\n * Whether the enclosing row carries the selection background. Inner\n * renderers that paint their own background (currently\n * {@link MarkdownBlock} for inline-code chips + the markdown body\n * itself) read this and swap to a selection-aware variant so the\n * highlight band stays visually continuous across the row.\n */\n selected?: boolean\n}) {\n const COLOR = useColors()\n const { settings } = useSettings()\n const safeText = event.text === '' ? ' ' : event.text\n const effectiveDepth = Math.max(0, (event.depth ?? 0) - depthOffset)\n const row = rowStyle(indentFor(effectiveDepth))\n\n // Subagent text is dimmed across the board so the visual hierarchy is\n // obvious even before reading the indent.\n const child = isChild(event)\n\n switch (event.kind) {\n case 'separator':\n return <text> </text>\n case 'user-prompt':\n return <UserPromptBlock text={safeText} refs={event.refs} attachments={event.attachments} />\n case 'info':\n return (\n <box style={row}>\n <text fg={COLOR.dim}>{safeText}</text>\n </box>\n )\n case 'thinking':\n return (\n <box style={row}>\n <text fg={COLOR.dim}>{safeText}</text>\n </box>\n )\n case 'tool':\n if (event.edit && settings.showEditDiffs) {\n return (\n <box style={row}>\n <EditDiffBlock payload={event.edit} dim={child} />\n </box>\n )\n }\n // `'hidden'` was already filtered out by isVisible upstream, so\n // by the time we render the dispatch reduces to `'formatted' | 'full'`.\n return (\n <box style={row}>\n <ToolCallBlock\n event={event}\n display={settings.toolCallDisplay === 'full' ? 'full' : 'formatted'}\n dim={child}\n />\n {/* Inline \"what's in progress\" affordance for `todowrite`. The\n tool-result line summarizes the tally; this sub-list spells\n out the LIVE work — the items the model has flagged as\n `in_progress` right now — so the user can read the\n checkpoint at a glance without opening the modal. Hidden\n for any `todowrite` whose payload has no in-progress\n items (e.g. all-completed close-out → list cleared). */}\n {event.tool === TODOWRITE_TOOL && <TodoInProgressList input={event.input} dim={child} />}\n </box>\n )\n case 'tool-result':\n return <ToolResultBlock text={event.text} indent={row.paddingLeft} />\n case 'error':\n return (\n <box style={row}>\n <text fg={COLOR.error}>\n <span fg={COLOR.error}>✗ </span>\n {safeText}\n </text>\n </box>\n )\n case 'markdown':\n return (\n <box style={row}>\n {/* eslint-disable-next-line ts/no-use-before-define -- forward-referenced memo component, evaluated at render time */}\n <MarkdownBlock text={event.text} dim={child} selected={selected} />\n </box>\n )\n case 'spawn-start':\n // `hideChildLabel` is set when the surrounding `SubagentBlock`\n // already shows `child-N` in its border title — repeating it on\n // every inner row reads as visual noise. We still keep the glyph\n // + body text so the row is meaningful in isolation (e.g. when\n // the user collapses or scrolls past the box title).\n return (\n <box style={row}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>🌱 </span>\n {!hideChildLabel && (\n <>\n <span fg={COLOR.brand}>{event.childId ?? 'child'}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n </>\n )}\n <span fg={COLOR.dim}>{safeText}</span>\n </text>\n </box>\n )\n case 'spawn-end':\n // Same shape as `spawn-start` — 2-cell emoji keeps the column\n // grid stable across a subagent's lifecycle, and the label is\n // hoisted to the box title in single-child runs.\n return (\n <box style={row}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>🌳 </span>\n {!hideChildLabel && (\n <>\n <span fg={COLOR.brand}>{event.childId ?? 'child'}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n </>\n )}\n <span fg={COLOR.mute}>{safeText}</span>\n </text>\n </box>\n )\n case 'compact-summary':\n // Boundary card — distinguishes itself visually from the regular\n // transcript so the user can tell at a glance \"the model only sees\n // history from this row down\". The summary body is rendered as\n // dimmed markdown so links / file refs still highlight; the meta\n // line above carries `n turns compacted · model · tokens`.\n return <CompactSummaryBlock event={event} indent={row.paddingLeft} />\n case 'task-notification':\n // One-line banner for background-task completion. Status-accented\n // glyph + command preview + status label + duration + clickable\n // (OSC 8) output path. Dedicated component so the structured\n // fields read cleanly — the underlying text block was already\n // suppressed from the generic user-prompt path in\n // `eventsFromTurns`'s replay synthesis.\n return <TaskNotificationBlock event={event} indent={row.paddingLeft} />\n default:\n return <text>{safeText}</text>\n }\n}\n\n/**\n * One-line completion banner for a background task that exited or was\n * killed. Visual contract:\n *\n * - Glyph color reflects exit cleanliness:\n * `'exited'` exit code 0 → success (subtle accent — quiet).\n * `'exited'` non-zero → warn (model / user should notice).\n * `'killed'` → warn (we issued SIGTERM).\n * - Task id reads in the agent's brand accent so the banner stands\n * apart from surrounding markdown without screaming.\n * - Status label takes the same accent as the glyph — color-coupling\n * reinforces the \"this is what happened\" mental model at a glance.\n * - Output path is `compactPath()`-formatted (`~/.zidane/...` when\n * under `$HOME`) so the column doesn't blow past terminal width\n * just to show the user's home prefix they already know about.\n *\n * Vertical spacing comes from `marginTopFor` — banners get a `tool`-like\n * gap before (and the next non-task event provides the gap after), but\n * consecutive banners stack tightly so a burst of completions doesn't\n * scatter the transcript.\n */\nfunction TaskNotificationBlock({\n event,\n indent,\n}: {\n event: StreamEvent\n indent: number | undefined\n}) {\n const COLOR = useColors()\n const task = event.task\n const isError = task ? (task.status === 'killed' || task.exitCode !== 0) : false\n // Glyph + status share an accent so the eye reads them as one unit.\n const statusAccent = isError ? COLOR.warn : COLOR.brand\n const statusLabel = task ? formatTaskStatus(task) : 'done'\n // Show the path as `~/...` whenever it lives under `$HOME` — the\n // task dir is always under there for TUI sessions\n // (`<userDir>/<sessionId>/tasks/`), so the full prefix is noise.\n const displayPath = task?.outputPath ? compactPath(task.outputPath) : ''\n\n return (\n <box style={{ paddingLeft: indent }}>\n <text wrapMode=\"none\">\n <span fg={statusAccent}>{'⌁ '}</span>\n <span fg={COLOR.brand}>{task?.taskId ?? '?'}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={statusAccent}>{statusLabel}</span>\n {task && task.durationMs > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.dim}>{formatDuration(task.durationMs)}</span>\n </>\n )}\n {displayPath && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.dim}>{displayPath}</span>\n </>\n )}\n </text>\n </box>\n )\n}\n\n/**\n * Boundary card for `compact-summary` events. Two visual rows:\n *\n * 1. Meta line — emoji + replaced-turn count + model + token usage,\n * dimmed so it reads as metadata rather than dialogue.\n * 2. Summary body — the LLM-generated summary text, indented under\n * the meta line. Rendered as plain text (not markdown) because the\n * summary follows the 9-section template and benefits more from\n * crisp wrap than from inline syntax highlighting.\n *\n * The card never collapses by default — the user explicitly asked for\n * this compaction, so the immediate post-compact view should show what\n * the model now sees.\n */\nfunction CompactSummaryBlock({\n event,\n indent,\n}: {\n event: StreamEvent\n indent: number | undefined\n}) {\n const COLOR = useColors()\n const meta = event.compact\n return (\n <box style={{ flexDirection: 'column', paddingLeft: indent }}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>⎯⎯ </span>\n <span fg={COLOR.warn}>{`${meta?.replacedCount ?? 0} turn${meta?.replacedCount === 1 ? '' : 's'} compacted`}</span>\n {meta && (\n <>\n <span fg={COLOR.mute}> · model </span>\n <span fg={COLOR.model}>{meta.model}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.dim}>{fmtTokens(meta.inputTokens + meta.cacheReadTokens + meta.cacheCreationTokens)}</span>\n <span fg={COLOR.mute}> in / </span>\n <span fg={COLOR.dim}>{fmtTokens(meta.outputTokens)}</span>\n <span fg={COLOR.mute}> out</span>\n </>\n )}\n </text>\n <text fg={COLOR.dim}>{event.text}</text>\n </box>\n )\n}\n\n/**\n * User prompt — bordered to rhyme with the prompt input box below.\n *\n * No refs → plain text, rendered as a single `<text>` node. Mixed text +\n * refs → flex-row of word-sized atomic segments; each chip is its own\n * `<text>` painted with the theme's per-provider `chips[providerId]`\n * pair (or `chips.default` when the provider id isn't themed).\n *\n * Why flex-row instead of `<span>` siblings inside `<text>`: the OpenTUI\n * text buffer's \"word\" wrap breaks at every punctuation boundary, so a\n * file path like `@src/index.ts` would split between line 1 (`@src/index.`)\n * and line 2 (`ts`) on a narrow terminal — and the chip's background\n * would visually fragment across the wrap. With flex-row + flexWrap, each\n * chip is one atomic flex item; the wrap engine never breaks inside it.\n */\n/** Prompt chevron rendered ahead of every user-prompt block. */\nconst USER_PROMPT_PREFIX = '❯ '\n\nfunction UserPromptBlock({\n text,\n refs,\n attachments,\n}: {\n text: string\n refs?: readonly { start: number, end: number, providerId: string }[]\n attachments?: readonly { name: string, mediaType: string, size: number }[]\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n\n const boxStyle = {\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n }\n\n const attachmentChips = attachments && attachments.length > 0\n ? (\n <box style={{ flexDirection: 'row', flexWrap: 'wrap', paddingTop: text.length > 0 ? 0 : 0 }}>\n {attachments.map((att, idx) => {\n const sz = att.size\n const label = sz < 1024\n ? `${sz}B`\n : sz < 1024 * 1024\n ? `${(sz / 1024).toFixed(1)}KB`\n : `${(sz / (1024 * 1024)).toFixed(1)}MB`\n const icon = att.mediaType.startsWith('image/') ? '🖼' : '📎'\n const chipColor = resolveChipColor(SURFACE.chips, 'file')\n return (\n <text key={`att-${idx}`}>\n {idx > 0 ? ' ' : ''}\n {icon}\n <span fg={chipColor.fg} bg={chipColor.bg}>\n {' '}\n {att.name}\n {' '}\n (\n {label}\n )\n {' '}\n </span>\n </text>\n )\n })}\n </box>\n )\n : null\n\n if (!refs || refs.length === 0) {\n return (\n <box style={boxStyle}>\n {text.length > 0 && (\n <text>\n <span fg={COLOR.brand}>{USER_PROMPT_PREFIX}</span>\n {text}\n </text>\n )}\n {!text.length && attachmentChips && (\n <text fg={COLOR.brand}>{USER_PROMPT_PREFIX}</text>\n )}\n {attachmentChips}\n </box>\n )\n }\n\n // Refs offsets are raw (no prefix shift). The chevron rides as a\n // separate leading flex item so wrap behavior stays clean: on a\n // narrow terminal the prompt text wraps under the chevron, chips\n // stay atomic, and ref offsets don't have to know about the prefix.\n const segments = splitPromptSegments(text, refs)\n return (\n <box style={boxStyle}>\n {/*\n Two boxes — outer = border + padding, inner = wrap container.\n Splitting lets the outer keep its bordered geometry while the\n inner does the row layout + flex-wrap work that keeps each chip\n atomic.\n */}\n <box style={{ flexDirection: 'row', flexWrap: 'wrap' }}>\n <text fg={COLOR.brand}>{USER_PROMPT_PREFIX}</text>\n {segments.map((seg, i) => {\n if (seg.kind === 'plain')\n return <text key={i}>{seg.text}</text>\n const chip = resolveChipColor(SURFACE.chips, seg.providerId)\n return <text key={i} fg={chip.fg} bg={chip.bg}>{seg.text}</text>\n })}\n </box>\n {attachmentChips}\n </box>\n )\n}\n\n/**\n * Markdown block. Renders both live-streaming markdown (while deltas are\n * still appending) and finalized markdown (after `turn:after`, or every\n * entry on a reloaded transcript) through a SINGLE stable `<markdown>`\n * element.\n *\n * Three knobs are intentionally pinned to constants:\n *\n * - `streaming={true}` — always. The OpenTUI setter for `streaming`\n * calls `updateBlocks(true)` (force table refresh) on every flip, so\n * a `true → false` transition at stream completion would rebuild\n * every block renderable in the markdown tree. Pinning the prop to\n * `true` leaves the trailing 2 blocks technically \"unstable\" forever,\n * but that costs nothing once deltas stop arriving — the parser\n * just never re-lexes anything because `content` doesn't change.\n *\n * - `internalBlockMode=\"coalesced\"` — always. The naming suggests this\n * mode merges adjacent prose into one big block (which would force a\n * full re-flow on every delta), but the merge step is gated on\n * `!this._renderNode` inside the markdown core, so the `renderNode`\n * callback below keeps the per-token block structure of top-level\n * mode WHILE getting coalesced's `updateBlockRenderable` update\n * path. That path mutates the existing renderable's `content` in\n * place instead of destroying it on every delta, which is what\n * unlocks the body's \"preserve last-styled buffer while async\n * tree-sitter resolves\" behaviour and kills the streaming blink on\n * the live block. There's no setter for this prop at runtime — it's\n * set-once at construction.\n *\n * - `renderNode` — wraps `code` tokens with a {@link CodeBlockWrapper}\n * to host the `[lang] … [copy]` header and reimplements\n * inter-block spacing (which coalesced + `renderNode` strips along\n * with the parser's space tokens). The resulting visual shape is\n * bit-identical to what top-level mode would produce in opencode-\n * style well-formatted markdown; see the block comment above\n * `makeMarkdownRenderNode` for details.\n *\n * `splitMarkdownCodeBlocks` (now in `zidane/chat`) exposes the\n * segmented prose / code shape for any non-transcript surface that\n * still wants to split fences out for custom rendering; the streaming\n * Transcript no longer uses it.\n */\n// ---------------------------------------------------------------------------\n// Fenced code blocks — lifted out of `<markdown>` so we can pin a copy\n// affordance to the header row. Selection-driven OSC 52 (mounted in\n// AppShell) already covers \"drag to copy\", but a fenced block is the\n// archetypal \"yank me whole\" thing — the header skips the precision-\n// dragging step entirely.\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Custom block renderers for OpenTUI's `<markdown renderNode>` hook.\n//\n// Two transcript-specific concerns are solved by intercepting block tokens\n// at the renderable layer:\n//\n// 1. **Streaming \"blink\" on the live block.** With `streaming: true`\n// OpenTUI's markdown parser marks the last two tokens as \"unstable\"\n// and re-lexes them on every content delta. In\n// `internalBlockMode: \"top-level\"`, the markdown handles those new\n// tokens by destroying the existing renderable and creating a\n// fresh one — and a freshly-constructed CodeRenderable\n// (`drawUnstyledText: false`, `streaming: true`, `filetype:\n// \"markdown\"`) paints NOTHING until the async tree-sitter highlight\n// resolves. On chunks where the parse misses the 16ms frame budget\n// the user sees a blank, then-styled flash that reads as the live\n// block \"blinking\". Opencode runs the exact same configuration and\n// has the same vulnerability; we just want the live block to stay\n// visibly stable.\n//\n// `internalBlockMode: \"coalesced\"` chooses a different update path:\n// same-type adjacent tokens UPDATE the existing renderable's\n// `content` in place instead of destroying it. The CodeRenderable's\n// content setter then takes its early-return path\n// (`streaming && !drawUnstyledText && filetype` are all true), which\n// preserves the previously-styled buffer on screen while the async\n// highlight computes new chunks. The eye registers a smooth\n// styled → styled transition. No blink, regardless of how long\n// tree-sitter takes.\n//\n// One side effect: coalesced mode normally MERGES adjacent prose\n// tokens into one big block (which would force a full re-flow on\n// every delta). The merge step is gated on `!this._renderNode`\n// inside the markdown core, so having a `renderNode` callback set\n// (even one that returns `undefined` for prose) is enough to keep\n// the per-token block structure of top-level mode while landing on\n// coalesced's smoother update path.\n//\n// 2. **`[copy]` button on fenced code.** Same `renderNode` callback\n// intercepts `code` tokens and parents the default body in a\n// {@link CodeBlockWrapper} BoxRenderable subclass that hosts a\n// one-row `[lang] … [copy]` header. Clicking the indicator\n// pushes the body to the OS clipboard via OSC 52. The wrapper\n// lives entirely inside OpenTUI — React sees ONE `<markdown>`\n// element across the whole stream lifecycle, so there's no\n// React-tree restructure at the streaming → finalize boundary\n// (that was the failure mode of an earlier \"split markdown into\n// React subtrees\" approach).\n//\n// Coalesced + `renderNode` strips the \"space\" tokens the markdown's\n// top-level pass would normally fold into `block.marginTop`. The fix is\n// a small spacing rule applied uniformly: every non-first block gets\n// `marginTop: 1`, and the `CodeBlockWrapper` pins its own\n// `marginBottom: 0` so a code fence followed by prose doesn't stack the\n// markdown's default `getInterBlockMargin(code) = 1` with the next\n// block's `marginTop`. In every common configuration (prose ↔ prose,\n// prose ↔ code, code ↔ code, all separated by `\\n\\n` in the source)\n// the resulting visual shape is bit-identical to what top-level mode\n// would have produced from the parser's space tokens — the rule simply\n// reimplements that pass at our layer.\n//\n// The callback is captured at `<markdown>` construction time (OpenTUI\n// doesn't expose a setter for `renderNode`), so it must close over\n// stable references. We feed it via a `ref` updated on every render so\n// live colour values drive newly-built renderables AND any\n// already-mounted {@link CodeBlockWrapper}s — the React component\n// registers each wrapper in `bag.wrappers` on construction and a\n// `useEffect` sweep re-paints their header chrome on every theme\n// switch. The wrapper's `destroy()` removes it from the set so the\n// sweep never touches a dead renderable.\n\n/**\n * Mutable state shared with the markdown's `renderNode` callback and\n * every {@link CodeBlockWrapper} mounted by it.\n *\n * `wrappers` is the registry the React component sweeps on every theme\n * change to re-paint already-mounted copy buttons / header chrome — see\n * {@link MarkdownBlock} for the `useEffect` that drives it. Wrappers add\n * themselves on construction and remove themselves in `destroy()`.\n */\ninterface RenderNodeBag {\n ctx: CliRenderer\n colors: ThemeColors\n surfaces: ThemeSurfaces\n wrappers: Set<CodeBlockWrapper>\n}\n\n/** Click-feedback delay before `[copied]` reverts to `[copy]`, in ms. */\nconst COPY_FEEDBACK_MS = 1200\n\n/**\n * BoxRenderable subclass that hosts a `[lang] … [copy]` header above\n * an inner {@link CodeRenderable} body. Returned from\n * {@link makeMarkdownRenderNode} for every `code` token so the affordance\n * lives entirely inside OpenTUI — React doesn't see the wrapper.\n *\n * The accessor pairs below mirror the CodeRenderable surface (`content`,\n * `filetype`, `syntaxStyle`, `fg`, `bg`, `conceal`, `streaming`) so\n * coalesced mode's `applyCodeBlockRenderable` — which writes those\n * properties on the wrapper for every delta — lands on the body where\n * it produces visible output.\n *\n * Two of those forwarders deliberately ignore the value the markdown\n * writes:\n *\n * - `drawUnstyledText` is pinned to `false` so the body's `content`\n * setter stays on the \"preserve last-styled buffer while async\n * highlight runs\" path. The markdown's `applyCodeBlockRenderable`\n * writes `!(streaming && concealCode)` here, which evaluates to\n * `true` for our defaults — that would put the body on the\n * synchronous `textBuffer.setText` path and reintroduce a per-delta\n * plain → styled colour swap.\n *\n * - `marginBottom` is pinned to `0`. The markdown writes\n * `getInterBlockMargin(code) = 1` here on every delta; combined with\n * the `marginTop: 1` `renderNode` applies to the next block, it\n * would stack into a two-row gap below every fence. Pinning to 0\n * makes the next block's `marginTop` the single source of truth for\n * inter-block spacing.\n */\nclass CodeBlockWrapper extends BoxRenderable {\n /** Live code body owned by this wrapper. Updates pass through here. */\n private readonly body: CodeRenderable\n /** Header chrome — re-themed on every {@link applyTheme} call. */\n private readonly header: BoxRenderable\n private readonly spacer: BoxRenderable\n private readonly langLabel: TextRenderable\n private readonly button: TextRenderable\n /** Bag we belong to. Used for live theme lookups and self-deregistration. */\n private readonly bag: RenderNodeBag\n /** Pending feedback-reset timer for the `[copied]` flash. */\n private feedbackTimer: ReturnType<typeof setTimeout> | null = null\n /** Whether the button is currently showing `[copied]`. */\n private copied = false\n /**\n * Latest body content, captured every time the markdown writes to\n * `content`. Read by the click handler so a copy click pushes the\n * up-to-date code (not whatever was there on first mount).\n */\n private latestContent: string\n\n constructor(ctx: CliRenderer, body: CodeRenderable, options: {\n lang: string\n bag: RenderNodeBag\n }) {\n const { colors, surfaces } = options.bag\n super(ctx, {\n flexDirection: 'column',\n flexShrink: 0,\n alignSelf: 'stretch',\n // Elevated \"code panel\" paint — same tier as modal overlays so the\n // fenced block reads as a distinct surface against the prose body.\n // Theme switches re-drive this via `applyTheme` (and pinned bg\n // setter on the body keeps it from being clobbered by markdown\n // delta passes — see class doc).\n backgroundColor: surfaces.modal,\n })\n this.body = body\n this.bag = options.bag\n this.latestContent = body.content\n\n // Outer margin lives on us, not on the body, so the header sits flush\n // with the code below it. Pin `drawUnstyledText` to false up front so\n // even the very first render uses the \"wait for highlight, keep prior\n // styled buffer\" path (see class doc). Pin `bg` here to the elevated\n // surface so every code row picks up the panel paint; the pinned\n // setter below keeps the markdown's per-delta `bg` writes from\n // clobbering it back to the prose surface.\n body.marginTop = 0\n body.marginBottom = 0\n body.drawUnstyledText = false\n body.bg = surfaces.modal\n\n this.header = new BoxRenderable(ctx, {\n flexDirection: 'row',\n height: 1,\n alignSelf: 'stretch',\n backgroundColor: surfaces.modal,\n })\n this.langLabel = new TextRenderable(ctx, {\n content: options.lang && options.lang.length > 0 ? options.lang : 'code',\n fg: colors.mute,\n selectable: false,\n })\n this.spacer = new BoxRenderable(ctx, {\n flexGrow: 1,\n backgroundColor: surfaces.modal,\n })\n this.button = new TextRenderable(ctx, {\n content: '[copy]',\n fg: colors.warn,\n selectable: false,\n })\n this.button.onMouseDown = (event) => {\n event.stopPropagation()\n event.preventDefault()\n if (!writeToClipboard(this.latestContent))\n return\n // Always read the LATEST colours from the bag — a theme switch\n // mid-click would otherwise leave the button painted with the\n // construction-time accent / warn.\n const live = this.bag.colors\n if (!this.copied) {\n this.copied = true\n this.button.content = '[copied]'\n this.button.fg = live.accent\n }\n if (this.feedbackTimer)\n clearTimeout(this.feedbackTimer)\n this.feedbackTimer = setTimeout(() => {\n this.copied = false\n this.feedbackTimer = null\n if (this.button.isDestroyed)\n return\n this.button.content = '[copy]'\n this.button.fg = this.bag.colors.warn\n }, COPY_FEEDBACK_MS)\n }\n\n this.header.add(this.langLabel)\n this.header.add(this.spacer)\n this.header.add(this.button)\n this.add(this.header)\n this.add(this.body)\n\n // Register so {@link MarkdownBlock}'s theme effect can find us.\n options.bag.wrappers.add(this)\n }\n\n /**\n * Re-paint header chrome with the current theme. Called from\n * {@link MarkdownBlock}'s `useEffect` after every theme switch — the\n * body's `fg`/`bg` are already re-driven by the markdown's own\n * `rerenderBlocks` pass through our forwarding accessors, so we only\n * need to refresh the bits the markdown doesn't manage: the wrapper's\n * own background, the header row, the spacer, the lang label, and the\n * `[copy]`/`[copied]` button colour (which depends on `this.copied`).\n */\n applyTheme(colors: ThemeColors, surfaces: ThemeSurfaces): void {\n this.backgroundColor = surfaces.modal\n this.header.backgroundColor = surfaces.modal\n this.spacer.backgroundColor = surfaces.modal\n // Refresh the body's per-line bg so the panel paint follows a theme\n // switch even though the pinned setter below ignores delta-time\n // writes from the markdown.\n this.body.bg = surfaces.modal\n this.langLabel.fg = colors.mute\n this.button.fg = this.copied ? colors.accent : colors.warn\n }\n\n // Forwarding accessors — the markdown's `applyCodeBlockRenderable`\n // writes to these by name on whatever `state.renderable` ended up\n // being. The setters proxy to the body so updates land where they\n // actually produce visible output; the getters are paired for ESLint's\n // accessor-pairs rule and for any future reader that wants to inspect\n // current state without reaching into `.body`.\n\n get content(): string {\n return this.body.content\n }\n\n set content(value: string) {\n this.latestContent = value\n this.body.content = value\n }\n\n get filetype(): string | undefined {\n return this.body.filetype\n }\n\n set filetype(value: string | undefined) {\n this.body.filetype = value\n }\n\n get syntaxStyle(): CodeRenderable['syntaxStyle'] {\n return this.body.syntaxStyle\n }\n\n set syntaxStyle(value: ConstructorParameters<typeof CodeRenderable>[1]['syntaxStyle']) {\n this.body.syntaxStyle = value\n }\n\n get fg(): CodeRenderable['fg'] {\n return this.body.fg\n }\n\n set fg(value: ConstructorParameters<typeof CodeRenderable>[1]['fg']) {\n this.body.fg = value\n }\n\n /**\n * Always reports the live elevated-panel paint: see the setter doc.\n */\n get bg(): CodeRenderable['bg'] {\n return this.body.bg\n }\n\n /**\n * Pinned to {@link ThemeSurfaces.modal} — the elevated code-panel\n * surface. The markdown's `applyCodeBlockRenderable` writes `bg` on\n * every delta from whatever surface the parent `<markdown>` carries\n * (the prose body paint). Forwarding that through would flip the\n * code panel back to the prose surface on every keystroke and erase\n * the visual lift. Theme switches are funnelled through\n * {@link applyTheme} instead, which rewrites `body.bg` directly.\n */\n set bg(_value: ConstructorParameters<typeof CodeRenderable>[1]['bg']) {\n this.body.bg = this.bag.surfaces.modal\n }\n\n get conceal(): boolean {\n return this.body.conceal\n }\n\n set conceal(value: boolean) {\n this.body.conceal = value\n }\n\n get streaming(): boolean {\n return this.body.streaming\n }\n\n set streaming(value: boolean) {\n this.body.streaming = value\n }\n\n /**\n * Always reports `false`: see the setter doc.\n */\n get drawUnstyledText(): boolean {\n return false\n }\n\n /**\n * Pinned to `false` regardless of what the markdown writes. See the\n * class doc — leaving this open to the markdown's default would put\n * the body's `content` setter on the synchronous-plain-text path on\n * every delta, replacing the styled buffer with raw text for one\n * frame and producing a visible colour swap.\n */\n set drawUnstyledText(_value: boolean) {\n if (this.body.drawUnstyledText !== false)\n this.body.drawUnstyledText = false\n }\n\n /**\n * Always reports `0`: see the setter doc.\n */\n override get marginBottom(): number {\n return 0\n }\n\n /**\n * Pinned to `0`. The next block's `marginTop` (which `renderNode`\n * sets to `1` on every non-first block) is the single source of\n * inter-block spacing. Letting the markdown also write\n * `getInterBlockMargin(code) = 1` here on every delta would stack to\n * a two-row gap below every fence.\n */\n override set marginBottom(_value: number | 'auto' | `${number}%` | null | undefined) {\n // no-op\n }\n\n override destroy(): void {\n this.bag.wrappers.delete(this)\n if (this.feedbackTimer) {\n clearTimeout(this.feedbackTimer)\n this.feedbackTimer = null\n }\n super.destroy()\n }\n}\n\n/**\n * Block-index regex applied to OpenTUI's auto-generated renderable ids\n * (`${markdown.id}-block-${index}`). The trailing digits give us the\n * block's position inside the markdown's child list, which is the only\n * piece of context we need to gate the \"non-first block → marginTop: 1\"\n * spacing rule.\n */\nconst BLOCK_ID_INDEX_RE = /-block-(\\d+)$/\n\n/**\n * Build a stable `renderNode` callback wired to a live `RenderNodeBag`.\n *\n * Returns:\n * - `code` tokens → {@link CodeBlockWrapper} (hosts the `[lang] …\n * [copy]` header).\n * - everything else (prose, heading, list, table, …) → the renderable\n * we got from `context.defaultRender()`, mutated in place.\n *\n * Critical: we MUST return the renderable we mutated, not `undefined`.\n * OpenTUI's coalesced-mode loop, when `renderNode` returns a falsy\n * value, creates a fresh default renderable (`markdown.ts ~8851`) — so\n * anything we mutated via `defaultRender()` is thrown away.\n *\n * Tables: returning the default `TextTableRenderable` looks like it\n * should orphan the markdown's `tableContentCache` tuple (incremental\n * table updates depend on it), but the markdown has a follow-up branch\n * (`markdown.ts ~8854`) that re-builds the cache from the token when\n * `renderNode` returned a `TextTableRenderable` without one. So we can\n * safely mutate + return the table renderable too, and the spacing\n * rule applies uniformly.\n *\n * Every non-first block gets `marginTop: 1`. This reimplements the\n * inter-block spacing that top-level mode would compute from the\n * parser's space tokens — coalesced mode + `renderNode` strips them\n * (see the file-level comment), so we recompute the spacing ourselves.\n * The result is bit-identical to top-level mode for every\n * well-formatted markdown source.\n */\nfunction makeMarkdownRenderNode(bag: MutableRefObject<RenderNodeBag>) {\n return (token: Token, context: RenderNodeContext) => {\n const inner = context.defaultRender()\n if (!inner)\n return undefined\n\n const isFirstBlock = parseBlockIndex(inner.id) === 0\n const topMargin = isFirstBlock ? 0 : 1\n\n if (token.type === 'code' && inner instanceof CodeRenderable) {\n // Code fence: wrap with our `[lang] … [copy]` header.\n const lang = typeof (token as { lang?: unknown }).lang === 'string'\n ? (token as { lang: string }).lang\n : ''\n const wrapper = new CodeBlockWrapper(bag.current.ctx, inner, { lang, bag: bag.current })\n wrapper.marginTop = topMargin\n return wrapper\n }\n\n // Prose, heading, list, table, etc. — mutate the default renderable's\n // top margin and return it. The markdown's downstream logic\n // (`applyMarkdownCodeRenderable` / `applyCodeBlockRenderable` /\n // table cache build) handles whatever specific renderable type we\n // return without further intervention.\n inner.marginTop = topMargin\n return inner\n }\n}\n\n/**\n * Pull the trailing block index out of an OpenTUI renderable id. Returns\n * `0` for any id that doesn't match the expected `…-block-N` shape — the\n * spacing rule then treats unparseable ids as \"first block\" (no\n * `marginTop`), which is the safer side of the rendering edge case.\n */\nfunction parseBlockIndex(id: string | undefined): number {\n if (!id)\n return 0\n const match = BLOCK_ID_INDEX_RE.exec(id)\n if (!match)\n return 0\n const parsed = Number.parseInt(match[1] ?? '', 10)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nconst MarkdownBlock = memo(({ text, dim, selected = false }: {\n text: string\n dim: boolean\n /**\n * Whether the enclosing row is painted with the selection surface.\n * Switches the `syntaxStyle` to a variant whose inline-code chips\n * (`markup.raw`) and fenced code blocks (`markup.raw.block`) carry\n * the selection bg — without this swap they keep their resting\n * `bg` and read as \"punched out\" rectangles against the highlight.\n */\n selected?: boolean\n}) => {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const mdStyle = useMdStyle({ selected })\n const renderer = useRenderer()\n\n // Strip leading / trailing newlines. The model commonly emits text\n // with `\\n\\n` paragraph breaks at the boundaries between tool calls\n // and narration; the markdown renderable paints those as phantom\n // blank rows above and below the content, producing the visible\n // \"huge gap between tool calls and the next text\" complaint. Inner\n // newlines stay — they ARE the paragraph breaks the model intended.\n // `String.trim` would also drop leading whitespace inside the first\n // line (indented code blocks, etc.) so we trim only newlines.\n const content = text.replace(/^\\n+|\\n+$/g, '')\n\n // Live values for the renderNode callback. The callback identity must\n // stay stable across renders (OpenTUI captures it at <markdown>\n // construction time and never re-reads), so we route the current values\n // through a ref and let the callback read `bag.current.*` lazily. The\n // `wrappers` set tracks every live {@link CodeBlockWrapper} so the theme\n // effect below can re-paint their non-body chrome on a theme switch.\n const bag = useRef<RenderNodeBag>({\n ctx: renderer,\n colors: COLOR,\n surfaces: SURFACE,\n wrappers: new Set(),\n })\n bag.current.ctx = renderer\n bag.current.colors = COLOR\n bag.current.surfaces = SURFACE\n\n // Re-paint live copy-button chrome on theme switch. The markdown's own\n // `rerenderBlocks` pass refreshes the body colours through our\n // forwarding accessors (`bg`, `fg`); the wrapper's surrounding\n // `BoxRenderable`s + `[lang]` / `[copy]` `TextRenderable`s aren't part\n // of that pass, so we sweep the registry ourselves.\n useEffect(() => {\n for (const wrapper of bag.current.wrappers)\n wrapper.applyTheme(COLOR, SURFACE)\n }, [COLOR, SURFACE])\n\n // Stable for the lifetime of this instance (see comment above).\n const renderNode = useMemo(() => makeMarkdownRenderNode(bag), [])\n\n return (\n <markdown\n content={content}\n syntaxStyle={mdStyle}\n // Pinned constants — see the JSDoc on this component.\n streaming={true}\n internalBlockMode=\"coalesced\"\n fg={dim ? COLOR.dim : undefined}\n // Solid background paints over the prior frame's cells in one\n // pass. Without it the renderer leaves cells transparent and\n // partial repaints can briefly show torn text underneath a\n // re-flowing trailing block. Tracks the row's selection state\n // so the markdown bg merges into the highlight (the inline-code\n // chips also flip in `useMdStyle({ selected })` above — paint\n // and chip both flow into one continuous selection band).\n bg={selected ? SURFACE.selection : SURFACE.background}\n renderNode={renderNode}\n />\n )\n})\nMarkdownBlock.displayName = 'MarkdownBlock'\n\n// ---------------------------------------------------------------------------\n// Tool result — left-bar block, truncated. Caps at TOOL_RESULT_MAX_LINES so a\n// 200-line file doesn't drown the transcript.\n// ---------------------------------------------------------------------------\n\nconst TOOL_RESULT_MAX_LINES = 6\n\nfunction ToolResultBlock({ text, indent }: { text: string, indent: number }) {\n const COLOR = useColors()\n // Trim trailing whitespace-only lines so a result text ending in\n // `\\n\\n` (common for tools that print a newline after their last\n // row) doesn't paint phantom `┃ ` rows between consecutive tool\n // calls — at narrow widths those phantom rows read as a giant\n // unexplained vertical gap. Leading / interior blanks stay intact\n // so structured output (e.g. paragraphs in a markdown result) keeps\n // its shape.\n const rawLines = text.split('\\n')\n let end = rawLines.length\n while (end > 0 && rawLines[end - 1]!.trim() === '')\n end--\n if (end === 0)\n return null\n const lines = rawLines.slice(0, end)\n const visible = lines.slice(0, TOOL_RESULT_MAX_LINES)\n const omitted = Math.max(0, lines.length - TOOL_RESULT_MAX_LINES)\n\n return (\n <box style={{ paddingLeft: indent, flexDirection: 'column' }}>\n {visible.map((line, i) => (\n <text key={i} fg={COLOR.mute}>\n <span fg={COLOR.borderActive}>┃ </span>\n {line || ' '}\n </text>\n ))}\n {omitted > 0 && (\n <text fg={COLOR.mute}>\n <span fg={COLOR.borderActive}>┃ </span>\n {`… ${omitted} more line${omitted === 1 ? '' : 's'}`}\n </text>\n )}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Edit diff — native <diff> renderable wrapper.\n//\n// OpenTUI ships a `DiffRenderable` that parses unified diff syntax and\n// renders it with full per-language syntax highlighting (via tree-sitter,\n// same client the markdown renderer uses for fenced blocks), bg row\n// coloring, and word/char/none wrap. We just serialize our `EditPayload`\n// to a unified diff string and pass it through; the renderable does the\n// rest.\n//\n// Header rides the same `↳ <tool> <path>` shape as a normal tool line.\n// `replace_all` and multi-hunk badges live in the meta tail of the\n// header so the renderable below stays clean.\n// ---------------------------------------------------------------------------\n\nfunction EditDiffBlock({ payload, dim }: { payload: EditPayload, dim: boolean }) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const mdStyle = useMdStyle()\n const { settings } = useSettings()\n const filetype = useMemo(() => filetypeFromPath(payload.path), [payload.path])\n\n // Badge for replace_all (edit/multi_edit only). Surfaces in the\n // header meta so the diff body itself stays clean of annotation\n // lines. Multi-edit with multiple hunks gets a count too.\n const replaceAllCount = payload.hunks.filter(h => h.replaceAll).length\n const hunkBadge = payload.tool === 'multi_edit' && payload.hunks.length > 1\n ? `${payload.hunks.length} hunks`\n : null\n\n const outcomeSummary = summarizeOutcomes(payload.outcomes)\n const hasMixedOutcomes = !!payload.outcomes && (outcomeSummary.denied + outcomeSummary.skipped + outcomeSummary.failed) > 0\n\n // Pre-compute total +/− stats once for the header. Cheap enough to\n // always show — the user gets a glance signal of how big the change\n // is regardless of the density setting below.\n const editSummary = useMemo(() => summarizeEditPayload(payload), [payload])\n\n // Per-hunk rendering — used whenever outcomes are present (live OR\n // replay). Splits the call into one diff per hunk with a status badge\n // above each so denied / skipped / failed hunks read as discrete units\n // instead of being silently dropped from a combined diff body.\n const perHunkMode = hasMixedOutcomes\n const compact = settings.editDiffDisplay === 'compact'\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, alignSelf: 'stretch' }}>\n <text fg={dim ? COLOR.dim : COLOR.model}>\n <span fg={COLOR.mute}>↳ </span>\n <span fg={dim ? COLOR.dim : COLOR.model}>{displayNameFor(payload.tool)}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={dim ? COLOR.dim : COLOR.warn}>{payload.path}</span>\n {(editSummary.totalAdded > 0 || editSummary.totalRemoved > 0) && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n {editSummary.totalAdded > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{`+${editSummary.totalAdded}`}</span>\n )}\n {editSummary.totalAdded > 0 && editSummary.totalRemoved > 0 && (\n <span fg={COLOR.mute}>{' '}</span>\n )}\n {editSummary.totalRemoved > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{`−${editSummary.totalRemoved}`}</span>\n )}\n </>\n )}\n {hunkBadge && <span fg={COLOR.mute}>{` · ${hunkBadge}`}</span>}\n {replaceAllCount > 0 && (\n <span fg={COLOR.mute}>{` · ${replaceAllCount > 1 ? `${replaceAllCount}× ` : ''}replace all`}</span>\n )}\n {payload.outcomes && payload.outcomes.length > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={outcomeSummary.applied > 0 ? COLOR.accent : COLOR.mute}>{`${outcomeSummary.applied} applied`}</span>\n {outcomeSummary.denied > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.error}>{`${outcomeSummary.denied} denied`}</span>\n </>\n )}\n {outcomeSummary.skipped > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.warn}>{`${outcomeSummary.skipped} skipped`}</span>\n </>\n )}\n {outcomeSummary.failed > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.error}>{`${outcomeSummary.failed} failed`}</span>\n </>\n )}\n </>\n )}\n </text>\n {compact\n ? <CompactDiffSummary summary={editSummary} dim={dim} />\n : perHunkMode\n ? (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {payload.hunks.map((hunk, i) => (\n <HunkBlock\n key={i}\n index={i}\n hunk={hunk}\n outcome={payload.outcomes?.[i]}\n tool={payload.tool}\n path={payload.path}\n filetype={filetype}\n mdStyle={mdStyle}\n COLOR={COLOR}\n SURFACE={SURFACE}\n dim={dim}\n />\n ))}\n </box>\n )\n : (\n <diff\n diff={payload.priorContent !== undefined\n ? buildContextualDiff(payload, payload.priorContent)\n : buildUnifiedDiff(payload)}\n view=\"unified\"\n wrapMode=\"word\"\n showLineNumbers={true}\n {...(filetype ? { filetype } : {})}\n syntaxStyle={mdStyle}\n addedBg={SURFACE.diff.addBg}\n removedBg={SURFACE.diff.removeBg}\n {...(SURFACE.diff.addContentBg ? { addedContentBg: SURFACE.diff.addContentBg } : {})}\n {...(SURFACE.diff.removeContentBg ? { removedContentBg: SURFACE.diff.removeContentBg } : {})}\n {...(SURFACE.diff.contextBg ? { contextBg: SURFACE.diff.contextBg } : {})}\n addedSignColor={SURFACE.diff.addFg}\n removedSignColor={SURFACE.diff.removeFg}\n treeSitterClient={getTreeSitterClient()}\n {...(dim ? { fg: COLOR.dim } : {})}\n />\n )}\n </box>\n )\n}\n\n/**\n * Compact-mode body — one line per hunk under the tool header. Format:\n *\n * ` L42 · +2 −1 · old → new`\n *\n * Where `L<n>` is the new-file line position (omitted when priorContent\n * is absent and the position is unknown), and the `old → new` preview\n * is the FIRST changed line on each side, ASCII-arrowed and truncated\n * by the renderer's own word-wrap. A pure addition shows `+ new` only;\n * a pure deletion shows `− old` only.\n */\nfunction CompactDiffSummary({\n summary,\n dim,\n}: {\n summary: ReturnType<typeof summarizeEditPayload>\n dim: boolean\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n if (summary.hunks.length === 0)\n return null\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {summary.hunks.map((h, i) => {\n const oldPreview = h.firstOld?.trim()\n const newPreview = h.firstNew?.trim()\n return (\n <text key={i} fg={dim ? COLOR.dim : COLOR.mute} wrapMode=\"word\">\n <span fg={COLOR.mute}>{' '}</span>\n {h.line !== undefined && (\n <>\n <span fg={dim ? COLOR.dim : COLOR.mute}>{`L${h.line}`}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n </>\n )}\n {h.added > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{`+${h.added}`}</span>\n )}\n {h.added > 0 && h.removed > 0 && <span fg={COLOR.mute}>{' '}</span>}\n {h.removed > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{`−${h.removed}`}</span>\n )}\n {(oldPreview || newPreview) && <span fg={COLOR.mute}>{' · '}</span>}\n {oldPreview && newPreview\n ? (\n <>\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{oldPreview}</span>\n <span fg={COLOR.mute}>{' → '}</span>\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{newPreview}</span>\n </>\n )\n : oldPreview\n ? (\n <>\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{'− '}</span>\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{oldPreview}</span>\n </>\n )\n : newPreview\n ? (\n <>\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{'+ '}</span>\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{newPreview}</span>\n </>\n )\n : null}\n </text>\n )\n })}\n </box>\n )\n}\n\n/**\n * One hunk inside an `EditDiffBlock` rendered as its own mini-diff with\n * a status badge above it. Used only in the per-hunk view (multi_edit\n * with mixed outcomes) so denied / skipped / failed edits remain\n * visible alongside the applied ones.\n */\n// Pin every status badge to one column width so they read as a clean\n// gutter when multiple hunks stack. `skipped` is the longest at 7\n// chars; everything else gets right-padded to match.\nconst BADGE_WIDTH = 7\n\nfunction HunkBlock({\n index,\n hunk,\n outcome,\n tool,\n path,\n filetype,\n mdStyle,\n COLOR,\n SURFACE,\n dim,\n}: {\n index: number\n hunk: EditPayload['hunks'][number]\n outcome: EditOutcome | undefined\n tool: EditPayload['tool']\n path: string\n filetype: string | undefined\n mdStyle: ReturnType<typeof useMdStyle>\n COLOR: ReturnType<typeof useColors>\n SURFACE: ReturnType<typeof useSurfaces>\n dim: boolean\n}) {\n const { settings } = useSettings()\n const kind = outcome?.kind ?? 'applied'\n const reason = outcome?.reason\n const dimmed = dim || kind !== 'applied'\n const compact = settings.editDiffDisplay === 'compact'\n\n // Per-hunk diff body — one mini unified diff. We build a stand-alone\n // EditPayload around this single hunk so `buildUnifiedDiff` produces a\n // valid `--- / +++ / @@` header even for denied / skipped hunks\n // (where the file content was never actually modified). Line numbers\n // here are synthetic within the snippet (start at 1) — we can't anchor\n // an unapplied hunk to a real file position.\n const diffText = useMemo(() => buildUnifiedDiff({\n tool,\n path,\n hunks: [hunk],\n }), [hunk, tool, path])\n\n // One-liner stats + first-line preview shown alongside the badge in\n // compact mode AND as a small caption in full mode. Same shape as\n // `CompactDiffSummary` so the two modes read consistently.\n const hunkStats = useMemo(\n () => summarizeEditPayload({ tool, path, hunks: [hunk] }).hunks[0],\n [hunk, tool, path],\n )\n\n const badge = kind === 'applied'\n ? { label: 'applied', fg: COLOR.accent }\n : kind === 'denied'\n ? { label: 'denied', fg: COLOR.error }\n : kind === 'skipped'\n ? { label: 'skipped', fg: COLOR.warn }\n : kind === 'failed'\n ? { label: 'failed', fg: COLOR.error }\n : { label: 'pending', fg: COLOR.mute }\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, marginTop: index === 0 ? 0 : 1 }}>\n <text fg={COLOR.mute} wrapMode=\"word\">\n <span fg={COLOR.mute}>{` #${(index + 1).toString().padStart(2)} `}</span>\n <span fg={badge.fg}>{badge.label.padEnd(BADGE_WIDTH)}</span>\n {hunkStats && (hunkStats.added > 0 || hunkStats.removed > 0) && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n {hunkStats.added > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{`+${hunkStats.added}`}</span>\n )}\n {hunkStats.added > 0 && hunkStats.removed > 0 && <span fg={COLOR.mute}>{' '}</span>}\n {hunkStats.removed > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{`−${hunkStats.removed}`}</span>\n )}\n </>\n )}\n {reason && (\n <>\n <span fg={COLOR.mute}>{': '}</span>\n <span fg={COLOR.dim}>{reason}</span>\n </>\n )}\n </text>\n {!compact && (\n <diff\n diff={diffText}\n view=\"unified\"\n wrapMode=\"word\"\n showLineNumbers={true}\n {...(filetype ? { filetype } : {})}\n syntaxStyle={mdStyle}\n addedBg={SURFACE.diff.addBg}\n removedBg={SURFACE.diff.removeBg}\n {...(SURFACE.diff.addContentBg ? { addedContentBg: SURFACE.diff.addContentBg } : {})}\n {...(SURFACE.diff.removeContentBg ? { removedContentBg: SURFACE.diff.removeContentBg } : {})}\n {...(SURFACE.diff.contextBg ? { contextBg: SURFACE.diff.contextBg } : {})}\n addedSignColor={SURFACE.diff.addFg}\n removedSignColor={SURFACE.diff.removeFg}\n treeSitterClient={getTreeSitterClient()}\n {...(dimmed ? { fg: COLOR.dim } : {})}\n />\n )}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Tool call — three display modes:\n//\n// - `'formatted'` (default) — per-tool curated one-liner. Uses the\n// {@link formatToolCall} registry; native tools get a clean\n// `↳ <Verb> <target> · <meta>` line, MCP / host tools fall back\n// to a `↳ <Title Case>` line with no args.\n// - `'full'` — debug view: `↳ <name>` header + the entire raw\n// `input` rendered as syntax-highlighted JSON via the native\n// `<code>` renderable. Useful for inspecting exactly what the\n// model emitted.\n// - `'hidden'` — gated upstream by `isVisible`, never reaches\n// this component.\n//\n// `event.text` carries the legacy `name({json})` preview as a final\n// fallback for `'formatted'` when neither a curated formatter nor\n// structured input is available (e.g. session reloaded from disk\n// without an input record — defensive guard).\n// ---------------------------------------------------------------------------\n\nconst FULL_VIEW_MAX_INPUT_BYTES = 32 * 1024\n\nfunction ToolCallBlock({\n event,\n display,\n dim,\n}: {\n event: StreamEvent\n display: 'formatted' | 'full'\n dim: boolean\n}) {\n const COLOR = useColors()\n const mdStyle = useMdStyle()\n const name = event.tool ?? ''\n const verb = displayNameFor(name, event.input)\n // Hooks must run unconditionally — both `pretty` (for `'full'`) and\n // `line` (for `'formatted'`) memo on `event.input` so the branch\n // below is a pure data-conditional, not a hooks-conditional.\n const pretty = useMemo(() => {\n if (!event.input)\n return null\n try {\n const text = JSON.stringify(event.input, null, 2)\n return text.length > FULL_VIEW_MAX_INPUT_BYTES\n ? `${text.slice(0, FULL_VIEW_MAX_INPUT_BYTES)}\\n…`\n : text\n }\n catch {\n return null\n }\n }, [event.input])\n const line = useMemo(\n () => (event.input ? formatToolCall(name, event.input) : null),\n [event.input, name],\n )\n\n if (display === 'full') {\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, alignSelf: 'stretch' }}>\n <text fg={dim ? COLOR.dim : COLOR.model}>\n <span fg={COLOR.mute}>↳ </span>\n <span fg={dim ? COLOR.dim : COLOR.model}>{verb}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.mute}>{name}</span>\n </text>\n {pretty\n ? (\n <code\n content={pretty}\n filetype=\"json\"\n syntaxStyle={mdStyle}\n {...(dim ? { fg: COLOR.dim } : {})}\n />\n )\n : (\n <text fg={COLOR.mute}>{' (no structured input)'}</text>\n )}\n </box>\n )\n }\n\n return (\n <text fg={dim ? COLOR.dim : COLOR.model}>\n <span fg={COLOR.mute}>↳ </span>\n <span fg={dim ? COLOR.dim : COLOR.model}>{verb}</span>\n {line?.target && (\n <>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={dim ? COLOR.dim : COLOR.warn}>{line.target}</span>\n </>\n )}\n {line?.meta && line.meta.length > 0 && (\n <span fg={COLOR.mute}>{` · ${line.meta.join(' · ')}`}</span>\n )}\n </text>\n )\n}\n\n// ---------------------------------------------------------------------------\n// TodoInProgressList — inline \"what's in progress\" affordance painted\n// directly under a `todowrite` tool call. Reads the call's `input.todos`\n// (the model's checkpoint payload) and renders one indented row per\n// `in_progress` item. Other statuses are out of scope — the existing\n// tool-result tally already reports them and the modal is one keystroke\n// away for the full picture.\n//\n// Rendered as a sibling of `ToolCallBlock`, NOT a child, so it inherits\n// the `tool` row's top margin and contributes its own bottom rhythm.\n// Empty payload (or no in-progress items) collapses to `null` so the\n// transcript stays tight when there's nothing live.\n// ---------------------------------------------------------------------------\n\nfunction TodoInProgressList({\n input,\n dim,\n}: {\n input: Record<string, unknown> | undefined\n dim: boolean\n}) {\n const COLOR = useColors()\n const items = useMemo(() => {\n const raw = (input as { todos?: unknown } | undefined)?.todos\n if (!Array.isArray(raw))\n return [] as { id: string, content: string }[]\n // Defensive parse — the renderer never trusts the model's payload\n // shape, only its own type checks. Anything that doesn't look like\n // `{ id, content, status: 'in_progress' }` is silently dropped.\n return raw.flatMap((t) => {\n if (t == null || typeof t !== 'object')\n return []\n const rec = t as Record<string, unknown>\n if (rec.status !== 'in_progress')\n return []\n const id = typeof rec.id === 'string' ? rec.id : ''\n const content = typeof rec.content === 'string' ? rec.content : ''\n if (!id || !content)\n return []\n return [{ id, content }]\n })\n }, [input])\n if (items.length === 0)\n return null\n const glyph = TODO_STATUS_GLYPHS.in_progress\n return (\n <box style={{ flexDirection: 'column', marginLeft: 2 }}>\n {items.map(item => (\n <text key={item.id} wrapMode=\"none\">\n <span fg={COLOR.mute}>{`${glyph} `}</span>\n <span fg={dim ? COLOR.dim : COLOR.warn}>{item.content}</span>\n </text>\n ))}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Back-compat re-exports\n//\n// These symbols used to live in this module and have since moved into\n// `zidane/chat` so a GUI shell can consume them without pulling OpenTUI.\n// The re-exports here preserve the existing `zidane/tui` import surface\n// for one minor release; new code should import directly from\n// `zidane/chat` (or `zidane/chat/<module>` where applicable).\n//\n// @deprecated These re-exports will be removed in the next minor.\n// ---------------------------------------------------------------------------\n\n/** @deprecated Import from `zidane/chat` (`../chat/hints`). */\nexport type { Hint } from '../chat/hints'\n/** @deprecated Import from `zidane/chat` (`../chat/hints`). */\nexport { clipHintsToWidth, hintsLength, truncateTrailing } from '../chat/hints'\n/** @deprecated Import from `zidane/chat` (`../chat/markdown-segments`). */\nexport type { MarkdownSegment } from '../chat/markdown-segments'\n/** @deprecated Import from `zidane/chat` (`../chat/markdown-segments`). */\nexport { splitMarkdownCodeBlocks } from '../chat/markdown-segments'\n/** @deprecated Import from `zidane/chat` (`../chat/store`). */\nexport { isEditErrorResult, isVisible, marginTopFor } from '../chat/store'\n/** @deprecated Import from `zidane/chat` (`../chat/tool-formatters`). */\nexport { displayNameFor, formatToolCall, TOOL_DISPLAY } from '../chat/tool-formatters'\n/** @deprecated Import from `zidane/chat` (`../chat/tool-formatters`). */\nexport type { ToolDisplayMeta, ToolFormatLine } from '../chat/tool-formatters'\n/** @deprecated Import from `zidane/chat` (`../chat/transcript-anchors`). */\nexport type { TranscriptItem } from '../chat/transcript-anchors'\n/** @deprecated Import from `zidane/chat` (`../chat/transcript-anchors`). */\nexport { computeTurnAnchors } from '../chat/transcript-anchors'\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable } from '@opentui/core'\nimport type { FzfResultItem } from 'fzf'\nimport { readdirSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, relative } from 'node:path'\nimport { useKeyboard } from '@opentui/react'\nimport { byLengthAsc, Fzf } from 'fzf'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\nconst VISIBLE_ROWS = 12\nconst HOME = homedir()\nconst MAX_ENTRIES = 50_000\nconst MAX_DEPTH = 5\n\nconst SKIP_DIRS = new Set([\n 'node_modules',\n '.git',\n '.hg',\n '.svn',\n '__pycache__',\n '.cache',\n '.npm',\n '.yarn',\n 'dist',\n 'build',\n '.next',\n '.nuxt',\n 'coverage',\n '.venv',\n 'venv',\n '.tox',\n 'vendor',\n 'target',\n '.gradle',\n '.idea',\n '.vscode',\n])\n\ninterface DirEntry {\n rel: string\n path: string\n}\n\nfunction walkDirs(base: string): DirEntry[] {\n const results: DirEntry[] = []\n const queue: { dir: string, depth: number }[] = [{ dir: base, depth: 0 }]\n\n while (queue.length > 0 && results.length < MAX_ENTRIES) {\n const { dir, depth } = queue.shift()!\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n }\n catch {\n continue\n }\n for (const e of entries) {\n if (results.length >= MAX_ENTRIES)\n break\n if (e.name.startsWith('.') || SKIP_DIRS.has(e.name))\n continue\n try {\n const full = join(dir, e.name)\n if (e.isDirectory() || (e.isSymbolicLink() && statSync(full).isDirectory())) {\n results.push({ rel: relative(base, full), path: full })\n if (depth + 1 < MAX_DEPTH)\n queue.push({ dir: full, depth: depth + 1 })\n }\n }\n catch {}\n }\n }\n return results\n}\n\nfunction compactHome(p: string): string {\n return p === HOME ? '~' : p.startsWith(`${HOME}/`) ? `~${p.slice(HOME.length)}` : p\n}\n\nfunction highlightName(name: string, positions: Set<number>, hlColor: string, dimColor: string) {\n const spans: { text: string, color: string }[] = []\n let run = ''\n let runHL = false\n for (let i = 0; i < name.length; i++) {\n const hl = positions.has(i)\n if (hl !== runHL && run) {\n spans.push({ text: run, color: runHL ? hlColor : dimColor })\n run = ''\n }\n run += name[i]\n runHL = hl\n }\n if (run)\n spans.push({ text: run, color: runHL ? hlColor : dimColor })\n return spans\n}\n\nexport function CwdPickerModal({\n currentCwd,\n onPick,\n}: {\n currentCwd: string\n onPick: (dir: string) => void\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const inputRef = useRef<InputRenderable | null>(null)\n const [query, setQuery] = useState('')\n const [browsePath, setBrowsePath] = useState(HOME)\n const [selectedIdx, setSelectedIdx] = useState(0)\n\n useEffect(() => { inputRef.current?.focus() }, [])\n\n const dirs = useMemo(() => walkDirs(browsePath), [browsePath])\n\n const fzf = useMemo(\n () => new Fzf(dirs, { selector: d => d.rel, tiebreakers: [byLengthAsc], limit: 200 }),\n [dirs],\n )\n\n const results: FzfResultItem<DirEntry>[] = useMemo(() => {\n if (!query)\n return dirs.slice(0, 200).map(item => ({ item, start: 0, end: 0, score: 0, positions: new Set<number>() }))\n return fzf.find(query)\n }, [fzf, dirs, query])\n\n const safeIndex = results.length === 0 ? 0 : Math.min(selectedIdx, results.length - 1)\n\n const handleQueryChange = useCallback((next: string) => {\n setQuery(next)\n setSelectedIdx(0)\n }, [])\n\n const commit = () => {\n const row = results[safeIndex]\n if (row)\n onPick(row.item.path)\n }\n\n const drillDown = () => {\n const row = results[safeIndex]\n if (row) {\n setBrowsePath(row.item.path)\n setQuery('')\n setSelectedIdx(0)\n }\n }\n\n const goUp = () => {\n const parent = join(browsePath, '..')\n if (parent !== browsePath) {\n setBrowsePath(parent)\n setQuery('')\n setSelectedIdx(0)\n }\n }\n\n const viewport = useMemo(() => {\n if (results.length <= VISIBLE_ROWS)\n return { start: 0, slice: results }\n const half = Math.floor(VISIBLE_ROWS / 2)\n let start = Math.max(0, safeIndex - half)\n if (start + VISIBLE_ROWS > results.length)\n start = results.length - VISIBLE_ROWS\n return { start, slice: results.slice(start, start + VISIBLE_ROWS) }\n }, [results, safeIndex])\n\n useKeyboard((key) => {\n if (key.name === 'up') {\n setSelectedIdx(i => results.length === 0 ? i : ((i - 1) % results.length + results.length) % results.length)\n return\n }\n if (key.name === 'down') {\n setSelectedIdx(i => results.length === 0 ? i : (i + 1) % results.length)\n return\n }\n if (key.name === 'tab') {\n drillDown()\n return\n }\n if (key.name === 'left' && !query) {\n goUp()\n return\n }\n if (key.name === 'right' && !query) {\n drillDown()\n return\n }\n if (key.name === 'return') {\n commit()\n }\n })\n\n return (\n <Modal title=\"change directory\" maxWidth={80}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>{'browsing '}</span>\n <span fg={COLOR.brand}>{compactHome(browsePath)}</span>\n <span fg={COLOR.mute}>{` · ${dirs.length} dirs`}</span>\n </text>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n focused\n placeholder=\"fuzzy search directories…\"\n onInput={handleQueryChange}\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n\n <box style={{ flexDirection: 'column', height: VISIBLE_ROWS, flexShrink: 0 }}>\n {results.length === 0\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>{'no directories match '}</span>\n <span fg={COLOR.warn}>{query.trim()}</span>\n </text>\n )\n : (\n viewport.slice.map((result, i) => {\n const focused = viewport.start + i === safeIndex\n const current = result.item.path === currentCwd\n const marker = current ? '●' : ' '\n const nameColor = focused ? COLOR.brand : COLOR.dim\n const spans = query\n ? highlightName(result.item.rel, result.positions, COLOR.warn, nameColor)\n : [{ text: result.item.rel, color: nameColor }]\n return (\n <box\n key={result.item.path}\n style={{\n height: 1,\n paddingLeft: 1,\n paddingRight: 1,\n flexShrink: 0,\n backgroundColor: focused ? SURFACE.selection : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={current ? COLOR.brand : COLOR.mute}>{marker}</span>\n <span fg={COLOR.mute}>{' '}</span>\n {spans.map((s, si) => <span key={si} fg={s.color}>{s.text}</span>)}\n <span fg={COLOR.mute}>/</span>\n </text>\n </box>\n )\n })\n )}\n </box>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>tab</span>\n {' enter dir · '}\n <span fg={COLOR.warn}>←</span>\n {' parent · '}\n <span fg={COLOR.warn}>↵</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close · '}\n <span fg={COLOR.mute}>{`${results.length} match${results.length === 1 ? '' : 'es'}`}</span>\n </text>\n </Modal>\n )\n}\n","/** @jsxImportSource @opentui/react */\n\n/**\n * Workspace-discovery state container.\n *\n * Sits ABOVE `<ModalRoot>` so that the modal's active node (rendered\n * as a sibling of ModalRoot's children, see `modal.tsx`) sees fresh\n * catalogs through context propagation. Putting this state inside\n * `<AppShell>` would expose it to AppShell's tree but NOT to the\n * modal layer — modals would freeze their catalog snapshot at open\n * time and a `setSkillsCatalog` would never reach them.\n *\n * Owns the per-project discovery slots (stale-while-revalidate\n * coordinators) for files + skills, the eager MCP discovery, and the\n * action thunks (`ensureFiles`, `refreshSkills`, …). Catalogs are\n * pushed through `<DiscoveryProvider>`; AppShell + every modal read\n * via `useDiscovery()`.\n */\n\nimport type { ReactNode } from 'react'\nimport type { FileEntry } from '../chat/files-discovery'\nimport type { DiscoveredMcp, DiscoveryError } from '../chat/mcps-discovery'\nimport type { SkillConfig } from '../skills'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { bootTick } from '../chat/boot-profiler'\nimport { useConfig } from '../chat/config-context'\nimport { DiscoveryProvider } from '../chat/discovery-context'\nimport { createDiscoverySlot } from '../chat/discovery-slot'\nimport { listProjectFiles } from '../chat/files-discovery'\nimport { useMcpAuthDispatch } from '../chat/mcp-auth-context'\nimport { createFileMcpCredentialStore } from '../chat/mcp-credentials'\nimport { discoverProjectMcps } from '../chat/mcps-discovery'\nimport { findGitRoot } from '../chat/project-root'\nimport { discoverProjectSkills } from '../chat/skills-discovery'\nimport { errorMessage } from '../errors'\n\n/**\n * SWR throttles. `files` is short so a long-open `@` popover picks up\n * new files within seconds; `skills` is long because SKILL.md changes\n * are rare and the walk is cheap enough that we already eager-load it.\n */\nconst FILES_REFRESH_THROTTLE_MS = 3_000\nconst SKILLS_REFRESH_THROTTLE_MS = 30_000\n\nfunction debugLog(...args: unknown[]): void {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/tui] ${args.map(errorMessage).join(' ')}\\n`)\n}\n\nexport function DiscoveryShell({ children }: { children: ReactNode }) {\n const config = useConfig()\n const dispatchAuth = useMcpAuthDispatch()\n const dispatchAuthRef = useRef(dispatchAuth)\n dispatchAuthRef.current = dispatchAuth\n\n // `projectDir` is the git root the session anchors to. Set once at\n // mount via `useState` initializer so concurrent renders see a\n // stable value — switching project means rebooting the process.\n const [projectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd())\n const dataDir = config.paths.userDir\n const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir])\n\n const [skillsCatalog, setSkillsCatalog] = useState<readonly SkillConfig[]>([])\n const [mcpsCatalog, setMcpsCatalog] = useState<readonly DiscoveredMcp[]>([])\n const [mcpsErrors, setMcpsErrors] = useState<readonly DiscoveryError[]>([])\n const [filesCatalog, setFilesCatalog] = useState<readonly FileEntry[]>([])\n\n const filesSlotRef = useRef<ReturnType<typeof createDiscoverySlot<FileEntry>> | null>(null)\n const skillsSlotRef = useRef<ReturnType<typeof createDiscoverySlot<SkillConfig>> | null>(null)\n\n useEffect(() => {\n // Slot rotation: abort any in-flight walk from the previous\n // `projectDir`, build fresh slots, and rewind catalogs to empty.\n // The empty rewind is deliberate — a stale catalog from another\n // project would surface in the new project's UI before the\n // ensure() walk landed.\n filesSlotRef.current?.abort()\n skillsSlotRef.current?.abort()\n setFilesCatalog([])\n setSkillsCatalog([])\n\n const filesSlot = createDiscoverySlot<FileEntry>({\n throttleMs: FILES_REFRESH_THROTTLE_MS,\n walk: signal => listProjectFiles({ cwd: projectDir, signal }),\n onLoad: (items) => {\n if (filesSlotRef.current === filesSlot)\n setFilesCatalog(items)\n },\n onError: (err, phase) => {\n debugLog(`listProjectFiles ${phase} failed`, err)\n },\n })\n const skillsSlot = createDiscoverySlot<SkillConfig>({\n throttleMs: SKILLS_REFRESH_THROTTLE_MS,\n walk: () => discoverProjectSkills({ cwd: projectDir, prefix: config.prefix }),\n onLoad: (items) => {\n if (skillsSlotRef.current === skillsSlot)\n setSkillsCatalog(items)\n },\n onError: (err, phase) => {\n debugLog(`discoverProjectSkills ${phase} failed`, err)\n },\n })\n filesSlotRef.current = filesSlot\n skillsSlotRef.current = skillsSlot\n\n // Eager skills load — the settings modal expects a populated\n // catalog whenever it opens, independent of whether the user has\n // hit `/` in the prompt yet. The walk is cheap (~10–50ms typical)\n // so it's fine on the discovery-effect path.\n void skillsSlot.ensure().then(skills => bootTick(`discovery:skills (${skills.length})`))\n\n try {\n const { servers, errors } = discoverProjectMcps({ cwd: projectDir, prefix: config.prefix })\n setMcpsCatalog(servers)\n setMcpsErrors(errors)\n bootTick(`discovery:mcps (${servers.length} servers, ${errors.length} parse errors)`)\n // Seed auth state from the credential store BEFORE the lazy MCP\n // bootstrap runs (the bootstrap doesn't fire until the first\n // `agent.run()` / `warmup()`, so a user who opens the MCP picker\n // before sending any message would otherwise see no status badge).\n for (const entry of servers) {\n if (entry.config.auth !== 'oauth')\n continue\n const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens\n dispatchAuthRef.current(\n hasTokens\n ? { type: 'auth-success', name: entry.config.name }\n : { type: 'auth-required', name: entry.config.name, reason: 'no-tokens' },\n )\n }\n }\n catch (err) {\n debugLog('discoverProjectMcps failed', err)\n }\n\n return () => {\n filesSlot.abort()\n skillsSlot.abort()\n }\n }, [projectDir, config.prefix, mcpCredentialStore])\n\n const ensureFiles = useCallback(\n (): Promise<readonly FileEntry[]> =>\n filesSlotRef.current?.ensure() ?? Promise.resolve([] as readonly FileEntry[]),\n [],\n )\n const ensureSkills = useCallback(\n (): Promise<readonly SkillConfig[]> =>\n skillsSlotRef.current?.ensure() ?? Promise.resolve([] as readonly SkillConfig[]),\n [],\n )\n const refreshFiles = useCallback(\n (): Promise<void> => filesSlotRef.current?.refresh() ?? Promise.resolve(),\n [],\n )\n const refreshSkills = useCallback(\n (): Promise<void> => skillsSlotRef.current?.refresh() ?? Promise.resolve(),\n [],\n )\n const refreshMcps = useCallback((): Promise<void> => {\n try {\n const { servers, errors } = discoverProjectMcps({ cwd: projectDir, prefix: config.prefix })\n setMcpsCatalog(servers)\n setMcpsErrors(errors)\n for (const entry of servers) {\n if (entry.config.auth !== 'oauth')\n continue\n const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens\n dispatchAuthRef.current(\n hasTokens\n ? { type: 'auth-success', name: entry.config.name }\n : { type: 'auth-required', name: entry.config.name, reason: 'no-tokens' },\n )\n }\n }\n catch (err) {\n debugLog('refreshMcps failed', err)\n }\n return Promise.resolve()\n }, [projectDir, config.prefix, mcpCredentialStore])\n\n // Stable value identity is NOT a goal here — the whole point of\n // this context is to re-render consumers on every catalog change.\n // The wrapper above ModalRoot makes that propagation reach the\n // active modal's subtree regardless of element identity.\n const value = useMemo(() => ({\n skillsCatalog,\n mcpsCatalog,\n mcpsErrors,\n filesCatalog,\n refreshSkills,\n refreshMcps,\n refreshFiles,\n ensureSkills,\n ensureFiles,\n }), [\n skillsCatalog,\n mcpsCatalog,\n mcpsErrors,\n filesCatalog,\n refreshSkills,\n refreshMcps,\n refreshFiles,\n ensureSkills,\n ensureFiles,\n ])\n\n return <DiscoveryProvider value={value}>{children}</DiscoveryProvider>\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable } from '@opentui/core'\nimport type { ThinkingLevel } from '../types'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/**\n * Modal that lets the user pick a reasoning effort for the active\n * model. Mirrors `ModelPickerModal`'s shape — search input on top,\n * filterable row list below, hint row at the bottom — so the two\n * pickers feel like siblings in the same family. Only surfaced for\n * models whose registry entry reports `reasoning: true` (see\n * `modelSupportsReasoning`).\n *\n * Unlike the model picker, the list isn't windowed: there are at most\n * 6 levels, so we just render them all. The search input still earns\n * its keep — typing the first letter of a level (`h` → `high`, `m` →\n * `minimal` + `medium`) commits in a single keystroke after `↵`.\n *\n * `'adaptive'` is Anthropic-only; pass `supportsAdaptive` to surface\n * it. Other providers silently treat it as `'off'`, so we hide it\n * rather than let the user pick a level that does nothing on their\n * active model.\n */\n\ninterface EffortLevel {\n id: ThinkingLevel\n description: string\n}\n\nconst BASE_LEVELS: readonly EffortLevel[] = [\n { id: 'off', description: 'no reasoning — fastest, smallest output' },\n { id: 'minimal', description: 'tiny reasoning budget (gpt-5 family)' },\n { id: 'low', description: 'short reasoning pass' },\n { id: 'medium', description: 'balanced — sensible default' },\n { id: 'high', description: 'deep reasoning — slowest, longest' },\n]\n\nconst ADAPTIVE_LEVEL: EffortLevel = {\n id: 'adaptive',\n description: 'model decides per-turn (Anthropic)',\n}\n\nexport function EffortPickerModal({\n current,\n supportsAdaptive,\n onPick,\n}: {\n current: ThinkingLevel | undefined\n supportsAdaptive: boolean\n onPick: (effort: ThinkingLevel) => void\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const inputRef = useRef<InputRenderable | null>(null)\n const [query, setQuery] = useState('')\n\n // Build the level catalog + its lowercased search corpus together so\n // filtering on every keystroke is O(levels × queryLength) without\n // re-normalizing per call.\n const levels = useMemo(() => {\n const base = supportsAdaptive ? [...BASE_LEVELS, ADAPTIVE_LEVEL] : BASE_LEVELS\n return base.map(l => ({\n ...l,\n searchCorpus: `${l.id} ${l.description}`.toLowerCase(),\n }))\n }, [supportsAdaptive])\n\n const filtered = useMemo(() => {\n const trimmed = query.trim().toLowerCase()\n if (!trimmed)\n return levels\n const terms = trimmed.split(/\\s+/)\n return levels.filter(l => terms.every(t => l.searchCorpus.includes(t)))\n }, [levels, query])\n\n // Seed selection at the active level (or `medium` if nothing's\n // remembered — same fallback the old `<select>`-based picker used).\n // Anchored to the un-filtered list because the picker opens with an\n // empty query; once the user types, `handleQueryChange` resets to 0.\n const [selectedIdx, setSelectedIdx] = useState(() => {\n const idx = levels.findIndex(l => l.id === current)\n if (idx >= 0)\n return idx\n const fallback = levels.findIndex(l => l.id === 'medium')\n return fallback < 0 ? 0 : fallback\n })\n const handleQueryChange = useCallback((next: string) => {\n setQuery(next)\n setSelectedIdx(0)\n }, [])\n\n const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1)\n\n const commit = () => {\n const row = filtered[safeIndex]\n if (row)\n onPick(row.id)\n }\n\n // Same imperative-focus dance as the model picker — see the long\n // comment there for why `focused={true}` alone isn't enough when\n // the picker mounts on top of an already-focused chat textarea.\n useEffect(() => {\n inputRef.current?.focus()\n }, [])\n\n useKeyboard((key) => {\n if (key.name === 'up') {\n // Wrap at the edges so ↑ on row 0 lands on the last entry. Same\n // pattern the model picker + settings modal use.\n setSelectedIdx((i) => {\n if (filtered.length === 0)\n return i\n return ((i - 1) % filtered.length + filtered.length) % filtered.length\n })\n return\n }\n if (key.name === 'down') {\n setSelectedIdx((i) => {\n if (filtered.length === 0)\n return i\n return (i + 1) % filtered.length\n })\n return\n }\n if (key.name === 'return') {\n commit()\n }\n })\n\n return (\n <Modal title=\"select reasoning effort\" maxWidth={80}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n // Hard-coded `true` — see `ModelPickerModal` for the rationale\n // (the picker IS a modal, so `useModalAwareFocus()` would\n // report `false` here and fight our imperative focus).\n focused\n placeholder=\"search effort levels…\"\n onInput={handleQueryChange}\n // Suppress the input's own submit handler — ↵ commits the\n // selected row via our top-level keyboard handler.\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {filtered.length === 0\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>no levels match </span>\n <span fg={COLOR.warn}>{query.trim()}</span>\n </text>\n )\n : (\n filtered.map((level, i) => (\n <EffortRow\n key={level.id}\n level={level}\n isCurrent={level.id === current}\n isFocused={i === safeIndex}\n highlightBg={SURFACE.selection}\n />\n ))\n )}\n </box>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close · '}\n <span fg={COLOR.mute}>{`${filtered.length} / ${levels.length} level${levels.length === 1 ? '' : 's'}`}</span>\n </text>\n </Modal>\n )\n}\n\n/**\n * Single row in the picker. Mirrors `ModelRow` in `model-picker.tsx`:\n * `●` marker for the current pick, single-space middle-dot separators,\n * focused row gets the `surfaces.selection` background lift.\n */\nfunction EffortRow({\n level,\n isCurrent,\n isFocused,\n highlightBg,\n}: {\n level: EffortLevel\n isCurrent: boolean\n isFocused: boolean\n highlightBg: string\n}) {\n const COLOR = useColors()\n const marker = isCurrent ? '●' : ' '\n return (\n <box\n style={{\n height: 1,\n paddingLeft: 1,\n paddingRight: 1,\n flexShrink: 0,\n backgroundColor: isFocused ? highlightBg : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isCurrent ? COLOR.brand : COLOR.mute}>{marker}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isFocused ? COLOR.brand : COLOR.dim}>{level.id}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.mute}>{level.description}</span>\n </text>\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ScrollBoxRenderable } from '@opentui/core'\nimport type { KeyBindingDef, KeyBindings } from '../chat/keybindings'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useMemo, useRef } from 'react'\nimport { compactPath } from '../chat/format'\nimport { formatBindingForDisplay, KEYBINDING_DEFS } from '../chat/keybindings'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n// ---------------------------------------------------------------------------\n// KeybindingsModal — read-only catalog of every action shortcut, grouped\n// by surface (Global, Message queue, Turn details, Session details).\n//\n// Layout (illustrative — actual widths reflow against the terminal):\n// ┌─ keybindings ────────────────────────── 23 actions ─┐\n// │ │\n// │ Global │\n// │ ctrl+o settings │\n// │ open the Settings modal (toggles…) │\n// │ ctrl+x session │\n// │ open the session details modal… │\n// │ … │\n// │ │\n// │ Message queue │\n// │ … │\n// │ │\n// │ ▶ Edit keybindings file › │\n// │ ~/.zidane/keybindings.json │\n// │ │\n// └────────────── ↵ edit file · esc close ──────────────┘\n//\n// Purely informational — no per-row selection. ↵ opens the JSON file in\n// `$EDITOR`; ↑/↓ scroll the list (via the OpenTUI scrollbox's own focus,\n// when the catalog grows past the modal's max-height); esc closes.\n//\n// `KEYBINDING_DEFS` is the source of truth for both content and ordering\n// — render order tracks the catalog order, with a section header\n// inserted whenever the `group` field changes between adjacent entries.\n// Adding a new action in `keybindings.ts` automatically lands here with\n// no edits required.\n// ---------------------------------------------------------------------------\n\ninterface Props {\n /** Effective bindings — defaults merged with user overrides. */\n bindings: KeyBindings\n /**\n * Absolute path to the user-level `keybindings.json`. Threaded\n * through so the \"edit file\" button's caption shows the real path\n * (with `$HOME` collapsed to `~`) — falls back to the install\n * default when not provided. The actual open path is opaque to\n * the modal; that's owned by `onEditFile`.\n */\n filePath?: string\n /** Open `<userDir>/keybindings.json` in `$EDITOR`. Closes the modal first. */\n onEditFile: () => void\n /** Dismiss the modal without opening the editor. */\n onClose: () => void\n}\n\n/**\n * Fixed column width for the rendered key — derived once from\n * {@link KEYBINDING_DEFS} so adding an action with a wider default spec\n * (`ctrl+shift+x`, etc.) automatically grows the column instead of\n * truncating the label. Falls back to a sensible minimum so empty /\n * one-glyph defaults don't collapse the layout.\n */\nconst KEY_COL_WIDTH = (() => {\n let max = 8\n for (const def of KEYBINDING_DEFS) {\n const width = formatBindingForDisplay(def.default).length\n if (width > max)\n max = width\n }\n return max + 2 // breathing room before the label column\n})()\n\nexport function KeybindingsModal({ bindings, filePath, onEditFile, onClose }: Props) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const { height: termHeight } = useTerminalDimensions()\n const scrollRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Pre-compute the grouped rows ONCE per (bindings, defs) change. The\n // catalog is small (<30 rows); the memo is mostly so the section\n // header insertion stays a pure derivation of `KEYBINDING_DEFS`.\n const sections = useMemo(() => groupBindings(bindings), [bindings])\n\n useKeyboard((key) => {\n if (key.name === 'return') {\n onEditFile()\n }\n })\n\n // Cap the modal at ~two-thirds of the terminal — same floor/ceiling\n // logic the todos modal uses. Leaves the chat behind visible while\n // giving the list room to scroll on tall terminals.\n const idealHeight = Math.floor((termHeight - 4) * 0.7)\n const maxHeight = Math.max(18, Math.min(40, idealHeight))\n\n const totalCount = KEYBINDING_DEFS.length\n\n return (\n <Modal\n title=\"keybindings\"\n bottomTitle=\"↵ edit file · esc close\"\n rightTitle={<CountsBadge count={totalCount} />}\n maxWidth={104}\n minWidth={64}\n maxHeight={maxHeight}\n onClose={onClose}\n >\n <box\n style={{\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n }}\n >\n <scrollbox\n ref={scrollRef}\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n {sections.map((section, sectionIdx) => (\n <box\n key={section.group}\n style={{\n flexDirection: 'column',\n flexShrink: 0,\n marginTop: sectionIdx === 0 ? 0 : 1,\n }}\n >\n <SectionHeader label={section.group} />\n {section.rows.map(row => (\n <BindingRow\n key={row.def.action}\n def={row.def}\n spec={row.spec}\n />\n ))}\n </box>\n ))}\n </scrollbox>\n </box>\n\n <EditFileButton\n filePath={filePath}\n highlightBg={SURFACE.selection}\n brand={COLOR.brand}\n mute={COLOR.mute}\n dim={COLOR.dim}\n />\n </Modal>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Section / row primitives\n// ---------------------------------------------------------------------------\n\nfunction SectionHeader({ label }: { label: string }) {\n const COLOR = useColors()\n return (\n <box style={{ flexShrink: 0, paddingLeft: 1, paddingRight: 1 }}>\n <text wrapMode=\"none\">\n <span fg={COLOR.brand}>{label}</span>\n </text>\n </box>\n )\n}\n\nfunction BindingRow({ def, spec }: { def: KeyBindingDef, spec: string }) {\n const COLOR = useColors()\n const display = formatBindingForDisplay(spec)\n // Unbound actions render `—` in the mute palette so the gap stays\n // legible. Header line uses `wrapMode=\"none\"` — key + label never\n // wrap; the column padding guarantees label-column alignment via\n // `padEnd` (every glyph we substitute is single-cell, so character\n // count == column count).\n //\n // The description sits in its OWN sibling box with `paddingLeft:\n // KEY_COL_WIDTH` instead of a space-prefixed string. That makes\n // OpenTUI compute wrap width against the narrower (indented) column,\n // so the wrap continuation lands under the description's left edge\n // instead of leaking back to the row's left edge.\n const keyText = (display || '—').padEnd(KEY_COL_WIDTH, ' ')\n const keyColor = display ? COLOR.warn : COLOR.mute\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 3, paddingRight: 1 }}>\n <text wrapMode=\"none\">\n <span fg={keyColor}>{keyText}</span>\n <span fg={COLOR.dim}>{def.label}</span>\n </text>\n <box style={{ flexShrink: 0, paddingLeft: KEY_COL_WIDTH }}>\n <text wrapMode=\"word\" fg={COLOR.mute}>{def.description}</text>\n </box>\n </box>\n )\n}\n\nfunction EditFileButton({\n filePath,\n highlightBg,\n brand,\n mute,\n dim,\n}: {\n filePath: string | undefined\n highlightBg: string\n brand: string\n mute: string\n dim: string\n}) {\n // `compactPath` collapses `$HOME` to `~` so the displayed string is\n // short and recognizable. When the host didn't thread a path, fall\n // back to the install-default location — keeps the legend useful\n // even for embedders that don't wire the prop.\n const display = filePath ? compactPath(filePath) : '~/.zidane/keybindings.json'\n return (\n <box\n style={{\n flexShrink: 0,\n flexDirection: 'column',\n paddingLeft: 1,\n paddingRight: 1,\n backgroundColor: highlightBg,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={brand}>▶ </span>\n <span fg={brand}>Edit keybindings file</span>\n <span fg={mute}>{' '}</span>\n <span fg={brand}>›</span>\n </text>\n <text wrapMode=\"none\">\n <span fg={mute}>{' '}</span>\n <span fg={dim}>{`${display} — restart to apply changes`}</span>\n </text>\n </box>\n )\n}\n\nfunction CountsBadge({ count }: { count: number }) {\n const COLOR = useColors()\n return (\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.accent}>{String(count)}</span>\n <span fg={COLOR.mute}>{` action${count === 1 ? '' : 's'} `}</span>\n </text>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Pure helpers\n// ---------------------------------------------------------------------------\n\ninterface Section {\n group: KeyBindingDef['group']\n rows: ReadonlyArray<{ def: KeyBindingDef, spec: string }>\n}\n\n/**\n * Walk `KEYBINDING_DEFS` in order and bucket rows into contiguous\n * sections by `group`. Preserves catalog order — if two actions in the\n * same group are split by an entry from another group, they'd render\n * as two separate sections with the same header (catalog order wins\n * over \"merge same-group entries\" so the on-screen story matches the\n * on-disk file).\n */\nfunction groupBindings(bindings: KeyBindings): readonly Section[] {\n const sections: Array<{ group: KeyBindingDef['group'], rows: Array<{ def: KeyBindingDef, spec: string }> }> = []\n for (const def of KEYBINDING_DEFS) {\n const last = sections[sections.length - 1]\n const spec = bindings[def.action] ?? ''\n if (last && last.group === def.group) {\n last.rows.push({ def, spec })\n continue\n }\n sections.push({ group: def.group, rows: [{ def, spec }] })\n }\n return sections\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable } from '@opentui/core'\nimport type { ProviderAuth, ProviderKey } from '../chat/auth'\nimport type { CatalogEntry } from '../chat/model-catalog'\nimport type { ModelInfo } from '../chat/providers'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { fmtTokens } from '../chat/format'\nimport { buildModelCatalog, filterModelCatalog, indexOfEntry } from '../chat/model-catalog'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/**\n * Cross-provider, searchable model picker.\n *\n * The picker unions every available provider's models into one flat\n * catalog, lets the user filter with a typed query, and returns both\n * the chosen provider and model id on commit. Pick a model from a\n * different provider than the current one and the caller is expected\n * to swap the active `ProviderAuth` + rebuild the agent — see\n * `app.tsx`'s `onPickModel` for the canonical wiring.\n *\n * Geometry:\n * - Top: single-row search input. The input owns focus so the user\n * can type immediately; ↑/↓/↵/⎋ are intercepted before the input's\n * text handler sees them so navigation works without losing focus.\n * - Middle: windowed list of catalog rows around the current\n * selection. Each row shows the model name (or id) in brand color,\n * the provider label in dim, and a compact capability suffix\n * (`ctx 200k · reasoning · vision`).\n * - Bottom: shortcut hint row.\n *\n * Empty states:\n * - No available providers → \"no providers configured\" notice.\n * - Query has no matches → \"no matches\" row, keep the search input\n * live so the user can backspace out.\n */\n\n/** Visible rows in the windowed list. Keeps the modal a stable size on long catalogs. */\nconst VISIBLE_ROWS = 10\n\nexport interface PickedModel {\n providerKey: ProviderKey\n modelId: string\n}\n\nexport function ModelPickerModal({\n providers,\n modelsFor,\n current,\n onPick,\n}: {\n /** Authed providers — the catalog is unioned across these. */\n providers: readonly ProviderAuth[]\n /** Resolver from `ResolvedConfig.modelsFor`. */\n modelsFor: (key: ProviderKey) => readonly ModelInfo[]\n /** Currently active selection. Promoted to top of its provider section + pre-highlighted. */\n current: PickedModel\n /** Called on commit with the chosen `{ providerKey, modelId }`. */\n onPick: (picked: PickedModel) => void\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const inputRef = useRef<InputRenderable | null>(null)\n const [query, setQuery] = useState('')\n\n // Imperatively grab focus on mount. Two things conspire to make the\n // search input lose focus without this:\n //\n // 1. When the picker mounts on top of a chat screen that owned\n // focus (the prompt textarea), OpenTUI's prop diff doesn't\n // always steal focus from the previously-focused renderable\n // just because we declared `focused={true}` on a freshly-\n // mounted input.\n // 2. The picker is rendered INSIDE the modal layer, so\n // `useModalAwareFocus()` (which background inputs use to blur\n // when a modal opens) would report `false` here and short-circuit\n // the focus transfer — we'd be using a hook designed to shed\n // focus right when we want to claim it.\n //\n // We sidestep both by declaring `focused={true}` unconditionally on\n // the input and forcing `.focus()` once on mount, then letting the\n // keyboard handler below own the up/down/return overrides.\n useEffect(() => {\n inputRef.current?.focus()\n }, [])\n\n // Pre-compute the catalog once per `providers`. `modelsFor` is stable\n // for a `ResolvedConfig` lifetime, so we don't depend on it in the\n // memo key — depending on the function identity would invalidate\n // every render where the App re-creates a wrapper closure.\n const catalog = useMemo(\n () => buildModelCatalog({ providers, modelsFor, current }),\n [providers, modelsFor, current],\n )\n\n const filtered = useMemo(() => filterModelCatalog(catalog, query), [catalog, query])\n\n // Selection index INTO `filtered`. Seeded at the current pick so\n // the picker opens with the active model highlighted (and scrolled\n // into view via the window calc below). Anchoring at 0 in a\n // `useEffect([query])` would have fired on MOUNT too — clobbering\n // the seed before the user saw it — so the reset lives in\n // `handleQueryChange` instead, only firing on actual edits.\n const [selectedIdx, setSelectedIdx] = useState(() =>\n Math.max(0, indexOfEntry(catalog, current)),\n )\n const handleQueryChange = useCallback((next: string) => {\n setQuery(next)\n // The previously-selected row may have been filtered out, so anchor\n // at the first match — the natural read position after a search.\n setSelectedIdx(0)\n }, [])\n\n const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1)\n\n const commit = () => {\n const row = filtered[safeIndex]\n if (row)\n onPick({ providerKey: row.providerKey, modelId: row.model.id })\n }\n\n // Viewport of rows actually rendered. Centers the selection inside\n // the cap when possible; clamps at edges so scrolling stays smooth\n // at the tail. Named `viewport` rather than `window` to avoid\n // shadowing the global.\n const viewport = useMemo(() => {\n if (filtered.length <= VISIBLE_ROWS)\n return { start: 0, slice: filtered }\n const half = Math.floor(VISIBLE_ROWS / 2)\n let start = Math.max(0, safeIndex - half)\n if (start + VISIBLE_ROWS > filtered.length)\n start = filtered.length - VISIBLE_ROWS\n return { start, slice: filtered.slice(start, start + VISIBLE_ROWS) }\n }, [filtered, safeIndex])\n\n // Keyboard handler — wins over the input's default text handling\n // for the navigation keys + commit. We register at the modal level\n // (not the input's onKeyDown) because the modal is the topmost\n // surface and `useKeyboard` gives us a single place to coordinate\n // arrow-driven list navigation alongside the input's free-text\n // editing. No focus guard: this component only lives while its\n // modal is open, and the single-slot Modal layer guarantees no\n // sibling modal can steal events from us. The input still receives\n // every non-overridden keystroke through its own text path\n // (character input, backspace, cursor moves, etc.) — single-line\n // inputs treat `up`/`down` as no-ops, so there's no conflict.\n useKeyboard((key) => {\n if (key.name === 'up') {\n // Wrap at the edges: ↑ on the first row jumps to the last entry\n // (and ↓ at the bottom comes back to the first) — keeps long\n // catalogs navigable without forcing the user to scroll back up.\n setSelectedIdx((i) => {\n if (filtered.length === 0)\n return i\n return ((i - 1) % filtered.length + filtered.length) % filtered.length\n })\n return\n }\n if (key.name === 'down') {\n setSelectedIdx((i) => {\n if (filtered.length === 0)\n return i\n return (i + 1) % filtered.length\n })\n return\n }\n if (key.name === 'return') {\n commit()\n }\n })\n\n if (providers.length === 0)\n return <EmptyProvidersState />\n\n return (\n <Modal title=\"select model\" maxWidth={100}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n // Hard-coded `true` instead of `useModalAwareFocus()` (which\n // would return `false` here because the picker IS a modal —\n // the hook is designed to blur background inputs when a\n // modal opens, not to focus inputs inside the modal).\n focused\n placeholder=\"search models — provider, name, capability…\"\n onInput={handleQueryChange}\n // Suppress the input's own submit handler — ↵ commits the\n // selected row via our top-level keyboard handler. Without\n // an `onSubmit` of `() => {}`, the OpenTUI input runtime\n // would forward the keypress, then we'd commit twice.\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n\n <box style={{ flexDirection: 'column', height: VISIBLE_ROWS, flexShrink: 0 }}>\n {filtered.length === 0\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>no models match </span>\n <span fg={COLOR.warn}>{query.trim()}</span>\n </text>\n )\n : (\n viewport.slice.map((entry, i) => (\n <ModelRow\n key={`${entry.providerKey}:${entry.model.id}`}\n entry={entry}\n isCurrent={\n entry.providerKey === current.providerKey\n && entry.model.id === current.modelId\n }\n isFocused={viewport.start + i === safeIndex}\n // Surface color so the focused row pops without\n // pulling in a per-row prop chain just for theming.\n highlightBg={SURFACE.selection}\n />\n ))\n )}\n </box>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close · '}\n <span fg={COLOR.mute}>{`${filtered.length} / ${catalog.length} model${catalog.length === 1 ? '' : 's'}`}</span>\n </text>\n </Modal>\n )\n}\n\n/**\n * Single row in the picker. Renders the model name in `brand`, the\n * provider tag in `dim`, and the capability suffix in `mute`. The\n * focused row gets a subtle selection background (same surface as\n * select-turn mode in the transcript) so it pops without a separate\n * marker glyph competing with the `●` \"current\" indicator.\n */\nfunction ModelRow({\n entry,\n isCurrent,\n isFocused,\n highlightBg,\n}: {\n entry: CatalogEntry\n isCurrent: boolean\n isFocused: boolean\n highlightBg: string\n}) {\n const COLOR = useColors()\n const marker = isCurrent ? '●' : ' '\n return (\n <box\n style={{\n height: 1,\n paddingLeft: 1,\n paddingRight: 1,\n flexShrink: 0,\n backgroundColor: isFocused ? highlightBg : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isCurrent ? COLOR.brand : COLOR.mute}>{marker}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isFocused ? COLOR.brand : COLOR.dim}>{entry.model.name ?? entry.model.id}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.model}>{entry.providerLabel}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.mute}>{describeModel(entry.model)}</span>\n </text>\n </box>\n )\n}\n\nfunction EmptyProvidersState() {\n const COLOR = useColors()\n return (\n <Modal title=\"select model\">\n <text fg={COLOR.dim}>No authed providers — configure one via</text>\n <text fg={COLOR.dim}>\n <span fg={COLOR.model}>{' settings → re-configure providers'}</span>\n {' or '}\n <span fg={COLOR.model}>esc → sessions → settings</span>\n {' first.'}\n </text>\n </Modal>\n )\n}\n\n/** \"ctx 200k · reasoning · vision\" — compact capability blurb. */\nfunction describeModel(m: ModelInfo): string {\n const parts: string[] = [`ctx ${fmtTokens(m.contextWindow)}`]\n if (m.reasoning)\n parts.push('reasoning')\n if (m.input?.includes('image'))\n parts.push('vision')\n return parts.join(' · ')\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ReactNode } from 'react'\nimport type { CompletionState } from '../chat/completion'\nimport { resolveChipColor } from '../chat/theme'\nimport { useColors, useSelectStyle, useSurfaces } from '../chat/theme-context'\n\n/**\n * Popover above the textarea showing the active provider's items. Provider-\n * agnostic — reads `label` + `description` off each `CompletionItem`. The\n * TUI hosts can pass any `CompletionState<TItem>`; the popup never needs\n * to know what `TItem` is.\n *\n * Geometry: 1 row of chrome + min(N, visibleRows) item rows + 1 hint row.\n * `flexShrink: 0` pins the height so a long transcript can't squeeze it.\n *\n * The popup is invisible (`null`-rendered) when `state.active` is null or\n * `state.items` is empty — the prompt block keeps its layout calm.\n *\n * Solid `backgroundColor: SURFACE.modal` is load-bearing: in `PromptBlock`\n * the popup floats over the transcript via `position: absolute`, and a\n * transparent fill would let the transcript text bleed through. Pairs\n * with the modal panel surface so floating UI shares one visual identity.\n *\n * Title overlays are painted on the top border (same trick as\n * `PromptHints`): provider label on the left in the chip-id's accent\n * color, match count on the right in dim text. Both ride absolute\n * positions so they take no flow space; the popup's height comes\n * entirely from the bordered body. The accent reuses\n * `resolveChipColor(...).bg` (foreground only, no background pill) so\n * the picker title still reads as \"this is the X provider\" without\n * carrying the chip pill's heavy visual weight.\n */\nexport function CompletionPopup({\n state,\n /** Cap on visible rows. 6 keeps the popover compact even with long catalogs. */\n visibleRows = 6,\n}: {\n state: CompletionState<unknown>\n visibleRows?: number\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const SELECT = useSelectStyle()\n\n if (!state.active)\n return null\n\n const loading = state.loading && state.items.length === 0\n if (state.items.length === 0 && !loading)\n return null\n\n const chip = resolveChipColor(SURFACE.chips, state.active.provider.id)\n const providerLabel = state.active.provider.label.toLowerCase()\n\n let body: ReactNode\n let height: number\n if (loading) {\n body = <text fg={COLOR.dim}>loading…</text>\n height = 3\n }\n else {\n const rows = Math.min(state.items.length, visibleRows)\n const half = Math.floor(rows / 2)\n let start = Math.max(0, state.selectedIndex - half)\n if (start + rows > state.items.length)\n start = state.items.length - rows\n const slice = state.items.slice(start, start + rows)\n height = 2 + rows + 1\n body = (\n <>\n <box style={{ flexDirection: 'column' }}>\n {slice.map((item, i) => {\n const absoluteIndex = start + i\n const focused = absoluteIndex === state.selectedIndex\n return (\n <box key={item.id} style={{ height: 1, overflow: 'hidden', flexShrink: 0 }}>\n <text\n fg={focused ? COLOR.brand : COLOR.dim}\n wrapMode=\"none\"\n // `truncate` asks OpenTUI's text-buffer-view to\n // collapse the overflow with an ellipsis when the\n // styled text would exceed the row's viewport —\n // labels stay intact, descriptions tail off cleanly\n // with `…` instead of being chopped mid-word.\n truncate\n >\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={focused ? COLOR.brand : SELECT.textColor}>{item.label}</span>\n {item.description && (\n <span fg={COLOR.mute}>\n {' '}\n {item.description}\n </span>\n )}\n </text>\n </box>\n )\n })}\n </box>\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' / '}\n <span fg={COLOR.warn}>tab</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </>\n )\n }\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n backgroundColor: SURFACE.modal,\n paddingLeft: 1,\n paddingRight: 1,\n height,\n flexShrink: 0,\n alignSelf: 'stretch',\n flexDirection: 'column',\n }}\n >\n {body}\n </box>\n {/*\n Title + meta overlays — both siblings of the bordered box,\n declared after it so the renderer paints them on top of the top\n border. Leading + trailing mute spaces overwrite the border\n characters underneath so each segment reads as a self-contained\n chunk. `position: absolute` means they take no flow space.\n\n The provider label rides the chip-id's accent as foreground only\n (no pill background) so the picker chrome reads light against\n the modal surface — the chip background is reserved for the\n in-prompt reference pills, where the heavier visual weight\n actually signals \"this is a structured token in your buffer\".\n */}\n <text style={{ position: 'absolute', top: 0, left: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={chip.bg}>{providerLabel}</span>\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n {loading\n ? <span fg={COLOR.dim}>loading…</span>\n : <span fg={COLOR.dim}>{`${state.items.length} match${state.items.length === 1 ? '' : 'es'}`}</span>}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\n// ---------------------------------------------------------------------------\n// FileEditApprovalModal — file-edit permission dialog with per-edit toggles.\n//\n// Two layouts, picked off the call's hunk count:\n//\n// - Single-hunk (`edit`, `write_file`, single-step `multi_edit`):\n// ┌─ Permission Request ─────────────────────────┐\n// │ edit · src/foo.ts │\n// │ <split / unified diff> │\n// │ [ Allow ] [ Allow session ] [ Always ] [Deny]│\n// └───────────────────────────────────────────────┘\n//\n// - Multi-hunk `multi_edit`:\n// ┌─ Permission Request ─────────────────────────┐\n// │ multi_edit · src/foo.ts · 2/3 selected │\n// │ ▸ ✓ Edit 1/3 -old / +new (one-line preview)│\n// │ ✗ Edit 2/3 -old / +new (one-line preview)│\n// │ ✓ Edit 3/3 -old / +new (one-line preview)│\n// │ │\n// │ <unified diff of focused edit> │\n// │ │\n// │ [ Allow All ] [ Deny All ] [ Apply Selected ]│\n// └───────────────────────────────────────────────┘\n//\n// Both layouts share the same keyboard contract for the \"bulk\" decisions\n// (`a` / `s` / `p` / `d`), with `space` reserved for the multi-edit list\n// toggle and `↑/↓` to navigate.\n// ---------------------------------------------------------------------------\n\nimport type { ScrollBoxRenderable } from '@opentui/core'\nimport type { ApprovalDecision, ApprovalOriginator, ApprovalRequest } from '../chat/safe-mode-context'\nimport type { EditPayload } from '../chat/types'\nimport * as fs from 'node:fs'\nimport { getTreeSitterClient } from '@opentui/core'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { extractEditPayload, filetypeFromPath, previewEditPayload } from '../chat/edit-diff'\nimport { compactPath } from '../chat/format'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { errorMessage } from '../errors'\nimport { useModal } from './modal'\nimport { useMdStyle } from './theme'\n\n// Tools we treat as file-edit operations and route through this modal.\n// Everything else stays on the inline `ApprovalBlock` flow.\nconst FILE_EDIT_TOOLS = new Set(['edit', 'multi_edit', 'write_file'])\n\nexport function isFileEditTool(tool: string): boolean {\n return FILE_EDIT_TOOLS.has(tool)\n}\n\ninterface FileEditApprovalModalProps {\n request: ApprovalRequest\n onDecide: (decision: ApprovalDecision) => void\n}\n\n// ---------------------------------------------------------------------------\n// Shared shell — reads the prior file content + builds the EditPayload.\n// ---------------------------------------------------------------------------\n\nfunction useEditPayloadFromRequest(request: ApprovalRequest): {\n payload: EditPayload | null\n priorContent: string\n priorError: string | null\n targetPath: string\n} {\n const targetPath = String(request.input.path ?? '')\n\n const [priorContent, priorError] = useMemo<[string, string | null]>(() => {\n try {\n return [fs.readFileSync(targetPath, 'utf8'), null]\n }\n catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT')\n return ['', null]\n return ['', errorMessage(e)]\n }\n }, [targetPath])\n\n const payload = useMemo<EditPayload | null>(() => {\n try {\n return extractEditPayload(request.tool, request.input, priorContent) ?? null\n }\n catch {\n return null\n }\n }, [request.tool, request.input, priorContent])\n\n return { payload, priorContent, priorError, targetPath }\n}\n\nexport function FileEditApprovalModal({ request, onDecide }: FileEditApprovalModalProps) {\n const { payload, priorContent, priorError, targetPath } = useEditPayloadFromRequest(request)\n\n // Multi-edit with >1 hunk routes to the per-edit list view. Everything\n // else (edit, write_file, single-step multi_edit) keeps the legacy\n // single-diff layout — same keystrokes, no behavioral change for the\n // common case.\n const isPerEdit = payload?.tool === 'multi_edit' && payload.hunks.length > 1\n\n if (isPerEdit && payload) {\n return (\n <MultiEditApprovalModal\n request={request}\n payload={payload}\n priorContent={priorContent}\n priorError={priorError}\n targetPath={targetPath}\n onDecide={onDecide}\n />\n )\n }\n\n return (\n <SingleEditApprovalModal\n request={request}\n payload={payload}\n priorContent={priorContent}\n priorError={priorError}\n targetPath={targetPath}\n onDecide={onDecide}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// Action bar — bulk decisions shared between single-edit + multi-edit.\n// ---------------------------------------------------------------------------\n\ninterface ActionButton {\n label: string\n decision: ApprovalDecision\n shortcut: string\n destructive?: boolean\n}\n\nconst BULK_ACTIONS: ActionButton[] = [\n { label: 'Allow', decision: 'accept-once', shortcut: 'a' },\n { label: 'Allow for session', decision: 'accept-session', shortcut: 's' },\n { label: 'Always allow', decision: 'accept-safelist', shortcut: 'p' },\n { label: 'Deny', decision: 'deny', shortcut: 'd', destructive: true },\n]\n\ninterface ActionBarProps {\n actions: readonly ActionButton[]\n selected: number\n customLabel?: { idx: number, label: string }\n}\n\nfunction ActionBar({ actions, selected, customLabel }: ActionBarProps) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n return (\n <box style={{ flexDirection: 'row', height: 1, flexShrink: 0 }}>\n {actions.map((action, i) => {\n const isSelected = i === selected\n const tint = action.destructive ? COLOR.error : COLOR.brand\n const baseLabel = customLabel && customLabel.idx === i ? customLabel.label : action.label\n const labelText = ` ${baseLabel} (${action.shortcut}) `\n return (\n <box key={`${action.decision === 'deny' ? 'deny' : i}`} style={{ marginRight: 2, flexShrink: 0 }}>\n {isSelected\n ? (\n <text bg={tint} fg={SURFACE.background} wrapMode=\"none\">\n {labelText}\n </text>\n )\n : (\n <text bg={SURFACE.selection} fg={COLOR.dim} wrapMode=\"none\">\n {labelText}\n </text>\n )}\n </box>\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Single-edit layout — original Crush-style dialog with per-action shortcuts.\n// ---------------------------------------------------------------------------\n\ninterface SingleEditApprovalModalProps extends FileEditApprovalModalProps {\n payload: EditPayload | null\n priorContent: string\n priorError: string | null\n targetPath: string\n}\n\n// Top-left title rendered on the modal's top border. Surrounding spaces\n// punch through the border `─` ticks so the label reads cleanly.\nconst SINGLE_EDIT_TITLE = ' edit approval '\nconst MULTI_EDIT_TITLE = ' multi-edit approval '\n\n/**\n * Render the originator-attribution suffix for the modal's right-side\n * title overlay. Returns `''` for parent calls (no suffix); subagent\n * calls get a ` · child-N` tag so the user can tell which agent issued\n * the gate request when subagents bubble their gates up through the\n * parent's hook bus.\n */\nfunction originatorSuffix(originator: ApprovalOriginator | undefined): string {\n if (!originator || originator.kind === 'parent')\n return ''\n return ` · ${originator.label}`\n}\n\n/**\n * Budget a basename for the modal's top-right title overlay so it never\n * overflows into the left title or off-screen. Leaves room for the left\n * title, the surrounding mute padding spaces on both ends, the corner\n * glyphs, and an optional trailing suffix (e.g. ` · N/M selected` or\n * ` · child-N`).\n */\nfunction rightTitleFilename(\n targetPath: string,\n termWidth: number,\n leftTitleLen: number,\n suffixLen = 0,\n): string {\n const base = targetPath ? (targetPath.split('/').pop() ?? targetPath) : '(no path)'\n const budget = Math.max(8, termWidth - leftTitleLen - suffixLen - 6)\n return compactPath(base, budget)\n}\n\nfunction SingleEditApprovalModal({ request, payload, priorContent, priorError, targetPath, onDecide }: SingleEditApprovalModalProps) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const mdStyle = useMdStyle()\n const { width: termWidth } = useTerminalDimensions()\n const modal = useModal()\n\n const preview = useMemo(\n () => (payload ? previewEditPayload(payload, priorContent, 6) : null),\n [payload, priorContent],\n )\n const diffText = preview?.diffText ?? ''\n const focusedResolved = preview?.resolution[0]?.resolved ?? true\n\n const [selected, setSelected] = useState(0)\n const filetype = useMemo(() => filetypeFromPath(targetPath), [targetPath])\n const childSuffix = originatorSuffix(request.originator)\n const filename = useMemo(\n () => rightTitleFilename(targetPath, termWidth, SINGLE_EDIT_TITLE.length, childSuffix.length),\n [targetPath, termWidth, childSuffix.length],\n )\n\n const decide = useCallback((decision: ApprovalDecision) => {\n onDecide(decision)\n modal.close()\n }, [onDecide, modal])\n\n useKeyboard((key) => {\n if (key.name === 'left') {\n setSelected(i => (i - 1 + BULK_ACTIONS.length) % BULK_ACTIONS.length)\n return\n }\n if (key.name === 'right' || key.name === 'tab') {\n setSelected(i => (i + 1) % BULK_ACTIONS.length)\n return\n }\n if (key.name === 'return') {\n decide(BULK_ACTIONS[selected].decision)\n return\n }\n if (key.name === 'escape') {\n decide('deny')\n return\n }\n const ch = (key.sequence ?? '').toLowerCase()\n const hit = BULK_ACTIONS.find(a => a.shortcut === ch)\n if (hit)\n decide(hit.decision)\n })\n\n // Two columns of border + two columns of padding eat 4 cells of the\n // outer panel width; the inner diff (and its border) share the rest.\n const useSplit = termWidth >= 100\n const diffWidth = termWidth - 8\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1, flexShrink: 1, overflow: 'hidden' }}>\n <box\n title={SINGLE_EDIT_TITLE}\n titleAlignment=\"left\"\n bottomTitle=\" ←→ navigate · ↵ confirm · esc deny · a/s/p/d shortcuts \"\n style={{\n flexGrow: 1,\n flexShrink: 1,\n // overflow:hidden does double duty: scissors any rogue child\n // paint at the modal's bounds AND tells Yoga to constrain\n // children below their intrinsic measured size, so a tall\n // diff can never push the action bar below the bottom border.\n overflow: 'hidden',\n border: true,\n borderColor: COLOR.brand,\n backgroundColor: SURFACE.modal,\n padding: 1,\n flexDirection: 'column',\n }}\n >\n {priorError\n ? (\n <box style={{ height: 3, flexShrink: 0, marginBottom: 1 }}>\n <text fg={COLOR.error} wrapMode=\"word\">\n {`Couldn't read ${targetPath}: ${priorError}`}\n </text>\n </box>\n )\n : !focusedResolved && payload\n ? (\n <UnresolvedHunkPanel\n hunk={payload.hunks[0]}\n targetPath={targetPath}\n width={diffWidth + 2}\n />\n )\n : (\n <box\n style={{\n border: true,\n borderColor: COLOR.mute,\n width: diffWidth + 2,\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n marginBottom: 1,\n }}\n >\n {/* Big diffs scroll inside this panel — the inner <diff>\n sizes to its intrinsic content height via the Code\n renderable's measure func, the scrollbox bounds the\n visible viewport to whatever Yoga gave the parent.\n Mouse-wheel drives scroll via OpenTUI's hit-tester\n regardless of focus; keyboard stays on the modal's\n approval shortcuts. */}\n <scrollbox\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n <diff\n diff={diffText}\n view={useSplit ? 'split' : 'unified'}\n wrapMode=\"word\"\n showLineNumbers={true}\n syntaxStyle={mdStyle}\n treeSitterClient={getTreeSitterClient()}\n {...(filetype ? { filetype } : {})}\n addedBg={SURFACE.diff.addBg}\n removedBg={SURFACE.diff.removeBg}\n {...(SURFACE.diff.addContentBg ? { addedContentBg: SURFACE.diff.addContentBg } : {})}\n {...(SURFACE.diff.removeContentBg ? { removedContentBg: SURFACE.diff.removeContentBg } : {})}\n addedSignColor={SURFACE.diff.addFg}\n removedSignColor={SURFACE.diff.removeFg}\n style={{ width: diffWidth, flexShrink: 0 }}\n />\n </scrollbox>\n </box>\n )}\n\n <ActionBar actions={BULK_ACTIONS} selected={selected} />\n </box>\n {/* Top-right title overlay — sibling of the bordered modal so the\n renderer paints it on top of the top border (a child can't,\n OpenTUI's box scissor rect excludes the border row). Same\n trick as TitleOverlay / CompletionPopup. */}\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.model}>{filename}</span>\n {childSuffix.length > 0 && <span fg={COLOR.accent}>{childSuffix}</span>}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Multi-edit layout — per-hunk approval list.\n//\n// State machine (`mask: boolean[]`, 1:1 with payload.hunks):\n// - All true → \"Allow\" / \"Allow session\" / \"Always allow\" emit the\n// corresponding bulk decision (no `partial` wrapping —\n// keeps the safelist path identical to single-edit).\n// - All false → emits `'deny'`.\n// - Mixed → emits `{ kind: 'partial', mask }`.\n// `esc` always denies the whole call regardless of toggle state — same\n// \"stop everything\" rule the single-edit modal follows.\n// ---------------------------------------------------------------------------\n\nconst PER_EDIT_ACTIONS: ActionButton[] = [\n // Index 0 is dynamic: label reads `Allow N` / `Apply N selected` /\n // `Deny all` depending on the mask. The shortcut stays `a` for muscle\n // memory; submit logic interprets based on mask state.\n { label: 'Apply', decision: 'accept-once', shortcut: 'a' },\n { label: 'Allow for session', decision: 'accept-session', shortcut: 's' },\n { label: 'Always allow', decision: 'accept-safelist', shortcut: 'p' },\n { label: 'Deny all', decision: 'deny', shortcut: 'd', destructive: true },\n]\n\ninterface MultiEditApprovalModalProps extends FileEditApprovalModalProps {\n payload: EditPayload\n priorContent: string\n priorError: string | null\n targetPath: string\n}\n\nfunction MultiEditApprovalModal({ request, payload, priorContent, priorError, targetPath, onDecide }: MultiEditApprovalModalProps) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const mdStyle = useMdStyle()\n const { width: termWidth, height: termHeight } = useTerminalDimensions()\n const modal = useModal()\n const listScrollRef = useRef<ScrollBoxRenderable | null>(null)\n const diffScrollRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Per-edit approval mask. Default: every edit approved — matches the\n // user's most-likely intent and means a quick `enter` press = \"yes,\n // run everything\", same as the old modal.\n const [mask, setMask] = useState<readonly boolean[]>(\n () => payload.hunks.map(() => true),\n )\n const [cursor, setCursor] = useState(0)\n const [actionIdx, setActionIdx] = useState(0)\n // Two focus zones — list (toggle individual edits) or buttons (submit).\n // `tab` cycles between them; `↑/↓` moves within the active zone.\n const [zone, setZone] = useState<'list' | 'actions'>('list')\n\n const filetype = useMemo(() => filetypeFromPath(targetPath), [targetPath])\n\n const selectedCount = mask.filter(Boolean).length\n const total = mask.length\n\n // Build the decision payload from the current mask + chosen bulk action.\n const resolveDecision = useCallback((bulk: ApprovalDecision): ApprovalDecision => {\n if (bulk === 'deny')\n return 'deny'\n if (selectedCount === 0)\n return 'deny'\n if (selectedCount === total)\n return bulk\n // Partial — the safelist / session paths don't make sense for a\n // one-off mixed pick (the safelist entry is the tool name, with no\n // hunk-level scope), so we collapse them to `partial`.\n //\n // UX trade-off: pressing `p` (\"Always allow\") with a mixed mask\n // intentionally does NOT write a safelist entry — the user almost\n // certainly meant \"always allow THESE hunks\", which the safelist\n // can't represent. We could surface this in the action label\n // (e.g. \"Apply N selected (won't safelist)\") but the action bar\n // is already tight; the safe collapse to `partial` is the right\n // bias since the alternative (silently safelisting tool+path for\n // future calls based on a mixed pick) is far more surprising.\n return { kind: 'partial', mask: [...mask] }\n }, [mask, selectedCount, total])\n\n const decide = useCallback((decision: ApprovalDecision) => {\n onDecide(decision)\n modal.close()\n }, [onDecide, modal])\n\n const toggleAt = useCallback((idx: number) => {\n setMask((prev) => {\n const next = prev.slice()\n next[idx] = !next[idx]\n return next\n })\n }, [])\n\n const setAll = useCallback((value: boolean) => {\n setMask(prev => prev.map(() => value))\n }, [])\n\n useKeyboard((key) => {\n if (key.name === 'escape') {\n decide('deny')\n return\n }\n\n if (key.name === 'tab') {\n setZone(z => (z === 'list' ? 'actions' : 'list'))\n return\n }\n\n if (zone === 'list') {\n if (key.name === 'up' || key.name === 'k') {\n setCursor(c => (c - 1 + total) % total)\n return\n }\n if (key.name === 'down' || key.name === 'j') {\n setCursor(c => (c + 1) % total)\n return\n }\n if (key.name === 'space') {\n toggleAt(cursor)\n return\n }\n if (key.name === 'return') {\n // Sensible default: hopping into the actions row puts the cursor\n // on `Apply` so a follow-up enter submits.\n setZone('actions')\n setActionIdx(0)\n return\n }\n }\n else {\n if (key.name === 'left') {\n setActionIdx(i => (i - 1 + PER_EDIT_ACTIONS.length) % PER_EDIT_ACTIONS.length)\n return\n }\n if (key.name === 'right') {\n setActionIdx(i => (i + 1) % PER_EDIT_ACTIONS.length)\n return\n }\n if (key.name === 'return') {\n decide(resolveDecision(PER_EDIT_ACTIONS[actionIdx].decision))\n return\n }\n }\n\n // Bulk shortcuts work in either zone — keep `a/s/p/d` as muscle\n // memory regardless of where the cursor is.\n const ch = (key.sequence ?? '').toLowerCase()\n if (ch === 'y') {\n // `y` toggles all edits ON — quick \"approve every edit\" when the\n // user landed in a mixed state and wants to reset.\n setAll(true)\n return\n }\n if (ch === 'n') {\n setAll(false)\n return\n }\n const hit = PER_EDIT_ACTIONS.find(a => a.shortcut === ch)\n if (hit)\n decide(resolveDecision(hit.decision))\n })\n\n // Sizing — the modal renders inside ChatScreen's transcript slot\n // (`flexGrow: 1`), so the outer box claims the full conversation\n // area automatically. Inner widths pin to `panelWidth` (≈ termWidth\n // − chrome) so each inner box's right border lines up with the\n // outer right padding edge instead of overflowing. Heights are\n // distributed via `flexGrow` so the list + diff split the body\n // proportionally regardless of how tall the conversation slot\n // actually is.\n const panelWidth = Math.max(40, termWidth - 6)\n // The diff renderable lives inside the focused-diff box's own\n // border, so it gets `panelWidth - 2` columns of paint room.\n const diffWidth = panelWidth - 2\n\n // Run the lenient resolver once per (payload, priorContent) so every\n // hunk's resolvability is known — list rows badge unresolved hunks\n // with a warning glyph, and the focused-diff panel falls back to a\n // readable explanation instead of rendering a blank box when the\n // model produced an `old_string` the file doesn't contain.\n const preview = useMemo(\n () => previewEditPayload(payload, priorContent, 4),\n [payload, priorContent],\n )\n\n const focusedDiffText = preview.perHunkDiff[cursor] ?? ''\n const focusedResolved = preview.resolution[cursor]?.resolved ?? true\n const focusedAmbiguous = preview.resolution[cursor]?.ambiguous === true\n\n // List height cap — keeps the hunk list compact when there are\n // only a few edits (`numHunks + 2 borders`), but never lets it\n // claim more than ~⅓ of the available height when the batch is\n // long. Anything past the cap scrolls inside the panel. The diff\n // panel below takes the remaining space via `flexGrow: 1`.\n const listIdealRows = payload.hunks.length + 2 /* inner border */\n const listCap = Math.max(4, Math.min(Math.floor(termHeight / 3), 14))\n const listHeight = Math.min(listIdealRows, listCap)\n\n // Keep the focused row in view when the user scrolls past the\n // visible window. Deferred a frame for the same reason the\n // settings modal does it — scrollSize is computed post-commit.\n useEffect(() => {\n const sb = listScrollRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollChildIntoView(`edit-row-${cursor}`)\n })\n return () => cancelAnimationFrame(handle)\n }, [cursor])\n\n // Reset the focused-diff panel to the top whenever the user moves the\n // hunk cursor. The scrollbox is reused across hunks (React keeps it\n // mounted), so a previously-scrolled position would otherwise carry\n // into the next hunk's smaller diff.\n useEffect(() => {\n const sb = diffScrollRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollTop = 0\n })\n return () => cancelAnimationFrame(handle)\n }, [cursor])\n\n // Action button labels — dynamic on the first slot (\"Apply\" turns into\n // \"Apply N selected\" when the mask is mixed; back to \"Allow\" when all\n // are selected so the bulk safelist path stays discoverable).\n const applyLabel = selectedCount === 0\n ? 'Nothing selected'\n : selectedCount === total\n ? 'Apply all'\n : `Apply ${selectedCount}/${total}`\n\n // Top-right title overlay — `{filename}[ · child-N] · {N}/{total} selected`.\n // Compact the basename so the suffixes never get pushed off the right\n // edge on a narrow terminal.\n const selectionSuffix = ` · ${selectedCount}/${total} selected`\n const childSuffix = originatorSuffix(request.originator)\n const filename = rightTitleFilename(\n targetPath,\n termWidth,\n MULTI_EDIT_TITLE.length,\n selectionSuffix.length + childSuffix.length,\n )\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1, flexShrink: 1, overflow: 'hidden' }}>\n <box\n title={MULTI_EDIT_TITLE}\n titleAlignment=\"left\"\n bottomTitle=\" ↑↓ select · space toggle · y/n all/none · tab focus · a/s/p/d shortcuts · ↵ confirm · esc deny \"\n style={{\n flexGrow: 1,\n flexShrink: 1,\n // Same containment contract as the single-edit modal: clip any\n // child paint past the modal's bounds and let Yoga compress\n // the diff/list panels below their intrinsic measured size so\n // the action bar / bottom border stay anchored on screen.\n overflow: 'hidden',\n border: true,\n borderColor: COLOR.brand,\n backgroundColor: SURFACE.modal,\n padding: 1,\n flexDirection: 'column',\n }}\n >\n {priorError && (\n <box style={{ marginBottom: 1, flexShrink: 0 }}>\n <text fg={COLOR.error} wrapMode=\"word\">\n {`Couldn't read ${targetPath}: ${priorError}`}\n </text>\n </box>\n )}\n\n {/* Edit list — one row per hunk with a toggle indicator. The\n inner box's `width: panelWidth` matches the outer's content\n area exactly (outer `width: panelWidth + 4` minus 2 border\n + 2 padding) so the right border lands on the right padding\n edge of the parent instead of overflowing. */}\n <box\n style={{\n border: true,\n borderColor: zone === 'list' ? COLOR.brand : COLOR.mute,\n width: panelWidth,\n height: listHeight,\n flexShrink: 0,\n flexDirection: 'column',\n marginBottom: 1,\n }}\n >\n <scrollbox\n ref={listScrollRef}\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, paddingLeft: 1, paddingRight: 1 }}\n >\n {payload.hunks.map((hunk, i) => {\n const isCursor = i === cursor && zone === 'list'\n const checked = mask[i]\n const marker = checked ? '✓' : '✗'\n const markerColor = checked ? COLOR.accent : COLOR.error\n const cursorGlyph = isCursor ? '▸ ' : ' '\n const previewText = oneLinePreview(hunk.oldString, hunk.newString)\n const res = preview.resolution[i]\n const unresolved = res?.resolved === false\n return (\n <box\n key={i}\n id={`edit-row-${i}`}\n style={{\n flexShrink: 0,\n backgroundColor: isCursor ? SURFACE.selection : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isCursor ? COLOR.brand : COLOR.mute}>{cursorGlyph}</span>\n <span fg={markerColor}>{`${marker} `}</span>\n <span fg={isCursor ? COLOR.brand : COLOR.dim}>{`#${(i + 1).toString().padStart(2)} `}</span>\n {/* Two trailing spaces, not one: `⚠` (U+26A0) is East-Asian-Width\n Ambiguous and most terminals render it 2 cells wide while OpenTUI's\n text layout reserves 1, which paints the next char over the glyph's\n second cell. Matches the workaround in the unresolved-hunk panel below. */}\n {unresolved && <span fg={COLOR.error}>{'! '}</span>}\n <span fg={unresolved ? COLOR.error : COLOR.dim}>{previewText}</span>\n </text>\n </box>\n )\n })}\n </scrollbox>\n </box>\n\n {/* Focused hunk panel. When the lenient resolver couldn't locate the\n model's `old_string` in the file (or the match was ambiguous\n and `replace_all` is off), render an explanatory fallback so\n the user understands why nothing would land — instead of an\n empty diff box. Same `width: panelWidth` rule as the list. */}\n {focusedResolved\n ? (\n <box\n style={{\n border: true,\n borderColor: COLOR.mute,\n width: panelWidth,\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n marginBottom: 1,\n }}\n >\n {/* Mirror the single-edit panel: the inner <diff> sizes to\n its intrinsic content via the Code measure func, the\n scrollbox bounds the viewport so a 100-line focused\n hunk can't push the action bar off-screen. Mouse-wheel\n drives scroll via OpenTUI's hit-tester regardless of\n focus; keyboard stays on the modal's approval handler.\n `diffScrollRef` is kept so the cursor-change effect\n resets `scrollTop = 0` when a new hunk is focused. */}\n <scrollbox\n ref={diffScrollRef}\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n <diff\n diff={focusedDiffText}\n view=\"unified\"\n wrapMode=\"word\"\n showLineNumbers={true}\n syntaxStyle={mdStyle}\n treeSitterClient={getTreeSitterClient()}\n {...(filetype ? { filetype } : {})}\n addedBg={SURFACE.diff.addBg}\n removedBg={SURFACE.diff.removeBg}\n {...(SURFACE.diff.addContentBg ? { addedContentBg: SURFACE.diff.addContentBg } : {})}\n {...(SURFACE.diff.removeContentBg ? { removedContentBg: SURFACE.diff.removeContentBg } : {})}\n addedSignColor={SURFACE.diff.addFg}\n removedSignColor={SURFACE.diff.removeFg}\n style={{ width: diffWidth, flexShrink: 0 }}\n />\n </scrollbox>\n </box>\n )\n : (\n <UnresolvedHunkPanel\n hunk={payload.hunks[cursor]}\n targetPath={targetPath}\n width={panelWidth}\n ambiguous={focusedAmbiguous}\n />\n )}\n\n <ActionBar\n actions={PER_EDIT_ACTIONS}\n selected={zone === 'actions' ? actionIdx : -1}\n customLabel={{ idx: 0, label: applyLabel }}\n />\n </box>\n {/* Top-right title overlay — sibling of the bordered modal so the\n renderer paints it on top of the top border. Selection-count\n rides `error` when nothing is checked / `accent` otherwise,\n mirroring the old inline header semantics. */}\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.model}>{filename}</span>\n {childSuffix.length > 0 && <span fg={COLOR.accent}>{childSuffix}</span>}\n <span fg={selectedCount === 0 ? COLOR.error : COLOR.accent}>{selectionSuffix}</span>\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n </box>\n )\n}\n\n/**\n * Compact one-line summary of an edit step for the list view. Keeps the\n * row a single cell tall regardless of how multi-line the actual edit\n * is — the focused-hunk diff below shows the full content.\n */\nfunction oneLinePreview(oldString: string, newString: string): string {\n const left = oneLine(oldString)\n const right = oneLine(newString)\n return `${left} → ${right}`\n}\n\nfunction oneLine(s: string): string {\n const collapsed = s.replace(/\\s+/g, ' ').trim()\n const max = 40\n return collapsed.length > max ? `${collapsed.slice(0, max)}…` : (collapsed || '(empty)')\n}\n\n// ---------------------------------------------------------------------------\n// UnresolvedHunkPanel — shown when the lenient resolver couldn't locate\n// the model's `old_string` in the file (or matched ambiguously without\n// `replace_all`). The user needs to see what the model *intended* even\n// though no diff is paintable; the tool would otherwise report this edit\n// as `failed` after the modal closes.\n// ---------------------------------------------------------------------------\n\ninterface UnresolvedHunkPanelProps {\n hunk: { oldString: string, newString: string } | undefined\n targetPath: string\n width: number\n ambiguous?: boolean\n}\n\nfunction UnresolvedHunkPanel({ hunk, targetPath, width, ambiguous }: UnresolvedHunkPanelProps) {\n const COLOR = useColors()\n const oldPreview = hunk ? snippet(hunk.oldString) : '(missing)'\n const newPreview = hunk ? snippet(hunk.newString) : '(missing)'\n const headline = ambiguous\n ? `old_string matches multiple places in ${targetPath} — set replace_all=true or expand the match.`\n : `old_string not found in ${targetPath}.`\n return (\n <box\n style={{\n border: true,\n borderColor: COLOR.error,\n width,\n // Claim the panel slot but stay shrinkable — on a tight terminal\n // a long unresolved snippet must not push the action bar past\n // the modal's bottom border. overflow:hidden clips any excess\n // paint at the warning panel's bounds.\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n marginBottom: 1,\n paddingLeft: 1,\n paddingRight: 1,\n paddingTop: 1,\n paddingBottom: 1,\n flexDirection: 'column',\n }}\n >\n <text wrapMode=\"word\">\n <span fg={COLOR.error}>{'! '}</span>\n <span fg={COLOR.error}>{headline}</span>\n </text>\n <text wrapMode=\"word\">\n <span fg={COLOR.mute}>{'Approving will report this edit as '}</span>\n <span fg={COLOR.error}>failed</span>\n <span fg={COLOR.mute}>{' once the tool runs. Deny to skip it.'}</span>\n </text>\n <box style={{ marginTop: 1, flexDirection: 'column', flexShrink: 0 }}>\n <text wrapMode=\"word\">\n <span fg={COLOR.mute}>{'old_string '}</span>\n <span fg={COLOR.dim}>{`(${hunk?.oldString.length ?? 0} chars)`}</span>\n </text>\n <text wrapMode=\"word\">\n <span fg={COLOR.error}>{oldPreview}</span>\n </text>\n </box>\n <box style={{ marginTop: 1, flexDirection: 'column', flexShrink: 0 }}>\n <text wrapMode=\"word\">\n <span fg={COLOR.mute}>{'new_string '}</span>\n <span fg={COLOR.dim}>{`(${hunk?.newString.length ?? 0} chars)`}</span>\n </text>\n <text wrapMode=\"word\">\n <span fg={COLOR.accent}>{newPreview}</span>\n </text>\n </box>\n </box>\n )\n}\n\nfunction snippet(s: string): string {\n const max = 240\n const trimmed = s.replace(/\\r\\n/g, '\\n')\n return trimmed.length > max ? `${trimmed.slice(0, max)}…` : (trimmed || '(empty)')\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable, ScrollBoxRenderable, TextareaRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type {\n AnswerValue,\n ConfirmQuestion,\n InteractionRequest,\n InteractionResponse,\n PlanDecision,\n PlanRequest,\n Question,\n QuestionRequest,\n SelectQuestion,\n TextQuestion,\n} from '../chat/interactions'\nimport { defaultTextareaKeyBindings } from '@opentui/core'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useColors } from '../chat/theme-context'\nimport { useModalAwareFocus } from './modal'\nimport { useMdStyle } from './theme'\n\n// ---------------------------------------------------------------------------\n// Textarea binding — `return` submits, `shift+return` inserts a newline.\n// Mirrors `makeSubmitBindings(true)` in `screens.tsx` but kept private so\n// this module has no cross-import on screens.tsx. Declared early so the\n// component bodies below can reference it without a forward use.\n// ---------------------------------------------------------------------------\n\nconst COMMENT_TEXTAREA_BINDINGS = (() => {\n const base = defaultTextareaKeyBindings.filter(\n b => b.name !== 'return' && !(b.name === 'a' && b.ctrl && !b.shift && !b.meta),\n )\n return [\n ...base,\n { name: 'a' as const, ctrl: true, action: 'select-all' as const },\n { name: 'return' as const, action: 'submit' as const },\n { name: 'return' as const, shift: true, action: 'newline' as const },\n ]\n})()\n\n/**\n * InteractionBlock — picker UI for `present_plan` and `ask_user` tool calls.\n *\n * Sits in the chat screen's prompt slot whenever the head of the\n * interactions queue is non-empty (live or resumed — the App layer\n * doesn't differentiate at this layer). Two surfaces driven off the\n * request's discriminator:\n *\n * - `plan` → renders the title + markdown body + (optional) step list,\n * then a 3-option decision picker. Reject and revise can flow into a\n * comment textarea for free-text feedback before final submit.\n *\n * - `question` → renders the question text, then a choice picker\n * (when the model provided choices) or a free-text answer textarea\n * (otherwise).\n *\n * Esc aborts the entire run via the parent keyboard handler; per-call\n * accept/deny only happens through the picker below — same convention\n * as `ApprovalBlock`.\n */\nexport function InteractionBlock({\n request,\n onResolve,\n}: {\n request: InteractionRequest\n /** Submit the user's response to the head of the queue. */\n onResolve: (response: InteractionResponse) => void\n}) {\n if (request.kind === 'plan')\n return <PlanInteractionBlock request={request} onResolve={onResolve} />\n return <QuestionInteractionBlock request={request} onResolve={onResolve} />\n}\n\n// ---------------------------------------------------------------------------\n// Shared shell — bordered container with a colored title + esc hint.\n//\n// `maxHeight` caps how tall the shell can grow when its content overflows;\n// the inner scrollbox (in the question wizard) takes the remaining space\n// inside the cap. Plans don't pass a cap — they're size-bounded by\n// `PLAN_BODY_MAX_LINES` and would look weird in a tiny scrollable window.\n// ---------------------------------------------------------------------------\n\n/** Subset of Yoga `maxHeight` values that OpenTUI's `<box>` accepts. */\ntype MaxHeightValue = number | `${number}%` | 'auto'\n\nfunction InteractionShell({\n title,\n maxHeight,\n children,\n}: {\n title: string\n /** Yoga `maxHeight` value — cells, percentage of parent, or `'auto'`. */\n maxHeight?: MaxHeightValue\n children: ReactNode\n}) {\n const COLOR = useColors()\n return (\n <box\n title={title}\n style={{\n border: true,\n borderColor: COLOR.warn,\n paddingLeft: 1,\n paddingRight: 1,\n flexDirection: 'column',\n flexShrink: 0,\n ...(maxHeight !== undefined ? { maxHeight } : {}),\n }}\n >\n {children}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Plan picker\n// ---------------------------------------------------------------------------\n\n/** Number of plan-body lines rendered inline before we truncate. */\nconst PLAN_BODY_MAX_LINES = 8\n\ninterface PlanOptionItem extends OptionItem {\n id: PlanDecision\n}\n\nconst PLAN_OPTIONS: readonly PlanOptionItem[] = [\n { id: 'approve', label: 'approve', description: 'accept the plan and continue' },\n { id: 'revise', label: 'revise', description: 'ask the agent to refine the plan' },\n { id: 'reject', label: 'reject', description: 'refuse the plan — the agent will see your feedback' },\n]\n\nfunction PlanInteractionBlock({\n request,\n onResolve,\n}: {\n request: PlanRequest\n onResolve: (response: InteractionResponse) => void\n}) {\n const COLOR = useColors()\n const mdStyle = useMdStyle()\n const focused = useModalAwareFocus()\n const [stage, setStage] = useState<'pick' | 'comment'>('pick')\n const [decision, setDecision] = useState<PlanDecision | null>(null)\n const textareaRef = useRef<TextareaRenderable | null>(null)\n\n const { title, plan, steps } = request.payload\n\n // Back-navigation — `shift+↹` from the comment stage returns to the\n // decision picker so a user who pressed `revise` or `reject` by\n // mistake can switch to `approve` without aborting the whole run.\n // The picker's cursor is fresh on return (no need to remember the\n // prior pick — the user just chose differently). No-op while on\n // the `pick` stage; the wizard isn't deep enough to need back.\n useKeyboard((key) => {\n if (!focused)\n return\n if (key.name !== 'tab' || !key.shift)\n return\n if (key.ctrl || key.meta || key.option)\n return\n if (stage !== 'comment')\n return\n setStage('pick')\n })\n\n // Plan body is rendered as markdown but capped at PLAN_BODY_MAX_LINES so a\n // verbose plan can't push the picker off-screen. The full plan stays in\n // the persisted tool_call.input — the user can always re-read via the\n // transcript / session-details modal.\n const { bodyText, truncated } = useMemo(() => {\n const lines = plan.split('\\n')\n if (lines.length <= PLAN_BODY_MAX_LINES)\n return { bodyText: plan, truncated: false }\n return {\n bodyText: lines.slice(0, PLAN_BODY_MAX_LINES).join('\\n'),\n truncated: true,\n }\n }, [plan])\n\n const onPick = useCallback((value: string) => {\n if (value === 'approve') {\n onResolve({ kind: 'plan', decision: 'approve' })\n return\n }\n setDecision(value as PlanDecision)\n setStage('comment')\n }, [onResolve])\n\n const onSubmitComment = useCallback(() => {\n const value = textareaRef.current?.plainText?.trim() ?? ''\n onResolve({\n kind: 'plan',\n decision: decision ?? 'revise',\n ...(value ? { comment: value } : {}),\n })\n }, [onResolve, decision])\n\n if (stage === 'comment') {\n return (\n <InteractionShell title={` ${decision === 'reject' ? 'reject plan' : 'revise plan'} · esc to abort run `}>\n <text fg={COLOR.dim} wrapMode=\"word\">\n Add an optional comment for the agent (\n <span fg={COLOR.warn}>↵</span>\n {' '}\n to submit,\n {' '}\n <span fg={COLOR.warn}>shift+↵</span>\n {' '}\n for a newline). Empty = no comment.\n </text>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 5,\n flexDirection: 'column',\n marginTop: 1,\n }}\n >\n <textarea\n ref={textareaRef}\n focused={focused}\n keyBindings={COMMENT_TEXTAREA_BINDINGS}\n placeholder=\"comment (optional)…\"\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={onSubmitComment}\n />\n </box>\n <text fg={COLOR.mute} style={{ marginTop: 1 }}>\n <span fg={COLOR.warn}>shift+↹</span>\n {' back to decision'}\n </text>\n </InteractionShell>\n )\n }\n\n return (\n <InteractionShell title=\" approve plan · esc to abort run \">\n <text fg={COLOR.brand} wrapMode=\"none\">\n {title}\n </text>\n <box style={{ flexDirection: 'column', marginTop: 1, marginBottom: 1 }}>\n <markdown\n content={bodyText}\n syntaxStyle={mdStyle}\n streaming={false}\n internalBlockMode=\"coalesced\"\n fg={COLOR.dim}\n />\n {truncated && (\n <text fg={COLOR.mute}>\n {`… plan body truncated (${plan.split('\\n').length - PLAN_BODY_MAX_LINES} more lines). Open the session details to see full content.`}\n </text>\n )}\n {steps && steps.length > 0 && (\n <box style={{ flexDirection: 'column', marginTop: 1 }}>\n <text fg={COLOR.mute}>steps:</text>\n {steps.map((step, i) => (\n <text key={step.id} fg={COLOR.dim} wrapMode=\"word\">\n <span fg={COLOR.warn}>{` ${i + 1}. `}</span>\n <span fg={COLOR.brand}>{step.title}</span>\n {step.description && (\n <span fg={COLOR.mute}>{` — ${step.description}`}</span>\n )}\n </text>\n ))}\n </box>\n )}\n </box>\n <OptionList items={PLAN_OPTIONS} onPick={onPick} />\n </InteractionShell>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Question wizard — one form, one or more questions, mixed types.\n//\n// Renders every question stacked so the user always sees the form's\n// shape and their progress. Only the active question shows an input;\n// completed ones show their answer summary, pending ones a placeholder.\n// On submit the active question advances; the LAST submit resolves the\n// whole batch with the `answers` record.\n//\n// Forward-only by design — once a question is answered the user can't\n// scroll back. If they need to revise, esc aborts the run and the\n// model re-issues the request.\n// ---------------------------------------------------------------------------\n\n/**\n * Cap the question-wizard's height to ~60% of the terminal so the\n * transcript above stays readable when the model fires a long batch.\n * The inner scrollbox handles overflow; the active question is\n * scrolled into view on every advance.\n */\nconst QUESTION_WIZARD_MAX_HEIGHT: MaxHeightValue = '60%'\n\nfunction QuestionInteractionBlock({\n request,\n onResolve,\n}: {\n request: QuestionRequest\n onResolve: (response: InteractionResponse) => void\n}) {\n const COLOR = useColors()\n const mdStyle = useMdStyle()\n const focused = useModalAwareFocus()\n const { intro, questions } = request.payload\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Seed answers with any `default` values declared by the model so the\n // user sees them pre-filled when they're rendered as a \"done\" summary\n // after being skipped (only possible when `required: false`).\n const [activeIndex, setActiveIndex] = useState(0)\n const [answers, setAnswers] = useState<Record<string, AnswerValue>>(() => {\n const initial: Record<string, AnswerValue> = {}\n for (const q of questions) {\n if ((q.type === 'text' || q.type === 'textarea') && q.default)\n initial[q.id] = q.default\n }\n return initial\n })\n\n const onAnswer = useCallback((id: string, value: AnswerValue) => {\n const next = { ...answers, [id]: value }\n if (activeIndex + 1 >= questions.length) {\n onResolve({ kind: 'question', answers: next })\n return\n }\n setAnswers(next)\n setActiveIndex(activeIndex + 1)\n }, [answers, activeIndex, questions, onResolve])\n\n // Back-navigation — `shift+tab` rewinds one step. The prior answer\n // stays in `answers` so the input re-mounts pre-filled (see\n // `QuestionRow`'s `initialAnswer` prop) and the user can edit\n // instead of retyping. No-op on the first question.\n //\n // Multi-question batches only: a single-question wizard has nothing\n // to go back to, so we suppress the affordance entirely. `cycleAgent`\n // (also `shift+tab`) is gated on `!pendingInteraction` in\n // `app.tsx`, so the same key doesn't cycle profiles while the\n // wizard owns the screen.\n const canGoBack = activeIndex > 0\n useKeyboard((key) => {\n if (!focused)\n return\n if (key.name !== 'tab' || !key.shift)\n return\n if (key.ctrl || key.meta || key.option)\n return\n if (!canGoBack)\n return\n setActiveIndex(i => Math.max(0, i - 1))\n })\n\n // Auto-scroll the active row into view whenever it changes — same\n // pattern as the transcript's selected-turn anchor effect. Deferred\n // one frame via `requestAnimationFrame` because OpenTUI's scrollbar\n // computes `scrollSize` during its post-commit layout pass; reading\n // a child's `y` / `height` synchronously after a React commit can\n // see stale measurements (the inactive→active swap changed the row's\n // height, and Yoga hasn't reflowed yet).\n //\n // The LAST question's submit resolves the whole interaction, which\n // unmounts this component, so no extra \"snap to bottom\" branch is\n // needed for that case.\n useEffect(() => {\n const scrollbox = scrollboxRef.current\n if (!scrollbox)\n return\n const activeQuestion = questions[activeIndex]\n if (!activeQuestion)\n return\n const handle = requestAnimationFrame(() => {\n scrollbox.scrollChildIntoView(anchorIdFor(activeQuestion.id))\n })\n return () => cancelAnimationFrame(handle)\n }, [activeIndex, questions])\n\n const titleLabel = questions.length > 1\n ? ` answer ${activeIndex + 1}/${questions.length} · esc to abort run `\n : ' answer · esc to abort run '\n\n return (\n <InteractionShell title={titleLabel} maxHeight={QUESTION_WIZARD_MAX_HEIGHT}>\n {intro && (\n <box style={{ flexDirection: 'column', marginBottom: 1, flexShrink: 0 }}>\n <markdown\n content={intro}\n syntaxStyle={mdStyle}\n streaming={false}\n internalBlockMode=\"coalesced\"\n fg={COLOR.dim}\n />\n </box>\n )}\n <scrollbox\n ref={scrollboxRef}\n // Never grab focus — option-list / textarea inputs own the\n // keyboard. Mouse-wheel still works as a fallback for users\n // who want to peek at pending questions below the fold.\n focusable={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n {questions.map((q, i) => (\n <QuestionRow\n key={q.id}\n anchorId={anchorIdFor(q.id)}\n index={i}\n question={q}\n status={i < activeIndex ? 'done' : i === activeIndex ? 'active' : 'pending'}\n answer={answers[q.id]}\n initialAnswer={i === activeIndex ? answers[q.id] : undefined}\n onAnswer={onAnswer}\n />\n ))}\n </scrollbox>\n {questions.length > 1 && (\n <text fg={COLOR.mute} style={{ marginTop: 1, flexShrink: 0 }}>\n {canGoBack && (\n <>\n <span fg={COLOR.warn}>shift+↹</span>\n {' back'}\n <span fg={COLOR.mute}>{' · '}</span>\n </>\n )}\n <span fg={COLOR.mute}>\n {`question ${activeIndex + 1} of ${questions.length}`}\n </span>\n </text>\n )}\n </InteractionShell>\n )\n}\n\n/**\n * Stable DOM-style id per question used as the `scrollChildIntoView`\n * target. Lives outside the component so the effect's deps don't need a\n * referentially-stable map.\n */\nfunction anchorIdFor(questionId: string): string {\n return `interaction-q-${questionId}`\n}\n\ntype QuestionStatus = 'done' | 'active' | 'pending'\n\n/**\n * One row of the question form — header + (input | summary | placeholder)\n * depending on the row's status. Lays its own vertical breathing room so\n * a batch of rows reads as a list without a parent `gap`.\n */\nfunction QuestionRow({\n anchorId,\n index,\n question,\n status,\n answer,\n initialAnswer,\n onAnswer,\n}: {\n /** Render id targeted by `ScrollBoxRenderable.scrollChildIntoView`. */\n anchorId: string\n index: number\n question: Question\n status: QuestionStatus\n /** Answer for a done row's summary — undefined for active / pending rows. */\n answer: AnswerValue | undefined\n /**\n * Pre-fill the active input with this value. Used by `shift+↹` back\n * navigation so revisiting a previously-answered question re-mounts\n * the input with the prior value editable in place. Defaults to\n * `question.default` for free-text types when no prior answer\n * exists.\n */\n initialAnswer: AnswerValue | undefined\n onAnswer: (id: string, value: AnswerValue) => void\n}) {\n const COLOR = useColors()\n\n // Status marker matches the visual hierarchy: ✓ done, ▶ active, · pending.\n // The active row's prompt rides `brand` so it stands out from context.\n const marker = status === 'done' ? '✓' : status === 'active' ? '▶' : '·'\n const markerColor\n = status === 'done'\n ? COLOR.accent\n : status === 'active'\n ? COLOR.brand\n : COLOR.mute\n const promptColor = status === 'active' ? COLOR.brand : COLOR.dim\n\n return (\n <box id={anchorId} style={{ flexDirection: 'column', marginBottom: 1, flexShrink: 0 }}>\n <text wrapMode=\"word\">\n <span fg={markerColor}>{`${marker} `}</span>\n <span fg={COLOR.mute}>{`${index + 1}. `}</span>\n <span fg={promptColor}>{question.prompt}</span>\n </text>\n {question.description && (\n <text fg={COLOR.mute} wrapMode=\"word\">\n {` ${question.description}`}\n </text>\n )}\n <box style={{ flexDirection: 'column', marginTop: activeMarginTopFor(status, question.type) }}>\n {status === 'active' && (\n <ActiveQuestionInput\n question={question}\n initialAnswer={initialAnswer}\n onSubmit={value => onAnswer(question.id, value)}\n />\n )}\n {status === 'done' && (\n <AnswerSummary question={question} answer={answer} />\n )}\n {status === 'pending' && (\n <text fg={COLOR.mute}>{` waiting…`}</text>\n )}\n </box>\n </box>\n )\n}\n\n/**\n * Per-type breathing room between the question header and its body.\n *\n * - `select` / `confirm` (active): 0 — the inline option list reads as\n * a tight continuation of the prompt (`▶ 2. Pick one` → ` ↳ react`).\n * - `text` / `textarea` (active): 1 — the bordered input box needs a\n * visible gap or it sits glued to the prompt line.\n * - Non-active rows: 0 — the summary / `waiting…` line sits flush.\n */\nfunction activeMarginTopFor(status: QuestionStatus, type: Question['type']): number {\n if (status !== 'active')\n return 0\n return type === 'text' || type === 'textarea' ? 1 : 0\n}\n\n/** Render a finalized answer as compact text below the question header. */\nfunction AnswerSummary({\n question,\n answer,\n}: {\n question: Question\n answer: AnswerValue | undefined\n}) {\n const COLOR = useColors()\n const text = formatAnswerForDisplay(question, answer)\n return (\n <text fg={COLOR.dim} wrapMode=\"word\">\n <span fg={COLOR.mute}>{' ↳ '}</span>\n {text}\n </text>\n )\n}\n\nfunction formatAnswerForDisplay(question: Question, answer: AnswerValue | undefined): string {\n if (answer === undefined || answer === '')\n return '(skipped)'\n if (question.type === 'confirm') {\n return typeof answer === 'boolean'\n ? (answer ? (question.affirmLabel ?? 'yes') : (question.denyLabel ?? 'no'))\n : '(invalid)'\n }\n if (question.type === 'select') {\n const choice = question.choices.find(c => c.id === answer)\n return choice ? choice.label : String(answer)\n }\n // text / textarea — collapse newlines for the one-line summary; the\n // full answer still rides the persisted tool_result for the model.\n if (typeof answer !== 'string')\n return '(invalid)'\n const oneLine = answer.replace(/\\s+/g, ' ').trim()\n return oneLine.length > 80 ? `${oneLine.slice(0, 80)}…` : oneLine\n}\n\n// ---------------------------------------------------------------------------\n// Active question — dispatches to a per-type input renderer.\n// ---------------------------------------------------------------------------\n\nfunction ActiveQuestionInput({\n question,\n initialAnswer,\n onSubmit,\n}: {\n question: Question\n /**\n * Carried answer to pre-fill the input. For text types this becomes\n * the textarea's `initialValue` / input's `value`; for select /\n * confirm it positions the cursor on the matching option. `undefined`\n * means \"first time on this question\" — fall back to the model's\n * `default` (text) or cursor 0 (select / confirm).\n */\n initialAnswer: AnswerValue | undefined\n onSubmit: (value: AnswerValue) => void\n}) {\n switch (question.type) {\n case 'text':\n return (\n <TextQuestionInput\n question={question}\n initialValue={typeof initialAnswer === 'string' ? initialAnswer : (question.default ?? '')}\n onSubmit={onSubmit}\n multiLine={false}\n />\n )\n case 'textarea':\n return (\n <TextQuestionInput\n question={question}\n initialValue={typeof initialAnswer === 'string' ? initialAnswer : (question.default ?? '')}\n onSubmit={onSubmit}\n multiLine\n />\n )\n case 'select':\n return (\n <SelectQuestionInput\n question={question}\n initialChoiceId={typeof initialAnswer === 'string' ? initialAnswer : undefined}\n onSubmit={onSubmit}\n />\n )\n case 'confirm':\n return (\n <ConfirmQuestionInput\n question={question}\n initialValue={typeof initialAnswer === 'boolean' ? initialAnswer : undefined}\n onSubmit={onSubmit}\n />\n )\n }\n}\n\n/**\n * Single- or multi-line text input. The `multiLine` flavor uses\n * `<textarea>` with `shift+↵` for newline; the single-line flavor uses\n * OpenTUI's `<input>` and submits on plain `↵`. Both honor the\n * question's `placeholder` and `default`.\n *\n * Required-field handling: an empty answer is allowed only when\n * `question.required` is explicitly `false`. For required free-text\n * questions the submit is ignored on an empty buffer so the user\n * never gets stuck pressing enter \"by accident\".\n */\nfunction TextQuestionInput({\n question,\n initialValue,\n onSubmit,\n multiLine,\n}: {\n question: TextQuestion\n /**\n * Buffer the input mounts with. Caller resolves the `priorAnswer →\n * question.default → empty` precedence so this component stays\n * dumb (one source of truth = the prop).\n */\n initialValue: string\n onSubmit: (value: AnswerValue) => void\n multiLine: boolean\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const textareaRef = useRef<TextareaRenderable | null>(null)\n const inputRef = useRef<InputRenderable | null>(null)\n // Required free-text answers default to FALSE — the user can skip and\n // the model will treat it as an empty answer. Explicit `required:\n // true` flips the flag and the submit handler refuses an empty buffer.\n const required = question.required ?? false\n\n const submit = useCallback(() => {\n const value = multiLine\n ? (textareaRef.current?.plainText ?? '')\n : (inputRef.current?.value ?? '')\n if (required && !value.trim())\n return\n onSubmit(value)\n }, [onSubmit, multiLine, required])\n\n const defaultPlaceholder = multiLine\n ? 'type your answer (↵ submit · shift+↵ newline)…'\n : 'type your answer (↵ submit)…'\n const placeholder = question.placeholder ?? defaultPlaceholder\n\n if (multiLine) {\n return (\n <box style={{ flexDirection: 'column' }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 5,\n flexDirection: 'column',\n }}\n >\n <textarea\n ref={textareaRef}\n focused={focused}\n keyBindings={COMMENT_TEXTAREA_BINDINGS}\n placeholder={placeholder}\n initialValue={initialValue}\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={submit}\n />\n </box>\n <TextSubmitHint multiLine required={required} />\n </box>\n )\n }\n\n return (\n <box style={{ flexDirection: 'column' }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n focused={focused}\n value={initialValue}\n placeholder={placeholder}\n onSubmit={submit}\n style={{ flexGrow: 1 }}\n />\n </box>\n <TextSubmitHint multiLine={false} required={required} />\n </box>\n )\n}\n\n/**\n * Submit-row hint shown below `TextQuestionInput`. Renders the\n * relevant submit shortcut + an optional `required` badge so the user\n * sees up-front whether an empty answer will be accepted. Shared\n * across the single-line / multi-line branches to keep the visual\n * contract identical.\n */\nfunction TextSubmitHint({ multiLine, required }: { multiLine: boolean, required: boolean }) {\n const COLOR = useColors()\n return (\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↵</span>\n {' submit'}\n {multiLine && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.warn}>shift+↵</span>\n {' newline'}\n </>\n )}\n {required && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.warn}>required</span>\n </>\n )}\n </text>\n )\n}\n\n/** Single-choice picker — one row per choice, returns the choice id. */\nfunction SelectQuestionInput({\n question,\n initialChoiceId,\n onSubmit,\n}: {\n question: SelectQuestion\n /** Pre-position the cursor on this choice id when re-entering the question. */\n initialChoiceId: string | undefined\n onSubmit: (value: AnswerValue) => void\n}) {\n const items = useMemo(\n () => question.choices.map(c => ({\n id: c.id,\n label: c.label,\n ...(c.description ? { description: c.description } : {}),\n })),\n [question.choices],\n )\n const initialCursor = useMemo(() => {\n if (initialChoiceId === undefined)\n return 0\n const idx = items.findIndex(i => i.id === initialChoiceId)\n return idx === -1 ? 0 : idx\n }, [items, initialChoiceId])\n return <OptionList items={items} initialCursor={initialCursor} onPick={id => onSubmit(id)} />\n}\n\n/** Yes/no picker — returns a boolean. */\nfunction ConfirmQuestionInput({\n question,\n initialValue,\n onSubmit,\n}: {\n question: ConfirmQuestion\n /** Pre-position the cursor on yes (true) or no (false) when re-entering. */\n initialValue: boolean | undefined\n onSubmit: (value: AnswerValue) => void\n}) {\n const items = useMemo(\n () => [\n { id: 'yes', label: question.affirmLabel ?? 'yes' },\n { id: 'no', label: question.denyLabel ?? 'no' },\n ],\n [question.affirmLabel, question.denyLabel],\n )\n const initialCursor = initialValue === false ? 1 : 0\n return <OptionList items={items} initialCursor={initialCursor} onPick={id => onSubmit(id === 'yes')} />\n}\n\n/**\n * Inline option list rendered as text rows — replaces OpenTUI's `<select>`\n * so the focused option uses the same `↳ <label>` format and indentation\n * as a finished question's `AnswerSummary`. Visually, the active row in\n * the form reads as the answer the user is in the middle of picking.\n *\n * Same pattern as `SessionsScreen` / `SettingsModal`: custom rendering\n * + a local `useKeyboard` listener. Renders one `<text>` per option,\n * with the focused row in `brand` and others in `dim`. `up`/`down`/\n * `j`/`k` move the cursor; `return` commits.\n *\n * Indent budget — 3 columns total:\n * - ` ` 3-space pad to align with the question prompt's text column\n * (the question header eats `▶ 1. ` = 5 cells, but the body text\n * starts at the prompt — we mirror the ` ↳ ` indent the\n * `AnswerSummary` uses for done rows).\n * - `↳ ` or `· ` glyph (focused vs unfocused).\n *\n * Wraps on horizontal edges (`up` at top → bottom, `down` at bottom →\n * top), matching the OpenTUI `wrapSelection` behavior.\n */\ninterface OptionItem {\n id: string\n label: string\n description?: string\n}\n\nfunction OptionList({\n items,\n initialCursor,\n onPick,\n}: {\n items: readonly OptionItem[]\n /** Seed the cursor on a specific row at mount. Clamped to bounds. Defaults to 0. */\n initialCursor?: number\n onPick: (id: string) => void\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const [cursor, setCursor] = useState(() => {\n if (typeof initialCursor !== 'number' || items.length === 0)\n return 0\n return Math.max(0, Math.min(items.length - 1, initialCursor))\n })\n\n const moveCursor = useCallback((delta: -1 | 1) => {\n setCursor((c) => {\n if (items.length === 0)\n return c\n return ((c + delta) % items.length + items.length) % items.length\n })\n }, [items.length])\n\n useKeyboard((key) => {\n if (!focused)\n return\n // Drop any key with a modifier so global shortcuts (ctrl+o, esc, …)\n // still reach the AppShell's keyboard handler while an option list\n // is mounted. OpenTUI's `ParsedKey` exposes `option` for Alt.\n if (key.ctrl || key.meta || key.shift || key.option)\n return\n if (key.name === 'up' || key.name === 'k') {\n moveCursor(-1)\n return\n }\n if (key.name === 'down' || key.name === 'j') {\n moveCursor(1)\n return\n }\n if (key.name === 'return') {\n const item = items[cursor]\n if (item)\n onPick(item.id)\n }\n })\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {items.map((item, i) => {\n const isCursor = i === cursor\n return (\n <text key={item.id} wrapMode=\"word\">\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isCursor ? COLOR.brand : COLOR.mute}>\n {isCursor ? '↳ ' : '· '}\n </span>\n <span fg={isCursor ? COLOR.brand : COLOR.dim}>{item.label}</span>\n {item.description && (\n <span fg={COLOR.mute}>{` · ${item.description}`}</span>\n )}\n </text>\n )\n })}\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport { useTerminalDimensions } from '@opentui/react'\n\n/**\n * Long URL rendered as N single-row OSC 8 hyperlinks instead of one\n * wrapped hyperlink.\n *\n * Why split: OpenTUI packs a `linkId` per cell into the attribute\n * bitfield, but its renderer emits an OSC 8 open/close pair per visual\n * row without the spec's `id=` parameter. Terminals (notably iTerm2)\n * need a matching `id=` to stitch hyperlink fragments across rows —\n * without it, only one row of a wrapped link ends up clickable. We\n * pre-chunk the URL into rows that fit on one line and render each as\n * its own intact `<a href>` with `wrapMode=\"none\"`, so the terminal\n * never sees a wrapped hyperlink and clicking any row opens the full\n * URL.\n *\n * Width: caller passes a hard cap (its container's content-area width).\n * We further clamp by the live terminal width minus `chromeWidth` so a\n * narrow terminal still chunks short enough to avoid forced wrap by\n * the layout engine. `chromeWidth` is the container's border + padding\n * budget — default 14 fits a typical modal; pass a smaller value for\n * less-padded containers (e.g. 6 for the auth wizard panel).\n */\nexport function OAuthUrlBlock({\n url,\n fg,\n maxLineWidth,\n chromeWidth = 14,\n}: {\n url: string\n fg: string | undefined\n /** Caller's container content-area width cap (container width minus padding+border). */\n maxLineWidth: number\n /** Reserved columns for the container's own border + padding. Defaults to a modal's budget. */\n chromeWidth?: number\n}) {\n const { width: termWidth } = useTerminalDimensions()\n const chunkWidth = Math.max(20, Math.min(maxLineWidth, termWidth - chromeWidth))\n const lines = chunkString(url, chunkWidth)\n return (\n <>\n {lines.map((line, i) => (\n <text key={i} wrapMode=\"none\" fg={fg}>\n <a href={url}>{line}</a>\n </text>\n ))}\n </>\n )\n}\n\nfunction chunkString(s: string, n: number): string[] {\n if (s.length <= n)\n return [s]\n const out: string[] = []\n for (let i = 0; i < s.length; i += n)\n out.push(s.slice(i, i + n))\n return out\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable } from '@opentui/core'\nimport type { RefObject } from 'react'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { tryOpenBrowser } from '../chat/browser'\nimport { fetchOAuthRedirect } from '../chat/oauth-redirect'\nimport { useColors } from '../chat/theme-context'\nimport { errorMessage } from '../errors'\nimport { writeToClipboard } from './clipboard'\nimport { OAuthUrlBlock } from './oauth-url-block'\n\n/** Keystroke shown next to the open-browser button. */\nconst OPEN_BROWSER_KEY = 'ctrl+b'\n\n/**\n * Unified affordance for surfacing an OAuth authorization URL in the TUI:\n *\n * 1. A prominent OSC 8 hyperlink button — \"Click here to open auth URL in\n * browser\". Honored by every terminal that supports clickable links\n * (iTerm2, Kitty, WezTerm, Ghostty, Alacritty, …). Lands on the user's\n * LOCAL terminal even over SSH because OSC 8 is interpreted client-side.\n * 2. The full URL rendered greyed below, chunked into per-row hyperlinks\n * via {@link OAuthUrlBlock}. Backup channel for terminals without OSC 8,\n * and for users who'd rather copy via drag-select than click.\n * 3. Optional paste-back input. When `paste` is provided, an `<input>`\n * renders below the URL so the user can paste the FULL redirect URL\n * their browser ended up at after authorizing — useful when zidane runs\n * over SSH and the browser-side redirect to loopback can't reach the\n * remote callback server. The caller's `onSubmit` typically pipes the\n * pasted value through {@link fetchOAuthRedirect} so the in-process\n * server receives the request and the OAuth promise resolves through\n * the same happy path a real browser would have taken.\n *\n * The component owns presentation only — keyboard focus, input ref, and\n * the submit handler stay with the caller so the affordance composes\n * cleanly with whatever picker / wizard / modal it lives in.\n */\nexport function OAuthAuthBlock({\n authUrl,\n maxLineWidth,\n chromeWidth = 14,\n paste,\n}: {\n authUrl: string\n /** Container content-area width cap (modal/panel width minus border + padding). */\n maxLineWidth: number\n /** Reserved columns for the container's own border + padding. */\n chromeWidth?: number\n paste?: {\n /** Ref the caller reads `value` from on submit. */\n inputRef: RefObject<InputRenderable | null>\n /** Whether the input should grab keys (caller controls focus). */\n focused: boolean\n /** Fired on `enter` — caller reads the input's value and handles the fetch. */\n onSubmit: () => void\n /** Optional override for the input placeholder. */\n placeholder?: string\n /** Optional inline error / progress message rendered above the input. */\n hint?: { text: string, tone: 'dim' | 'error' | 'accent' }\n }\n}) {\n const COLOR = useColors()\n return (\n <box style={{ flexDirection: 'column', gap: 1 }}>\n <OpenBrowserButton authUrl={authUrl} />\n <box style={{ flexDirection: 'column' }}>\n <text fg={COLOR.mute}>or copy the URL manually:</text>\n <OAuthUrlBlock\n url={authUrl}\n fg={COLOR.dim}\n maxLineWidth={maxLineWidth}\n chromeWidth={chromeWidth}\n />\n </box>\n {paste && (\n <box style={{ flexDirection: 'column' }}>\n <text fg={COLOR.mute}>\n or — if the browser couldn't reach this machine (SSH, firewall) — paste the URL it tried to redirect to:\n </text>\n {paste.hint && (\n <text fg={toneColor(paste.hint.tone, COLOR)}>{paste.hint.text}</text>\n )}\n <box\n style={{\n border: true,\n borderColor: paste.focused ? COLOR.borderActive : COLOR.border,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={paste.inputRef}\n focused={paste.focused}\n placeholder={paste.placeholder ?? 'paste redirect URL and press enter…'}\n onSubmit={paste.onSubmit}\n style={{ flexGrow: 1 }}\n />\n </box>\n </box>\n )}\n </box>\n )\n}\n\n/**\n * Real button: a bordered, brand-colored box hosting a labeled keystroke\n * hint. Pressing `ctrl+b` (anywhere this block is mounted) calls\n * {@link tryOpenBrowser} to launch the URL via the OS handler AND\n * {@link writeToClipboard} so the URL also lands in the user's clipboard\n * (OSC 52 → works over SSH, native helper → works locally). Both fire\n * because either alone can fail silently — `open`/`xdg-open` are no-ops\n * on a headless box, and OSC 52 is disabled on some terminals — and the\n * combination covers both failure modes without prompting again.\n *\n * The visible label is also wrapped in an OSC 8 hyperlink so a mouse\n * click in a supporting terminal (iTerm2, Kitty, WezTerm, …) reaches the\n * same browser. The keybind is the authoritative path — it doesn't\n * depend on terminal capability — and the hyperlink is the bonus.\n */\nfunction OpenBrowserButton({ authUrl }: { authUrl: string }) {\n const COLOR = useColors()\n const [feedback, setFeedback] = useState<string | null>(null)\n const feedbackTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const trigger = useCallback(() => {\n tryOpenBrowser(authUrl)\n const copied = writeToClipboard(authUrl)\n setFeedback(copied\n ? 'opened in browser · URL copied to clipboard'\n : 'opened in browser')\n if (feedbackTimer.current)\n clearTimeout(feedbackTimer.current)\n feedbackTimer.current = setTimeout(setFeedback, 4000, null)\n }, [authUrl])\n\n useKeyboard((key) => {\n // `ctrl+b` is not in OpenTUI's `defaultTextareaKeyBindings` action\n // table, so the focused paste input ignores it — meaning this\n // listener can fire regardless of which input owns focus.\n if (key.ctrl && key.name === 'b') {\n key.preventDefault()\n trigger()\n }\n })\n\n useEffect(() => () => {\n if (feedbackTimer.current)\n clearTimeout(feedbackTimer.current)\n }, [])\n\n return (\n <box style={{ flexDirection: 'column' }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.brand,\n paddingLeft: 1,\n paddingRight: 1,\n alignSelf: 'flex-start',\n }}\n >\n <text wrapMode=\"none\">\n <a href={authUrl} fg={COLOR.brand}>↗ Open auth URL in browser</a>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.warn}>{OPEN_BROWSER_KEY}</span>\n <span fg={COLOR.mute}>{' open · click also works'}</span>\n </text>\n </box>\n {feedback && (\n <text fg={COLOR.accent}>{`✓ ${feedback}`}</text>\n )}\n </box>\n )\n}\n\nfunction toneColor(\n tone: 'dim' | 'error' | 'accent',\n COLOR: ReturnType<typeof useColors>,\n): string {\n switch (tone) {\n case 'error': return COLOR.error\n case 'accent': return COLOR.accent\n case 'dim': return COLOR.dim\n }\n}\n\n/**\n * Self-contained MCP authorizing panel:\n *\n * - Renders the {@link OAuthAuthBlock} for the URL + paste input.\n * - Owns the input ref, the submit handler, and the hint state.\n * - Auto-fetches the pasted URL via {@link fetchOAuthRedirect} so the\n * MCP SDK's loopback callback server resolves the OAuth promise\n * through the same code path a real browser-redirect would have\n * taken — works over SSH where the browser can't reach the remote\n * callback server directly.\n *\n * `inputFocused` is forwarded as the input's `focused` prop AND read by\n * the parent picker's `useKeyboard` (via a ref) to suppress single-key\n * shortcuts (`l` / `o` / `r`) while the user is typing into the input.\n */\nexport function McpAuthorizingPanel({\n serverName,\n authUrl,\n maxLineWidth,\n chromeWidth,\n inputFocused,\n}: {\n serverName: string\n authUrl: string\n maxLineWidth: number\n chromeWidth?: number\n inputFocused: boolean\n}) {\n const COLOR = useColors()\n const inputRef = useRef<InputRenderable | null>(null)\n const [hint, setHint] = useState<{ text: string, tone: 'dim' | 'error' | 'accent' } | null>(null)\n\n const onSubmit = useCallback(() => {\n const value = inputRef.current?.value?.trim() ?? ''\n if (!value)\n return\n setHint({ text: 'submitting redirect URL…', tone: 'dim' })\n void (async () => {\n try {\n const result = await fetchOAuthRedirect(value)\n if (result.status >= 200 && result.status < 300) {\n setHint({ text: 'redirect accepted — finalizing…', tone: 'accent' })\n }\n else {\n setHint({\n text: `callback server rejected the URL (${result.status}${result.message ? `: ${result.message}` : ''}). Cancel and retry.`,\n tone: 'error',\n })\n }\n }\n catch (err) {\n setHint({ text: errorMessage(err), tone: 'error' })\n }\n })()\n }, [])\n\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.brand}>{`Authorizing ${serverName}`}</text>\n <text fg={COLOR.dim}>\n Your browser should have opened — complete the login, then return here.\n </text>\n <OAuthAuthBlock\n authUrl={authUrl}\n maxLineWidth={maxLineWidth}\n chromeWidth={chromeWidth}\n paste={{\n inputRef,\n focused: inputFocused,\n onSubmit,\n placeholder: 'paste redirect URL and press enter…',\n hint: hint ?? undefined,\n }}\n />\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { Session } from '../session'\nimport { useTerminalDimensions } from '@opentui/react'\nimport { useSettings } from '../chat/settings-context'\nimport { useColors } from '../chat/theme-context'\nimport { TODO_STATUS_GLYPHS, useActiveTodos } from '../chat/todos'\n\n// ---------------------------------------------------------------------------\n// TodoIndicator — single-line \"what is the agent working on?\" badge\n// above the prompt input.\n//\n// ◐ Currently in-progress todo title (truncated with ellipsis)\n//\n// Hides entirely (returns null) when:\n// - No session.\n// - No active run.\n// - Active run's todo list has no `in_progress` item.\n// - `Settings.showTodoIndicator` is off.\n//\n// Read-only — not focusable, not clickable. The user opens the full\n// list via `ctrl+t` (the `openTodos` keybinding); this bar is a\n// passive surface that surfaces the latest checkpoint at a glance.\n//\n// Renderer chrome only — the data slice + run resolution lives in\n// `chat/todos.ts`'s `useActiveTodos` so a future GUI shell can reuse it.\n// ---------------------------------------------------------------------------\n\ninterface TodoIndicatorProps {\n session: Session | null\n}\n\n/**\n * Layout budget when truncating the content. The bar never wraps —\n * single-line by contract — so we subtract every column consumed by\n * surrounding chrome before measuring how much room is left for the\n * todo's `content` text.\n *\n * - 2 cols ⇒ `<ChatScreen>`'s `border: true` (1 cell each side).\n * - 2 cols ⇒ this indicator's own `paddingLeft: 1 + paddingRight: 1`\n * (mirrors the queue block's outer padding so the bar\n * and queue read as aligned siblings).\n * - 4 cols ⇒ the indicator's own visible chrome: glyph \"◐\" (1) +\n * two spaces (2) + 1 col of trailing breathing room.\n *\n * That's 8 cols total of non-content overhead. Below `MIN_VISIBLE_TAIL`\n * the bar bails — a one-or-two-char tail isn't worth painting.\n */\nconst SCREEN_BORDER_COLS = 2\nconst INDICATOR_PADDING_COLS = 2\nconst CHROME_COLS = 4\nconst MIN_VISIBLE_TAIL = 8\n\nfunction truncateForWidth(text: string, max: number): string {\n if (max <= 0)\n return ''\n if (text.length <= max)\n return text\n if (max <= 1)\n return '…'\n return `${text.slice(0, max - 1)}…`\n}\n\nexport function TodoIndicator({ session }: TodoIndicatorProps) {\n const { settings } = useSettings()\n const COLOR = useColors()\n const { width: termWidth } = useTerminalDimensions()\n // Hooks before any early returns — keeps React's hook order stable.\n const state = useActiveTodos(session)\n\n if (!settings.showTodoIndicator)\n return null\n const item = state.inProgress\n if (!item)\n return null\n\n // Inside the ChatScreen's bordered outer box (1 cell each horizontal\n // side) and this indicator's own padding (1 each), the content area\n // is `termWidth - 4`. Subtract the visible chrome (glyph + spaces +\n // breathing room) to get the budget for the truncated content.\n const usable = Math.max(0, termWidth - SCREEN_BORDER_COLS - INDICATOR_PADDING_COLS - CHROME_COLS)\n if (usable < MIN_VISIBLE_TAIL)\n return null\n\n // Collapse internal whitespace + line breaks so the bar stays one\n // visual line even if a model emitted a multi-line `content`.\n const content = item.content.replace(/\\s+/g, ' ').trim()\n const display = truncateForWidth(content, usable)\n\n return (\n <box\n style={{\n // Hairline gap above so the bar visually separates from\n // whatever sits above (queue rows or the transcript bottom),\n // mirroring the queue box / interaction block spacing rules.\n marginTop: 1,\n flexShrink: 0,\n flexDirection: 'row',\n paddingLeft: 1,\n paddingRight: 1,\n }}\n >\n <text wrapMode=\"none\">\n {/* Glyph picks up the live-state `warn` tone — same color the\n modal uses for the in-progress row. Single source of truth\n for the visual language between the two surfaces. */}\n <span fg={COLOR.warn}>{TODO_STATUS_GLYPHS.in_progress}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.dim}>{display}</span>\n </text>\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable, KeyEvent, PasteEvent, ScrollBoxRenderable, TextareaRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type { ProviderAuth } from '../chat/auth'\nimport type { CompletionProvider, CompletionReference } from '../chat/completion'\nimport type { Hint } from '../chat/hints'\nimport type {\n InteractionRequest,\n InteractionResponse,\n} from '../chat/interactions'\nimport type { ProviderDescriptor } from '../chat/providers'\nimport type { ApprovalDecision, ApprovalRequest } from '../chat/safe-mode-context'\nimport type { SessionMeta, Settings, StreamEvent } from '../chat/types'\nimport type { Session } from '../session'\nimport type { MetaSegment } from './components'\nimport { Buffer } from 'node:buffer'\nimport { readFileSync, statSync } from 'node:fs'\nimport { basename } from 'node:path'\nimport { decodePasteBytes, defaultTextareaKeyBindings, stripAnsiSequences } from '@opentui/core'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { detectAuth } from '../chat/auth'\nimport { useCompletion } from '../chat/completion'\nimport { useConfig } from '../chat/config-context'\nimport { setProviderCredential } from '../chat/credentials'\nimport { ageString, compactPath } from '../chat/format'\nimport { clipHintsToWidth, EMPTY_HINTS, hintsLength } from '../chat/hints'\nimport { formatBindingForDisplay } from '../chat/keybindings'\nimport { oauthUsesManualCodePaste, runOAuthLogin, supportsOAuth } from '../chat/oauth'\nimport { fetchOAuthRedirect } from '../chat/oauth-redirect'\nimport { suggestSafelistEntry } from '../chat/safe-mode'\nimport { useSettings } from '../chat/settings-context'\nimport { resolveChipColor } from '../chat/theme'\nimport { useColors, useSelectStyle, useSurfaces } from '../chat/theme-context'\nimport { errorMessage } from '../errors'\nimport { CompletionPopup } from './completion-popup'\nimport { renderHintSpans, Spinner, TitleOverlay, Transcript } from './components'\nimport { FileEditApprovalModal, isFileEditTool } from './file-edit-approval-modal'\nimport { InteractionBlock } from './interaction-block'\nimport { useModal, useModalAwareFocus } from './modal'\nimport { OAuthAuthBlock } from './oauth-auth-block'\nimport { useChipHighlights, useChipStyle } from './theme'\nimport { TodoIndicator } from './todo-indicator'\n\n/**\n * Prompt-level attachment captured by the textarea's paste pipeline.\n * Lives only in the TUI layer — converted to {@link PromptPart}s at\n * `agent.run()` time. Internal buffer-based shape is more convenient\n * than the wire format (which is base64 for binary and raw text for\n * text/plain).\n */\nexport interface Attachment {\n name: string\n content: Buffer\n mediaType: string\n}\n\n/**\n * Build a key-binding set for the prompt textarea / API-key input. Strips the\n * default `return` action and reinstalls it with our preferred meaning, so the\n * binding wins regardless of modifier state. Pass `allowShiftReturnNewline`\n * to enable `shift+enter` → newline (multi-line input).\n *\n * Also overrides `ctrl+a`: OpenTUI's default is the emacs-style `line-home`,\n * but every modern editor + browser binds it to \"select all\". The previous\n * behavior is still reachable via `home` / `ctrl+e` (line ends). Net effect:\n * users can press ctrl+a then any printable key (or backspace) to clear the\n * prompt the way they expect.\n */\nfunction makeSubmitBindings(allowShiftReturnNewline: boolean) {\n const base = defaultTextareaKeyBindings.filter(\n b => b.name !== 'return' && !(b.name === 'a' && b.ctrl && !b.shift && !b.meta),\n )\n const overrides = [\n { name: 'a', ctrl: true, action: 'select-all' as const },\n { name: 'return', action: 'submit' as const },\n ]\n return allowShiftReturnNewline\n ? [...base, ...overrides, { name: 'return', shift: true, action: 'newline' as const }]\n : [...base, ...overrides]\n}\n\nconst TEXTAREA_BINDINGS = makeSubmitBindings(true)\nconst API_KEY_INPUT_BINDINGS = makeSubmitBindings(false)\n\n/**\n * Look up a `{ key }` item by the value of a `<select>` option. Used by every\n * screen that mixes keyed-entry rows with sentinel \"+ new\" / \"← back\" rows —\n * sentinel handling stays explicit at the call site, this helper just trims\n * the boilerplate `.find(i => i.key === ...)` typing.\n */\nfunction findByKey<T extends { key: string }>(items: readonly T[], value: unknown): T | undefined {\n return typeof value === 'string' ? items.find(i => i.key === value) : undefined\n}\n\n// ---------------------------------------------------------------------------\n// AuthScreen — first-run provider picker.\n// ---------------------------------------------------------------------------\n\n/**\n * Sentinel value used by the picker's \"+ add / re-configure\" option. Lives\n * outside the provider key namespace (key strings are at least one char, no\n * leading `__`) so we can't collide with a real registry entry.\n */\nconst WIZARD_OPTION_VALUE = '__wizard__'\n\nexport function AuthScreen({ onPick }: { onPick: (p: ProviderAuth) => void }) {\n const config = useConfig()\n const { providers: registry } = config\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n\n // `providers` is state, not a memo, so the wizard can imperatively\n // refresh it after writing credentials without us reaching for `useMemo`\n // dep tricks. The effect below seeds it on mount + whenever the registry\n // changes; `refresh` does the wizard-triggered re-detect.\n //\n // Credentials always live under the USER dir — never under a project's\n // `.{prefix}/` (which can be checked in). `paths.userDir` makes that\n // explicit; `paths.dir` would silently land creds in the project dir\n // when project-db mode is on.\n const [providers, setProviders] = useState<ProviderAuth[]>([])\n const refresh = useCallback(\n () => setProviders(detectAuth(config.paths.userDir, registry)),\n [config.paths.userDir, registry],\n )\n useEffect(() => { refresh() }, [refresh])\n\n // Explicit \"show the wizard\" flag — set when the user picks the\n // `+ add or re-configure` option, cleared when they save or cancel back\n // to the picker. Without it, the picker would be sticky once any provider\n // had credentials.\n const [forceWizard, setForceWizard] = useState(false)\n\n const available = useMemo(() => providers.filter(p => p.available), [providers])\n\n const onWizardDone = useCallback(() => {\n setForceWizard(false)\n refresh()\n }, [refresh])\n\n if (available.length === 0 || forceWizard) {\n // \"← back\" only makes sense when we got here from the picker (forceWizard)\n // AND there's still something to go back to. The `&&` guards against the\n // edge case where credentials disappear between renders.\n const canCancel = forceWizard && available.length > 0\n return (\n <SetupWizard\n registry={registry}\n dataDir={config.paths.userDir}\n onConfigured={onWizardDone}\n onCancel={canCancel ? () => setForceWizard(false) : undefined}\n />\n )\n }\n\n const options = [\n ...available.map(p => ({\n name: p.label,\n description: p.methods.map(m => m.detail).join(' · '),\n value: p.key,\n })),\n {\n name: '+ add or re-configure a provider',\n description: 'launch the setup wizard',\n value: WIZARD_OPTION_VALUE,\n },\n ]\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.border,\n padding: 1,\n flexDirection: 'column',\n flexGrow: 1,\n }}\n >\n <select\n {...SELECT_THEME}\n focused={focused}\n options={options}\n wrapSelection\n onSelect={(_idx, option) => {\n if (!option)\n return\n if (option.value === WIZARD_OPTION_VALUE) {\n setForceWizard(true)\n return\n }\n const provider = findByKey(available, option.value)\n if (provider)\n onPick(provider)\n }}\n style={{ flexGrow: 1 }}\n />\n </box>\n <TitleOverlay title=\"pick a provider\" />\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SetupWizard — first-run credential setup. Three steps:\n// 1. Pick provider\n// 2. Pick auth method (apikey · OAuth where the descriptor supports it)\n// 3a. Enter API key → save to credentials.json → refresh AuthScreen\n// 3b. Open browser → OAuth callback server → save tokens → refresh\n//\n// Everything is driven by the host's {@link ProviderDescriptor}s — no\n// hardcoded provider metadata lives here.\n// ---------------------------------------------------------------------------\n\ntype WizardStep\n = | { kind: 'pick-provider' }\n | { kind: 'pick-method', descriptor: ProviderDescriptor }\n | { kind: 'enter-apikey', descriptor: ProviderDescriptor }\n | { kind: 'oauth-running', descriptor: ProviderDescriptor }\n\nfunction SetupWizard({\n registry,\n dataDir,\n onConfigured,\n onCancel,\n}: {\n registry: Readonly<Record<string, ProviderDescriptor>>\n dataDir: string\n onConfigured: () => void\n /**\n * Available only when the wizard was opened from the picker\n * (\"+ add or re-configure\"). On first launch (no providers yet) it's\n * undefined and the wizard has no cancel affordance — the user must set\n * up a provider to proceed.\n */\n onCancel?: () => void\n}) {\n const [step, setStep] = useState<WizardStep>({ kind: 'pick-provider' })\n const [error, setError] = useState<string | null>(null)\n\n const descriptors = useMemo(() => Object.values(registry), [registry])\n\n const onPickProvider = useCallback((descriptor: ProviderDescriptor) => {\n setError(null)\n setStep({ kind: 'pick-method', descriptor })\n }, [])\n\n const onPickMethod = useCallback((descriptor: ProviderDescriptor, method: 'apikey' | 'oauth') => {\n setError(null)\n if (method === 'apikey') {\n setStep({ kind: 'enter-apikey', descriptor })\n }\n else {\n setStep({ kind: 'oauth-running', descriptor })\n }\n }, [])\n\n const onApiKeySubmit = useCallback((descriptor: ProviderDescriptor, value: string) => {\n const trimmed = value.trim()\n if (!trimmed) {\n setError('API key cannot be empty.')\n return\n }\n try {\n setProviderCredential(dataDir, descriptor, { kind: 'apikey', value: trimmed })\n // Also expose via env so providers created later in this process pick it\n // up. Skip for descriptors with no env-var convention (custom providers\n // that resolve credentials some other way).\n if (descriptor.envKey)\n process.env[descriptor.envKey] = trimmed\n onConfigured()\n }\n catch (err) {\n setError(errorMessage(err))\n }\n }, [dataDir, onConfigured])\n\n // `OAuthRunningStep` lists `onError` in its main effect's dep array\n // (alongside descriptor + dataDir + onSuccess). Inlining a fresh arrow\n // here on every render would re-run the effect on any unrelated state\n // change (e.g. the wizard transitioning steps), tearing down the\n // in-flight `runOAuthLogin` and re-spawning a browser tab + callback\n // server. The memoized identity keeps the OAuth flow alive once it's\n // running. The body still references the latest `step.descriptor` via\n // the closure-stable setter call.\n const onOAuthError = useCallback((msg: string) => {\n setError(msg)\n setStep(prev => prev.kind === 'oauth-running'\n ? { kind: 'pick-method', descriptor: prev.descriptor }\n : prev)\n }, [])\n\n if (descriptors.length === 0)\n return <EmptyRegistryNotice />\n\n if (step.kind === 'pick-provider') {\n return (\n <PickProviderStep\n descriptors={descriptors}\n error={error}\n onPick={onPickProvider}\n onCancel={onCancel}\n />\n )\n }\n\n if (step.kind === 'pick-method')\n return <PickMethodStep descriptor={step.descriptor} error={error} onPick={onPickMethod} />\n\n if (step.kind === 'enter-apikey')\n return <EnterApiKeyStep descriptor={step.descriptor} error={error} onSubmit={onApiKeySubmit} />\n\n return (\n <OAuthRunningStep\n descriptor={step.descriptor}\n dataDir={dataDir}\n onSuccess={onConfigured}\n onError={onOAuthError}\n />\n )\n}\n\n/**\n * Shared wrapper for every wizard step — same border + padding + flex\n * layout with a customizable title and accent color. Footnote slot at the\n * bottom for an error banner.\n *\n * Title rides `accent` when present (so the empty-registry notice's red\n * title matches its red border) and falls back to `COLOR.brand`. The\n * outer flex column hosts both the bordered content box and the\n * `TitleOverlay`, which must be its sibling (not a child) so the\n * border-row paint isn't clipped by the bordered box's scissor rect.\n */\nfunction WizardPanel({\n title,\n accent,\n error,\n children,\n}: {\n title: string\n accent?: string\n error?: string | null\n children: ReactNode\n}) {\n const COLOR = useColors()\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n <box\n style={{\n border: true,\n borderColor: accent ?? COLOR.border,\n padding: 1,\n gap: 1,\n flexDirection: 'column',\n flexGrow: 1,\n }}\n >\n {children}\n {error && <text fg={COLOR.error}>{error}</text>}\n </box>\n <TitleOverlay title={title.trim()} titleColor={accent} />\n </box>\n )\n}\n\n/** \"esc to exit\" footer hint shared by every wizard step that doesn't offer a \"← back\" affordance. */\nfunction WizardEscHint() {\n const COLOR = useColors()\n return <text fg={COLOR.dim}>esc to exit</text>\n}\n\nfunction EmptyRegistryNotice() {\n const COLOR = useColors()\n return (\n <WizardPanel title=\"no providers configured\" accent={COLOR.error}>\n <text fg={COLOR.error}>This TUI has no providers registered.</text>\n <text fg={COLOR.dim}>\n Pass providers via\n <span fg={COLOR.model}>{' runTui({ providers }) '}</span>\n or use the built-ins via\n <span fg={COLOR.model}>{' BUILTIN_PROVIDERS '}</span>\n .\n </text>\n </WizardPanel>\n )\n}\n\n/** Sentinel option value used for the wizard's \"← back to picker\" entry. */\nconst WIZARD_BACK_VALUE = '__back__'\n\nfunction PickProviderStep({\n descriptors,\n error,\n onPick,\n onCancel,\n}: {\n descriptors: readonly ProviderDescriptor[]\n error: string | null\n onPick: (descriptor: ProviderDescriptor) => void\n /** When set, adds a \"← back\" option that calls this to bail out without saving. */\n onCancel?: () => void\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n const options = [\n ...descriptors.map((d) => {\n const methods: string[] = supportsOAuth(d) ? ['API key', 'OAuth'] : ['API key']\n return { name: d.label, description: methods.join(' · '), value: d.key }\n }),\n ...(onCancel\n ? [{ name: '← back', description: 'return to the provider list', value: WIZARD_BACK_VALUE }]\n : []),\n ]\n\n // Different copy + title for the two entry paths: first-launch users need\n // the \"credentials live here\" hint; re-config users already know how the\n // TUI works and just want to pick a provider.\n const title = onCancel\n ? 'add or re-configure a provider'\n : 'welcome to zidane · pick a provider'\n\n return (\n <WizardPanel title={title} error={error}>\n {!onCancel && (\n <text fg={COLOR.dim}>\n No provider credentials yet. Pick a provider to configure — keys are stored in\n <span fg={COLOR.model}>{' ~/.zidane/credentials.json '}</span>\n (owner-only).\n </text>\n )}\n <select\n {...SELECT_THEME}\n focused={focused}\n options={options}\n wrapSelection\n onSelect={(_idx, option) => {\n if (!option)\n return\n if (option.value === WIZARD_BACK_VALUE) {\n onCancel?.()\n return\n }\n const descriptor = findByKey(descriptors, option.value)\n if (descriptor)\n onPick(descriptor)\n }}\n style={{ flexGrow: 1 }}\n />\n </WizardPanel>\n )\n}\n\nfunction PickMethodStep({\n descriptor,\n error,\n onPick,\n}: {\n descriptor: ProviderDescriptor\n error: string | null\n onPick: (descriptor: ProviderDescriptor, method: 'apikey' | 'oauth') => void\n}) {\n const focused = useModalAwareFocus()\n const SELECT_THEME = useSelectStyle()\n\n const options = useMemo(() => {\n interface MethodOption { name: string, description: string, value: 'apikey' | 'oauth' }\n const items: MethodOption[] = [\n { name: 'API key', description: `paste your ${descriptor.label} API key`, value: 'apikey' },\n ]\n if (supportsOAuth(descriptor)) {\n // OAuth hint comes from the descriptor — built-in `anthropicDescriptor`\n // sets it to \"Claude Pro/Max subscription\". Hosts adding OAuth on their\n // own providers set their own hint (or omit it).\n const hint = descriptor.oauthHint ? ` (${descriptor.oauthHint})` : ''\n items.push({\n name: 'OAuth',\n description: `browser-based sign-in${hint}`,\n value: 'oauth',\n })\n }\n return items\n }, [descriptor])\n\n return (\n <WizardPanel title={`configure ${descriptor.label} — pick auth method`} error={error}>\n <WizardEscHint />\n <select\n {...SELECT_THEME}\n focused={focused}\n options={options}\n wrapSelection\n onSelect={(_idx, option) => {\n if (option)\n onPick(descriptor, option.value)\n }}\n style={{ flexGrow: 1 }}\n />\n </WizardPanel>\n )\n}\n\nfunction EnterApiKeyStep({\n descriptor,\n error,\n onSubmit,\n}: {\n descriptor: ProviderDescriptor\n error: string | null\n onSubmit: (descriptor: ProviderDescriptor, value: string) => void\n}) {\n const focused = useModalAwareFocus()\n const inputRef = useRef<InputRenderable | null>(null)\n const COLOR = useColors()\n\n const submit = useCallback(() => {\n const value = inputRef.current?.value ?? ''\n onSubmit(descriptor, value)\n }, [descriptor, onSubmit])\n\n return (\n <WizardPanel title={`configure ${descriptor.label} — paste API key`} error={error}>\n <text fg={COLOR.dim}>\n Paste your\n {` ${descriptor.label} `}\n API key and press\n <span fg={COLOR.model}> enter </span>\n to save. Esc to exit.\n </text>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n focused={focused}\n keyBindings={API_KEY_INPUT_BINDINGS}\n placeholder={descriptor.apiKeyPlaceholder ?? 'API key…'}\n onSubmit={submit}\n style={{ flexGrow: 1 }}\n />\n </box>\n </WizardPanel>\n )\n}\n\n/**\n * An in-flight pi-ai `onPrompt(prompt)` deferred. We resolve it when the\n * user submits the input, or reject it on cancel/unmount so the awaiting\n * pi-ai promise unwinds and `runOAuthLogin` rejects cleanly (the screen's\n * abort path already routes that into `onError`).\n */\ninterface PendingPrompt {\n message: string\n placeholder?: string\n allowEmpty?: boolean\n resolve: (value: string) => void\n reject: (err: Error) => void\n}\n\nfunction OAuthRunningStep({\n descriptor,\n dataDir,\n onSuccess,\n onError,\n}: {\n descriptor: ProviderDescriptor\n dataDir: string\n onSuccess: () => void\n onError: (msg: string) => void\n}) {\n const usesManualPaste = oauthUsesManualCodePaste(descriptor)\n const [url, setUrl] = useState<string | null>(null)\n const [status, setStatus] = useState(\n usesManualPaste\n ? 'opening browser…'\n : 'starting browser…',\n )\n const [pending, setPending] = useState<PendingPrompt | null>(null)\n const [pasteHint, setPasteHint] = useState<{ text: string, tone: 'dim' | 'error' | 'accent' } | null>(null)\n const focused = useModalAwareFocus()\n const inputRef = useRef<InputRenderable | null>(null)\n\n // Keep the latest `pending` reachable from the unmount cleanup without\n // re-binding the OAuth effect (which would tear down + restart the flow\n // on every keystroke that changes `pending`).\n const pendingRef = useRef<PendingPrompt | null>(null)\n pendingRef.current = pending\n\n useEffect(() => {\n const ac = new AbortController()\n let cancelled = false\n\n void (async () => {\n try {\n const creds = await runOAuthLogin(descriptor, {\n onUrl: (loginUrl) => {\n if (cancelled)\n return\n setUrl(loginUrl)\n setStatus(\n usesManualPaste\n ? 'complete the login in your browser, then paste the code below'\n : 'waiting for browser callback…',\n )\n },\n onPrompt: prompt => new Promise<string>((resolve, reject) => {\n if (cancelled) {\n reject(new Error('OAuth flow cancelled'))\n return\n }\n // Latest prompt wins — pi-ai shouldn't issue overlapping\n // prompts in practice, but if it did we'd want the newest\n // one visible and the older one rejected so its awaiter\n // unwinds rather than hanging.\n const prev = pendingRef.current\n prev?.reject(new Error('superseded by a newer OAuth prompt'))\n setPending({\n message: prompt.message,\n placeholder: prompt.placeholder,\n allowEmpty: prompt.allowEmpty,\n resolve,\n reject,\n })\n }),\n onProgress: (message) => {\n if (!cancelled)\n setStatus(message)\n },\n signal: ac.signal,\n })\n if (cancelled)\n return\n // `creds` carries `{ access, refresh, expires, ...extras }`. Spread\n // verbatim — the storage shape only adds our `kind` tag on top.\n setProviderCredential(dataDir, descriptor, { kind: 'oauth', ...creds })\n onSuccess()\n }\n catch (err) {\n if (cancelled)\n return\n onError(errorMessage(err))\n }\n })()\n\n return () => {\n cancelled = true\n // Unwind any awaiting `onPrompt` so the pi-ai promise rejects\n // instead of hanging the AbortController.\n pendingRef.current?.reject(new Error('OAuth flow cancelled'))\n pendingRef.current = null\n ac.abort()\n }\n }, [descriptor, dataDir, onSuccess, onError, usesManualPaste])\n\n // One input, two callers. If pi-ai's `onPrompt` is awaiting a value\n // (paste-the-code flows like Anthropic Claude Pro/Max), feed it\n // verbatim — pi-ai's `parseAuthorizationInput` already accepts both\n // bare codes and full redirect URLs. Otherwise (the normal happy path:\n // pi-ai's loopback server is still waiting), fire the pasted URL at\n // OUR own loopback — the request hits pi-ai's server handler, state\n // matches, `waitForCode` resolves, OAuth promise completes. This is\n // the SSH escape hatch: even when the browser sits on the user's local\n // box and can't reach the remote callback server, copy-pasting the\n // redirect URL into the TUI works.\n const submitInput = useCallback(() => {\n const value = inputRef.current?.value?.trim() ?? ''\n const current = pendingRef.current\n if (current) {\n if (!value && !current.allowEmpty)\n return\n pendingRef.current = null\n setPending(null)\n setStatus('exchanging code…')\n current.resolve(value)\n return\n }\n if (!value)\n return\n setPasteHint({ text: 'submitting redirect URL…', tone: 'dim' })\n void (async () => {\n try {\n const result = await fetchOAuthRedirect(value)\n if (result.status >= 200 && result.status < 300) {\n setPasteHint({ text: 'redirect accepted — exchanging code…', tone: 'accent' })\n setStatus('exchanging code…')\n }\n else {\n setPasteHint({\n text: `callback server rejected the URL (${result.status}${result.message ? `: ${result.message}` : ''}). Try again or restart the flow.`,\n tone: 'error',\n })\n }\n }\n catch (err) {\n setPasteHint({ text: errorMessage(err), tone: 'error' })\n }\n })()\n }, [])\n\n return (\n <WizardPanel title={`configure ${descriptor.label} — OAuth`}>\n <WizardEscHint />\n {pending\n ? <Spinner label={pending.message} />\n : <Spinner label={status} />}\n {url && (\n <OAuthAuthBlock\n authUrl={url}\n // Wizard panel = border (2) + padding (4) ≈ 6 cols of chrome.\n maxLineWidth={200}\n chromeWidth={6}\n paste={{\n inputRef,\n focused,\n onSubmit: submitInput,\n placeholder: pending?.placeholder ?? 'paste redirect URL (or code) and press enter…',\n hint: pasteHint ?? undefined,\n }}\n />\n )}\n </WizardPanel>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SessionsScreen — list of sessions with a synthetic \"+ new\" entry on top.\n//\n// Renders rows manually (scrollbox + per-row `<text>` spans) instead of\n// using OpenTUI's `<select>` because the row's secondary line carries\n// multi-colored stats — warn-tinted numbers + dim labels, matching the\n// chat screen's bottom-bar palette. The native select only paints its\n// description in a single fg color, so it couldn't carry that look.\n//\n// Owns its own keyboard handling (↑↓ + return + page-up/down + home/end)\n// so the affordance set matches what users expect from the picker\n// without depending on the select renderable.\n//\n// Render is fully controlled against `focusedSessionId` — the cursor\n// follows the row's IDENTITY rather than a numerical slot, so a\n// `generate title` that bumps `updatedAt` (and reorders the list) leaves\n// the cursor on the same row visually.\n// ---------------------------------------------------------------------------\n\n/**\n * Sentinel row id for the synthetic \"+ new\" row at the top of the\n * sessions list. Exposed so the AppShell can pass it through\n * `focusedSessionId` (or rather: the focused ROW id, which is either a\n * real session id or this sentinel) and gate global shortcuts that\n * only make sense on real sessions — `ctrl+x` is the canonical example.\n *\n * Stays a plain string (rather than a discriminated union) so the\n * parent's focus state remains a flat `string | null` — `null` means\n * \"no preference yet, please default to the first session\"; this\n * sentinel means \"+ new is the active row\".\n */\nexport const NEW_SESSION_ROW_ID = '__new__'\n\n/** Guard for the `ctrl+x` handler: only a real session id should open the details modal. */\nexport function isSessionRowId(rowId: string | null): rowId is string {\n return rowId !== null && rowId !== NEW_SESSION_ROW_ID\n}\n\n/** Page-up / page-down jump size — half a typical visible window. */\nconst PAGE_JUMP = 6\n\ninterface SessionRowItem {\n kind: 'new' | 'session'\n /** `NEW_ROW_ID` for the \"+ new\" row, else the session id. */\n rowId: string\n /** Present only on session rows — used by the renderer for the stats line. */\n meta: SessionMeta | null\n}\n\nexport function SessionsScreen({\n sessions,\n currentId,\n focusedSessionId,\n onPick,\n onCreate,\n onFocusChange,\n showAllProjects = false,\n currentProjectRoot,\n}: {\n sessions: SessionMeta[]\n /**\n * Identity of the row the parent considers focused. Drives the row\n * cursor so the highlight follows the SESSION (not its slot) when the\n * list reorders. `null` lands the cursor on the first session row, or\n * on \"+ new\" if there are no sessions yet.\n */\n focusedSessionId: string | null\n currentId: string | null\n onPick: (id: string) => void\n onCreate: () => void\n /**\n * Notified as the user navigates between rows. Receives the session id\n * of the focused row, or `null` when \"+ new\" is focused (no session to\n * act on). Parents use this to wire `ctrl+x` against the live focus.\n */\n onFocusChange?: (id: string | null) => void\n /**\n * When `true`, render each session's project label under the title.\n * Off by default — when the list is already filtered to one project\n * the label would just repeat that project on every row.\n */\n showAllProjects?: boolean\n /**\n * Current project root, used to label \"this project\" on rows that\n * belong to it when `showAllProjects` is on. Untagged (legacy)\n * sessions render as \"untagged\".\n */\n currentProjectRoot?: string\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const inputRef = useRef<InputRenderable | null>(null)\n\n // Free-text filter typed into the top input. Lowercased terms are\n // matched against a per-session search corpus (title + project basename\n // + full project path) — splitting on whitespace lets the user combine\n // narrowing terms (`agent docs`) without having to type them in order.\n const [query, setQuery] = useState('')\n const filteredSessions = useMemo(() => {\n const trimmed = query.trim().toLowerCase()\n if (!trimmed)\n return sessions\n const terms = trimmed.split(/\\s+/)\n return sessions.filter((meta) => {\n const projectBasename = meta.projectRoot?.split('/').pop() ?? ''\n const corpus = `${meta.title} ${projectBasename} ${meta.projectRoot ?? ''}`.toLowerCase()\n return terms.every(t => corpus.includes(t))\n })\n }, [sessions, query])\n\n const rows = useMemo<readonly SessionRowItem[]>(() => [\n { kind: 'new', rowId: NEW_SESSION_ROW_ID, meta: null },\n ...filteredSessions.map<SessionRowItem>(meta => ({ kind: 'session', rowId: meta.id, meta })),\n ], [filteredSessions])\n\n // Imperative focus on mount + whenever the parent grants focus back\n // (e.g. closing a modal). Mirrors the model picker's dance — declaring\n // `focused` alone isn't enough when another input previously owned focus.\n useEffect(() => {\n if (focused)\n inputRef.current?.focus()\n }, [focused])\n\n // Derive the cursor index from the focused row id. Re-runs every\n // time `rows` mutates — when a session reorders (`generate title`\n // bumps `updatedAt`) the cursor follows its identity to the new slot.\n //\n // Meaning of `focusedSessionId`:\n // - `null` → no preference yet; default to first session.\n // - `NEW_SESSION_ROW_ID` → \"+ new\" row is intentionally focused.\n // - any other string → that session's row.\n //\n // Fallback ladder when no id is focused or the focused session has\n // disappeared from the list:\n // - First session row when one exists (index 1; \"+ new\" sits at 0).\n // - Index 0 (\"+ new\") when no sessions exist.\n const cursorIndex = useMemo(() => {\n if (focusedSessionId === NEW_SESSION_ROW_ID)\n return 0\n if (focusedSessionId) {\n const idx = rows.findIndex(r => r.kind === 'session' && r.rowId === focusedSessionId)\n if (idx !== -1)\n return idx\n }\n return rows.length > 1 ? 1 : 0\n }, [rows, focusedSessionId])\n\n // Reconcile the parent's focus state when the rows list mutates\n // independently of user navigation: first mount (no id yet) or the\n // focused session disappearing (delete). Pushes the id at the new\n // `cursorIndex` so the parent's `focusedSessionId` matches what the\n // cursor is visually pointing at.\n useEffect(() => {\n if (!onFocusChange)\n return\n if (focusedSessionId === NEW_SESSION_ROW_ID)\n return // intentional \"+ new\" focus, leave it\n if (focusedSessionId && rows.some(r => r.kind === 'session' && r.rowId === focusedSessionId))\n return // valid session focus, leave it\n const fallback = rows[cursorIndex]\n onFocusChange(fallback?.rowId ?? null)\n }, [rows, focusedSessionId, cursorIndex, onFocusChange])\n\n // Push the row id at a new cursor position back to the parent. Used\n // by the keyboard handler to keep `focusedSessionId` in sync with\n // the visible cursor — including the \"+ new\" sentinel, so the user\n // can actually navigate up to it (previously emitted `null`, which\n // the cursor-derivation logic treats as \"default to first session\"\n // and immediately snaps the cursor back).\n const moveCursor = useCallback((nextIndex: number) => {\n if (!onFocusChange || rows.length === 0)\n return\n const clamped = ((nextIndex % rows.length) + rows.length) % rows.length // wrap on edges\n const row = rows[clamped]\n onFocusChange(row?.rowId ?? null)\n }, [rows, onFocusChange])\n\n const commitCurrent = useCallback(() => {\n const row = rows[cursorIndex]\n if (!row)\n return\n if (row.kind === 'new')\n onCreate()\n else\n onPick(row.rowId)\n }, [rows, cursorIndex, onCreate, onPick])\n // Note: keyboard handler below catches `↵` and routes to this. The\n // `kind === 'new'` branch fires `onCreate` regardless of whether\n // `focusedSessionId` is `NEW_SESSION_ROW_ID` or `null` — both land\n // the cursor at index 0 in `cursorIndex`'s derivation.\n\n useKeyboard((key) => {\n // Gated on `focused` so a modal opened on top steals input cleanly\n // (mirrors what `useModalAwareFocus` does for the textarea/select).\n //\n // Runs alongside the search `<input>` — opentui's `useKeyboard` is a\n // global listener, not tied to a focused renderable, so the same key\n // can reach both. Up/down/return are no-ops or suppressed in the\n // input, and home/end are intentionally NOT handled here so they\n // edit the search text instead.\n if (!focused)\n return\n if (key.name === 'up') {\n moveCursor(cursorIndex - 1)\n return\n }\n if (key.name === 'down') {\n moveCursor(cursorIndex + 1)\n return\n }\n if (key.name === 'pageup') {\n moveCursor(cursorIndex - PAGE_JUMP)\n return\n }\n if (key.name === 'pagedown') {\n moveCursor(cursorIndex + PAGE_JUMP)\n return\n }\n if (key.name === 'return') {\n commitCurrent()\n }\n })\n\n // Title meta — laid out RIGHT-aligned on the top border. Composed\n // (left to right) of: cwd · [all projects ·] {count}.\n //\n // Responsive layout:\n // - Wide: full path · all projects · 3 / 12 sessions\n // - Narrower: cwd left-truncated by `compactPath` to fit\n // - Narrow: drop the cwd segment, keep the count\n // - Very narrow: `TitleOverlay` drops the meta entirely\n //\n // The cwd is shown without a \"cwd \" label — paths read as paths in\n // a title slot, and the chat screen uses the same convention for\n // its meta values (`5 user messages · 12 turns · ctrl+x`).\n const { width: termWidth } = useTerminalDimensions()\n const titleMeta = useMemo<readonly MetaSegment[]>(() => {\n const filtering = query.trim().length > 0\n const countSegs: MetaSegment[] = sessions.length === 0\n ? [{ text: 'no sessions yet', color: COLOR.mute }]\n : filtering\n ? [\n { text: String(filteredSessions.length), color: COLOR.warn },\n { text: ' / ' },\n { text: String(sessions.length), color: COLOR.warn },\n { text: ` session${sessions.length === 1 ? '' : 's'}` },\n ]\n : [\n { text: String(sessions.length), color: COLOR.warn },\n { text: ` session${sessions.length === 1 ? '' : 's'}` },\n ]\n\n if (!currentProjectRoot)\n return countSegs\n\n // Compute the cwd budget that fits alongside the count inside\n // `TitleOverlay`'s remaining meta width. The overlay reserves\n // 6 cells (2× wrap + 1× gap) + the title length out of `termWidth - 4`\n // (terminal width minus the screen wrapper's 1-cell padding on each\n // side and the bordered box's two corners). The path takes whatever\n // is left after the count and separators.\n const OVERLAY_RESERVED = 6 // mirrors TITLE_OVERLAY_{WRAP,META_WRAP,GAP}\n const TITLE_LEN = 'sessions'.length\n const SEP_LEN = ' · '.length\n const allProjectsLen = showAllProjects ? 'all projects'.length + SEP_LEN : 0\n const countLen = countSegs.reduce((sum, s) => sum + s.text.length, 0)\n const metaBudget = Math.max(0, termWidth - 4 - TITLE_LEN - OVERLAY_RESERVED)\n const cwdBudget = metaBudget - countLen - SEP_LEN - allProjectsLen\n\n // Below ~6 cells the path is unreadable (basically just `…/x`),\n // so drop it entirely and let the count keep its slot.\n const MIN_CWD = 6\n if (cwdBudget < MIN_CWD)\n return countSegs\n\n const cwd = compactPath(currentProjectRoot, cwdBudget)\n const segs: MetaSegment[] = [{ text: cwd, color: COLOR.model }]\n if (showAllProjects) {\n segs.push(\n { text: ' · ', color: COLOR.mute },\n { text: 'all projects', color: COLOR.accent },\n )\n }\n segs.push({ text: ' · ', color: COLOR.mute }, ...countSegs)\n return segs\n }, [\n sessions.length,\n filteredSessions.length,\n query,\n currentProjectRoot,\n showAllProjects,\n termWidth,\n COLOR,\n ])\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.border,\n padding: 1,\n flexDirection: 'column',\n flexGrow: 1,\n }}\n >\n {/*\n Search input — owns text focus so the user can filter as they\n type. Runs alongside the `useKeyboard` handler above; that\n handler drives ↑/↓/pgup/pgdn/↵ navigation regardless of who\n currently has focus, so the user can search and pick without\n ever leaving the keyboard. `onSubmit` is suppressed so ↵\n doesn't fire twice (once via input, once via useKeyboard).\n */}\n <box\n style={{\n border: true,\n borderColor: COLOR.border,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n flexShrink: 0,\n marginBottom: 1,\n }}\n >\n <input\n ref={inputRef}\n focused={focused}\n placeholder=\"filter sessions — title or project…\"\n onInput={setQuery}\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n {/*\n Scrollbox owns the visible window when the list outgrows the\n viewport. `focusable={false}` keeps it from stealing keyboard\n focus — our local `useKeyboard` drives navigation against the\n parent's `focusedSessionId`.\n */}\n <scrollbox\n focusable={false}\n style={{ flexGrow: 1 }}\n >\n {rows.map((row, idx) => (\n <SessionRow\n key={row.rowId}\n row={row}\n focused={idx === cursorIndex && focused}\n isCurrent={row.kind === 'session' && row.rowId === currentId}\n showProject={showAllProjects}\n currentProjectRoot={currentProjectRoot}\n />\n ))}\n {filteredSessions.length === 0 && query.trim().length > 0 && (\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' no sessions match '}</span>\n <span fg={COLOR.warn}>{query.trim()}</span>\n </text>\n )}\n </scrollbox>\n </box>\n <TitleOverlay title=\"sessions\" meta={titleMeta} />\n </box>\n )\n}\n\n/**\n * Two-line row for `SessionsScreen`. Top line is the title with the\n * focus + active markers; bottom is the stats summary (turns / user /\n * runs / age) in the bottom-bar's warn-number + dim-label palette.\n *\n * Alignment contract: the stats line indents by `STATS_INDENT` cells —\n * exactly the width of the focus + current markers above — so the first\n * stat (the turn count) sits flush under the title's first letter.\n */\nfunction SessionRow({\n row,\n focused,\n isCurrent,\n showProject = false,\n currentProjectRoot,\n}: {\n row: SessionRowItem\n focused: boolean\n isCurrent: boolean\n /** Render the project-label line under the stats row. */\n showProject?: boolean\n /** Resolved current project root — used to render rows that belong to it as \"this project\". */\n currentProjectRoot?: string\n}) {\n const COLOR = useColors()\n\n // 2 cells (focus mark) + 2 cells (current mark) = 4. Keeping both\n // markers fixed-width means the title always starts at column 4 and\n // the stats line can mirror that with a single string of 4 spaces.\n const STATS_INDENT = ' '\n\n const focusMark = focused ? '▶ ' : ' '\n const focusColor = focused ? COLOR.brand : COLOR.mute\n const titleColor = focused ? COLOR.brand : COLOR.dim\n\n if (row.kind === 'new') {\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, alignSelf: 'stretch' }}>\n <text wrapMode=\"none\">\n <span fg={focusColor}>{focusMark}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={titleColor}>+ new session</span>\n </text>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{STATS_INDENT}</span>\n <span fg={COLOR.mute}>start fresh</span>\n </text>\n </box>\n )\n }\n\n const meta = row.meta!\n const currentMark = isCurrent ? '● ' : ' '\n const currentColor = isCurrent ? COLOR.accent : COLOR.mute\n\n // Stats palette: numbers in `warn` (anchor the reader's eye), labels +\n // separators + age in `mute` so the secondary line recedes behind the\n // title. Previously labels rode `COLOR.dim` — the same color as the\n // non-focused title, which made the two lines compete visually.\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, alignSelf: 'stretch' }}>\n <text wrapMode=\"none\">\n <span fg={focusColor}>{focusMark}</span>\n <span fg={currentColor}>{currentMark}</span>\n <span fg={titleColor}>{meta.title}</span>\n </text>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{STATS_INDENT}</span>\n <span fg={COLOR.warn}>{meta.turnCount}</span>\n <span fg={COLOR.mute}>{` turn${meta.turnCount === 1 ? '' : 's'} · `}</span>\n <span fg={COLOR.warn}>{meta.userMessageCount}</span>\n <span fg={COLOR.mute}>{` user · `}</span>\n <span fg={COLOR.warn}>{meta.runCount}</span>\n <span fg={COLOR.mute}>{` run${meta.runCount === 1 ? '' : 's'} · `}</span>\n <span fg={COLOR.mute}>{ageString(meta.updatedAt)}</span>\n </text>\n {showProject && (\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{STATS_INDENT}</span>\n {renderProjectLabel(meta.projectRoot, currentProjectRoot, COLOR)}\n </text>\n )}\n </box>\n )\n}\n\n/**\n * Render the project label for the cross-project view. Three cases:\n *\n * - The row belongs to the current project → render \"this project\"\n * in `accent` so the user can quickly spot rows that match where\n * they are.\n * - The row is tagged but for a different project → render the\n * basename of the project root in `dim` (full path would be too\n * long for the row).\n * - The row is untagged (legacy) → render \"untagged\" in `mute`.\n */\nfunction renderProjectLabel(\n rowProject: string | undefined,\n currentProject: string | undefined,\n COLOR: ReturnType<typeof useColors>,\n): ReactNode {\n if (!rowProject) {\n return <span fg={COLOR.mute}>untagged</span>\n }\n if (currentProject && rowProject === currentProject) {\n return <span fg={COLOR.accent}>this project</span>\n }\n // Last path segment. Falls back to the full string if the path is\n // unsegmented (e.g. a Windows root or an unusual format).\n const basename = rowProject.split('/').pop() ?? rowProject\n return (\n <>\n <span fg={COLOR.mute}>project </span>\n <span fg={COLOR.dim}>{basename}</span>\n </>\n )\n}\n\n// ---------------------------------------------------------------------------\n// ChatScreen — transcript + auto-growing multi-line input + (running) spinner.\n// Enter inserts a newline; shift+enter submits; ctrl+↑↓ cycles prompt history.\n// ---------------------------------------------------------------------------\n\n/** Visible content lines: 1 minimum, 5 maximum (textarea scrolls past 5). */\nconst MIN_CONTENT_LINES = 1\nconst MAX_CONTENT_LINES = 5\n\nconst IMAGE_MEDIA_TYPES: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n}\n\nconst MIME_BY_EXT: Record<string, string> = {\n txt: 'text/plain',\n md: 'text/markdown',\n json: 'application/json',\n yaml: 'text/yaml',\n yml: 'text/yaml',\n toml: 'text/plain',\n xml: 'application/xml',\n html: 'text/html',\n htm: 'text/html',\n css: 'text/css',\n csv: 'text/csv',\n tsv: 'text/tab-separated-values',\n js: 'text/javascript',\n mjs: 'text/javascript',\n cjs: 'text/javascript',\n ts: 'text/typescript',\n mts: 'text/typescript',\n cts: 'text/typescript',\n tsx: 'text/typescript',\n jsx: 'text/javascript',\n py: 'text/x-python',\n rb: 'text/x-ruby',\n rs: 'text/x-rust',\n go: 'text/x-go',\n java: 'text/x-java',\n c: 'text/x-c',\n h: 'text/x-c',\n cpp: 'text/x-c++',\n hpp: 'text/x-c++',\n sh: 'text/x-shellscript',\n bash: 'text/x-shellscript',\n zsh: 'text/x-shellscript',\n fish: 'text/x-shellscript',\n sql: 'text/x-sql',\n graphql: 'text/x-graphql',\n pdf: 'application/pdf',\n zip: 'application/zip',\n tar: 'application/x-tar',\n gz: 'application/gzip',\n log: 'text/plain',\n env: 'text/plain',\n cfg: 'text/plain',\n ini: 'text/plain',\n conf: 'text/plain',\n}\n\n/**\n * Stable empty-array reference — keeps `queuedMessages` default referentially\n * stable across renders so memoized children don't bust their deps. Declared\n * here (not next to `QueuedPreview`) so it's defined before `ChatScreen`'s\n * default-value reference at the prop destructure.\n */\nconst EMPTY_QUEUED_MESSAGES: readonly QueuedPreview[] = []\n\nexport function ChatScreen({\n cwd,\n events,\n busy,\n compacting = false,\n queuedMessages = EMPTY_QUEUED_MESSAGES,\n queueSelectionIndex = null,\n queueShortcuts,\n onEnterQueueFromEmptyPrompt,\n settings,\n onSubmit,\n session,\n pending,\n onApproval,\n pendingInteraction,\n onInteraction,\n completionProviders,\n onPopupOpenChange,\n selectedTurnId,\n promptTriggerHints,\n liveSession = null,\n}: {\n cwd: string\n events: StreamEvent[]\n busy: boolean\n /**\n * `true` while a background auto-compaction is in flight. Drives the\n * same title-overlay spinner as {@link busy} but in a different color\n * — the conversation is \"active\" without anything streaming visibly.\n */\n compacting?: boolean\n /**\n * User prompts submitted while a run was already in flight. Rendered\n * stacked in a small box above the prompt input until the active\n * `agent.run()` finishes; each entry is then popped (FIFO) and pushed\n * into the transcript as a user message. Empty by default.\n */\n queuedMessages?: readonly QueuedPreview[]\n /**\n * Highlighted index inside {@link queuedMessages}, or `null` when the\n * prompt textarea has focus. When non-null, the textarea blurs (same\n * pattern as `selectMode`) so up / down / push / drop keys reach the\n * App's keyboard handler instead of being eaten by the input.\n */\n queueSelectionIndex?: number | null\n /**\n * Resolved keybinding specs (`\"ctrl+return\"`, `\"backspace\"` — and\n * whatever the user has configured) threaded into the queue UI so\n * the title hint + per-row action hints track `keybindings.json`\n * overrides. Required when the queue is wired — the binding strings\n * are only ever read here.\n */\n queueShortcuts?: QueueShortcuts\n /**\n * Called from `PromptBlock` when the user presses `↑` on an empty\n * prompt. Returning `true` parks focus on the last queued message\n * (the one closest to the prompt) and tells the caller to suppress\n * the default history-cycle behavior; `false` means \"no queue, do\n * your normal thing\" and history-cycling proceeds.\n */\n onEnterQueueFromEmptyPrompt?: () => boolean\n settings: Settings\n /**\n * Submit handler — receives the raw prompt text, the parsed references\n * (skills, files, …) so the App can act on them (e.g. activate the\n * referenced skill before `agent.run()`), and any attachments captured\n * by the textarea's paste pipeline.\n */\n onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => void\n session: SessionMeta | null\n /** Head of the safe-mode approval queue, or `null` when nothing is pending. */\n pending: ApprovalRequest | null\n /** Resolve the active prompt with the user's pick. */\n onApproval: (decision: ApprovalDecision) => void\n /**\n * Head of the interactions queue (plan approval / Q&A). When set, the\n * prompt slot renders {@link InteractionBlock} instead of the textarea\n * so the user resolves the request before continuing typing. Works the\n * same for live tool calls and resumed-session pending interactions —\n * the App layer only swaps the resolver behind {@link onInteraction}.\n */\n pendingInteraction: InteractionRequest | null\n /** Submit the user's response to the head of the interactions queue. */\n onInteraction: (response: InteractionResponse) => void\n /**\n * Optional autocomplete providers. When passed, `PromptBlock` shows a\n * popup above the textarea and intercepts navigation keys while it's\n * visible. Providers are typed by their item payload (e.g.\n * `CompletionProvider<SkillConfig>`).\n */\n completionProviders?: readonly CompletionProvider<unknown>[]\n /**\n * Notified whenever the popup opens / closes. The App uses this to gate\n * the esc-abort handler so esc dismisses the popup instead of aborting\n * the run when both could fire.\n */\n onPopupOpenChange?: (open: boolean) => void\n /**\n * Active turn id when the user is in select-turn mode. `null` = normal\n * mode. Drives the transcript highlight and unfocuses the textarea so\n * up/down/return reach the parent's keyboard handler instead of being\n * consumed by the input.\n */\n selectedTurnId?: string | null\n /**\n * Optional trigger affordances (e.g. `@ files`, `/ skills`) appended\n * to the prompt-box overlay's hint row when there's room. Caller\n * filters by provider availability (don't include `/ skills` if the\n * skills catalog is empty, etc.) — the prompt overlay drops the\n * triggers entirely on narrow terminals.\n */\n promptTriggerHints?: readonly Hint[]\n /**\n * Live `Session` reference for surfaces that read metadata directly\n * (currently {@link TodoIndicator}'s `useActiveTodos` slice). The\n * reference itself is stable across activations of the same session\n * — re-renders are driven by the `events` prop, which already\n * cascades on every tool-result. `null` while the screen is shown\n * without an attached session (the indicator hides itself in that\n * case, same as it does for \"no in_progress item\").\n *\n * Kept distinct from {@link session} (the lightweight\n * {@link SessionMeta} snapshot) so consumers that don't need live\n * data don't accidentally re-read mutable fields off of it.\n */\n liveSession?: Session | null\n}) {\n const COLOR = useColors()\n\n // Split the session header into title (left) + meta (right). Title rides\n // `COLOR.brand` via `TitleOverlay`'s default so the session name reads as\n // the surface's primary anchor. The meta carries two volume stats\n // (`user messages`, total `turns`) followed by the `ctrl+x session`\n // shortcut so the affordance lives next to the info it acts on. The\n // full session id rides the details modal (`ctrl+x`) — it's not\n // load-bearing for the at-a-glance read.\n //\n // - count digits ride `warn` (primary at-a-glance signal).\n // - descriptive suffix (`user messages`, `turns`, `session`) ride `dim`.\n // - `ctrl+x` rides `warn` (matches the bottom-bar shortcut palette).\n // - The shortcut is suppressed while a run is streaming or an\n // approval is pending — `app.tsx`'s ctrl+x handler is gated on the\n // same condition; the title meta and the bottom bar agree on what's\n // actionable.\n const titleText = session?.title ?? 'untitled'\n // Minimal UI mode drops the `ctrl+x session` chip from the title\n // meta — the shortcut is still bound, just no longer advertised in\n // the header. Users discover it via the keybindings panel (`ctrl+y`).\n const showSessionShortcut = !!session\n && !busy && !pending && !pendingInteraction\n && settings.uiMode !== 'minimal'\n // `user-prompt`-kind events are echoed user prompts (see `onSubmitPrompt`\n // in `app.tsx` and `eventsFromTurns` for the persisted case). Counting\n // them is equivalent to counting user turns in the session's history —\n // and stays correct mid-stream because we append the echo before the\n // agent run starts. The dedicated kind (vs filtering generic `info`)\n // means a future banner / status producer can emit `info` without\n // corrupting this counter.\n const userMessageCount = useMemo(\n () => events.filter(e => e.kind === 'user-prompt').length,\n [events],\n )\n const { width: termWidth } = useTerminalDimensions()\n const hasStatusIcon = busy || compacting\n const metaSegments = useMemo<readonly MetaSegment[] | null>(() => {\n if (!session)\n return null\n // Same two-tone pattern as the bottom bar: the primary value\n // (count, key) rides `warn`, the descriptive label rides `dim`,\n // separators ride `mute`. The user-message count replaces the\n // session id as the leading stat — it's the more meaningful\n // signal of \"how much have we said here\", and the id is one\n // `ctrl+x` away in the details modal when it's needed.\n const turnsSuffix = `turn${session.turnCount === 1 ? '' : 's'}`\n const messagesSuffix = `user message${userMessageCount === 1 ? '' : 's'}`\n const segments: MetaSegment[] = [\n { text: String(userMessageCount), color: COLOR.warn },\n { text: ` ${messagesSuffix}` },\n { text: ' · ', color: COLOR.mute },\n { text: String(session.turnCount), color: COLOR.warn },\n { text: ` ${turnsSuffix}` },\n ]\n if (showSessionShortcut) {\n segments.push(\n { text: ' · ', color: COLOR.mute },\n { text: 'ctrl+x', color: COLOR.warn },\n { text: ' session' },\n )\n }\n\n // Prepend the cwd when there's room alongside the title + stats.\n // Mirrors the SessionsScreen overlay: `TitleOverlay` reserves\n // WRAP(2) + META_WRAP(2) + GAP(2) = 6 cells against `termWidth - 4`\n // (terminal width minus the screen wrapper's 1-cell padding on each\n // side and the bordered box's two corners), plus 2 extra cells when\n // a status spinner rides the title slot. Drop the cwd entirely\n // below `MIN_CWD` so we don't show a glyph-only `…/x` fragment.\n const OVERLAY_RESERVED = 6\n const iconReserve = hasStatusIcon ? 2 : 0\n const statsLen = segments.reduce((sum, s) => sum + s.text.length, 0)\n const SEP_LEN = ' · '.length\n const cwdBudget = Math.max(\n 0,\n termWidth - 4 - titleText.length - iconReserve - OVERLAY_RESERVED - statsLen - SEP_LEN,\n )\n const MIN_CWD = 6\n if (cwdBudget >= MIN_CWD) {\n segments.unshift(\n { text: compactPath(cwd, cwdBudget), color: COLOR.dim },\n { text: ' · ', color: COLOR.mute },\n )\n }\n return segments\n }, [cwd, session, userMessageCount, COLOR, showSessionShortcut, termWidth, titleText, hasStatusIcon])\n\n // Prior user prompts sourced from the transcript itself — kept in submit\n // order so the history-navigation feels like a regular shell. `user-prompt`\n // events carry the raw prompt (no `❯ ` prefix), so the history is replayed\n // verbatim — including the rare case of a user message that starts with `❯`.\n const userPrompts = useMemo(\n () => events.filter(e => e.kind === 'user-prompt').map(e => e.text),\n [events],\n )\n\n // (The agent-status glyph that used to live in the title overlay has\n // moved to the Footer's left section — see `Footer status=…` in\n // `app.tsx`. Title overlay only carries title + meta now.)\n\n // Tracks whether the prompt's `/skill` / `@file` completion popup is\n // currently visible. Wired from `PromptBlock`'s `onPopupOpenChange`\n // and used to hide the queue box while the popup is open — see the\n // comment on the queue render below.\n const [completionPopupOpen, setCompletionPopupOpen] = useState(false)\n // Stable identity so `PromptBlock`'s `useEffect([popupOpen, onPopupOpenChange])`\n // doesn't fire on every parent re-render. Also forwards to the host's\n // `onPopupOpenChange` for any global gates (e.g. esc-abort suppression\n // in `app.tsx`) that depend on the popup state.\n const handlePopupOpenChange = useCallback((open: boolean) => {\n setCompletionPopupOpen(open)\n onPopupOpenChange?.(open)\n }, [onPopupOpenChange])\n\n // File-edit approvals (`edit` / `multi_edit` / `write_file`) take over\n // the transcript slot so the modal fills the exact conversation area\n // — same shape, prompt + footer stay anchored at the bottom. The\n // transcript stays mounted (display: 'none') so its internal state\n // — scroll position, computed turn anchors, lazy markdown chrome —\n // survives the modal open/close cycle.\n const fileEditPending = pending && isFileEditTool(pending.tool) ? pending : null\n\n // Signal \"modal is on screen\" so useModalAwareFocus blurs background\n // inputs and the app-level useKeyboard suppresses global shortcuts.\n // We use lock/unlock instead of open/close to avoid rendering a\n // full-screen overlay that would intercept mouse events (scroll, etc.).\n const modal = useModal()\n useEffect(() => {\n if (!fileEditPending)\n return\n modal.lock()\n return () => modal.unlock()\n }, [fileEditPending, modal])\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.border,\n flexGrow: 1,\n flexDirection: 'column',\n // Hide instead of unmounting so the Transcript's internal state\n // (memoized turn anchors, scrollbox position, mounted markdown\n // chrome) survives a modal open → close round-trip. OpenTUI's\n // `visible: false` maps to Yoga's `Display.None`, removing the\n // box from layout entirely so the sibling modal claims the slot\n // via its own `flexGrow: 1`.\n visible: !fileEditPending,\n }}\n >\n <Transcript\n events={events}\n settings={settings}\n selectedTurnId={selectedTurnId ?? null}\n // Hide the throbber while the agent is *waiting on us* — a\n // pending approval (file-edit modal, command gate) or a\n // pending question. In those states the agent isn't actually\n // working, it's blocked on user input, so the \"thinking\"\n // pulse would be misleading.\n busy={busy && !pending && !pendingInteraction}\n />\n </box>\n {fileEditPending && (\n // Key on the request id so React force-remounts when the safe-mode\n // queue advances to the next pending approval. Without the key,\n // `MultiEditApprovalModal`'s `mask` / `cursor` / `zone` state and\n // `SingleEditApprovalModal`'s `selected` carry over from the\n // previous call, leaving the user looking at a list whose\n // checkboxes match the prior modal, not the current one.\n <FileEditApprovalModal\n key={fileEditPending.id}\n request={fileEditPending}\n onDecide={onApproval}\n />\n )}\n\n {/*\n Slot priority below the transcript:\n 1. Non-file-edit safe-mode approval — bash gates, etc.\n 2. Pending interaction (plan / question) — the agent paused\n waiting on the user. Live (mid-run) and resumed cases\n share this slot; the App layer swaps the resolver.\n 3. Otherwise the PromptBlock is ALWAYS mounted, even while\n `busy` is true — that's what lets the user type ahead\n during a stream and queue messages for after it lands.\n The \"something's running\" affordance lives next to the\n session title (see `titleStatusIcon` below); a separate\n `QueuedMessagesBlock` above the prompt stacks any\n type-ahead prompts waiting for the drain loop.\n\n File-edit pending falls through to the prompt branch — the\n modal above owns the conversation area, esc/decisions/etc., so\n the prompt stays interactive (queue-only while `busy`).\n\n The pending-interaction case can fire while `busy` is still true\n (the agent's `run()` Promise is awaiting the tool's Promise), so\n it must beat `busy` in the priority chain.\n */}\n {pending && !fileEditPending\n ? <ApprovalBlock request={pending} onPick={onApproval} />\n : pendingInteraction\n ? <InteractionBlock request={pendingInteraction} onResolve={onInteraction} />\n : (\n <>\n {/*\n Hide the queue box while the `/skill` / `@file`\n completion popup is open. The popup's absolute-anchor\n paints above the prompt, and OpenTUI's `scrollbox`\n (the queue's viewport) composites its content at the\n end of its parent's paint pass, which makes them\n visually fight regardless of `zIndex` — the queue's\n rows bleed THROUGH the popup at the rows where they\n overlap. Suppressing the queue while the user is in\n the middle of a completion sidesteps the renderer\n conflict, and the contextual hit is small: the user\n isn't navigating the queue while typing a reference.\n Queue reappears the instant the popup closes (esc /\n commit / clear trigger).\n */}\n {queuedMessages.length > 0 && !completionPopupOpen && (\n <QueuedMessagesBlock\n messages={queuedMessages}\n selectionIndex={queueSelectionIndex}\n shortcuts={queueShortcuts}\n />\n )}\n {/* Subtle \"currently working on\" badge — hides itself when\n no `in_progress` todo exists, no live run is attached,\n or the user has disabled the indicator in Settings.\n Sits between the queue block and the prompt so the\n bar reads as part of the prompt-zone affordances. */}\n <TodoIndicator session={liveSession} />\n <PromptBlock\n userPrompts={userPrompts}\n onSubmit={onSubmit}\n completionProviders={completionProviders}\n onPopupOpenChange={handlePopupOpenChange}\n // Blur the textarea while the user is navigating either\n // the transcript (select-turn) OR the queue box — both\n // states route up/down/return to the App keyboard\n // handler instead of moving the input cursor. The\n // mode value also drives which hint set the prompt\n // shows below it.\n selectMode={\n queueSelectionIndex != null\n ? 'queue'\n : selectedTurnId != null\n ? 'turn'\n : null\n }\n triggerHints={promptTriggerHints}\n busy={busy}\n onEnterQueueFromEmpty={onEnterQueueFromEmptyPrompt}\n />\n </>\n )}\n\n <TitleOverlay title={titleText} meta={metaSegments} />\n </box>\n )\n}\n\n/** Max chars per scalar argument in the approval preview. */\nconst APPROVAL_ARG_MAX = 80\n\n/**\n * Render `{ path: 'x.ts', contents: 'long string' }` as\n * `path: \"x.ts\", contents: \"long string…\"` — readable, per-key, truncated\n * per value rather than dumping `JSON.stringify(input)` (which produces an\n * illegible 50KB blob for `write_file` etc.).\n */\nfunction formatApprovalArgs(input: Record<string, unknown>): string {\n const parts: string[] = []\n for (const [key, raw] of Object.entries(input)) {\n let value: string\n if (typeof raw === 'string') {\n const escaped = raw.replace(/\\n/g, '\\\\n')\n value = escaped.length > APPROVAL_ARG_MAX\n ? `\"${escaped.slice(0, APPROVAL_ARG_MAX)}…\"`\n : `\"${escaped}\"`\n }\n else {\n const json = JSON.stringify(raw)\n if (json.length > APPROVAL_ARG_MAX) {\n // Preserve the structural closer on object/array truncations so the\n // value reads as truncated JSON rather than a syntactically broken\n // fragment (`{ \"a\": 1, \"b…` is misleading; `{ \"a\": 1, \"b…}` reads).\n const closer = json[0] === '{' ? '…}' : json[0] === '[' ? '…]' : '…'\n value = `${json.slice(0, APPROVAL_ARG_MAX)}${closer}`\n }\n else {\n value = json\n }\n }\n parts.push(`${key}: ${value}`)\n }\n return parts.join(', ')\n}\n\n// `description` is required by OpenTUI's `SelectOption` shape even when the\n// select is rendered with `showDescription={false}` — passing an empty string\n// keeps the type happy and is invisible at the render layer.\ninterface DecisionOption { name: string, description: string, value: ApprovalDecision }\n\n/**\n * Inline approval picker — replaces the chat input while a tool call is\n * pending. Three options:\n * - **accept once** — let this call execute, don't persist anything.\n * - **accept + remember** — execute + add a `projects.json` entry so the\n * same shape doesn't prompt again in this directory.\n * - **deny** — refuse the call. The model gets `Blocked: …` and adapts.\n *\n * Esc aborts the whole run via the parent keyboard handler; per-call\n * accept/deny only happens through the select below.\n *\n * Layout is fully pinned so the picker never overlaps with itself or the\n * transcript above:\n *\n * - Outer `<box>` has an explicit `height`. The slot below the transcript\n * adapts (the chat container is column-flex), so we control exactly how\n * many rows we occupy.\n * - Summary row is a `<box height: 1, overflow: hidden>` wrapping a\n * `<text wrapMode=\"none\">` — a 500-char tool-call preview can never\n * wrap to row 2 and push the select off-screen.\n * - `<select showDescription={false}>` keeps each option to exactly one\n * row. Hints live in the `name` string after a `·` separator. Without\n * this, the default `showDescription: true` makes every option take 2\n * rows, and a `height: options.length` select would overdraw into the\n * summary above (the original bug).\n */\nfunction ApprovalBlock({\n request,\n onPick,\n}: {\n request: ApprovalRequest\n onPick: (decision: ApprovalDecision) => void\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n\n // File-edit tools (edit / multi_edit / write_file) are routed to the\n // inline `FileEditApprovalModal` in `ChatScreen`'s transcript slot\n // before reaching this block — see the `pending && !fileEditPending`\n // guard above. This component only handles the generic shell-call /\n // bash-style approval prompt.\n\n const summary = useMemo(\n () => `${request.tool}(${formatApprovalArgs(request.input)})`,\n [request.tool, request.input],\n )\n\n const options = useMemo<DecisionOption[]>(() => {\n const safelistEntry = suggestSafelistEntry(request.tool, request.input)\n return [\n { name: 'accept once · allow this call only', description: '', value: 'accept-once' },\n { name: 'accept for session · auto-approve matching calls until you quit', description: '', value: 'accept-session' },\n { name: `accept + remember · add \"${safelistEntry}\" to projects.json`, description: '', value: 'accept-safelist' },\n { name: 'deny · refuse — the model will see Blocked', description: '', value: 'deny' },\n ]\n }, [request.tool, request.input])\n\n // border (2) + summary (1) + one row per option = total outer height.\n const height = 2 + 1 + options.length\n\n return (\n <box\n title=\" approve tool call · esc to abort run \"\n style={{\n border: true,\n borderColor: COLOR.warn,\n paddingLeft: 1,\n paddingRight: 1,\n paddingTop: 0,\n paddingBottom: 0,\n height,\n flexDirection: 'column',\n flexShrink: 0,\n }}\n >\n <box style={{ height: 1, overflow: 'hidden', flexShrink: 0 }}>\n <text fg={COLOR.model} wrapMode=\"none\">\n <span fg={COLOR.warn}>↳ </span>\n {summary}\n </text>\n </box>\n <select\n {...SELECT_THEME}\n focused={focused}\n options={options}\n showDescription={false}\n wrapSelection\n onSelect={(_idx, option) => {\n if (option)\n onPick(option.value)\n }}\n style={{ height: options.length, flexShrink: 0 }}\n />\n </box>\n )\n}\n\n/**\n * One entry in the queued-messages preview list shown above the prompt\n * input. Only the displayable text + the user-provided ref spans are\n * surfaced — the `references` carry-through happens entirely in the\n * App layer (see `messageQueueRef`); this view doesn't need to know\n * about them.\n */\nexport interface QueuedPreview {\n /** Raw prompt text the user typed (no `❯` prefix). */\n text: string\n /**\n * Optional chip spans from the original prompt (`@files`, `/skills`)\n * — passed through so the queued entry highlights skill / file names\n * the same way the echoed user-prompt event does once it lands in\n * the transcript.\n */\n refs?: readonly { start: number, end: number, providerId: string }[]\n}\n\n/**\n * Resolved keybinding specs the queue UI surfaces to the user. Each\n * field is the literal string from `keybindings.json` (\"ctrl+return\",\n * \"backspace\" — or whatever the user has configured) formatted for\n * display via {@link formatBindingForDisplay} at render time so any\n * customization (e.g. `\"cmd+up\"`, `\"delete\"`) shows the actual key.\n */\nexport interface QueueShortcuts {\n enter: string\n push: string\n drop: string\n}\n\n/**\n * Maximum row count the queue box renders at once. A heavy queue\n * (think 18+ entries) would otherwise sprawl up the screen and crowd\n * the transcript; capping at 5 keeps the block proportional and\n * everything beyond scrolls behind the scrollbar. The user navigates\n * older entries with `↑` (selection mode) and the focused row is\n * auto-scrolled into view via `scrollChildIntoView`.\n */\nconst QUEUE_VISIBLE_ROWS = 5\n\n/** Stable anchor id per queue row — drives `scrollChildIntoView`. */\nfunction queueRowId(index: number): string {\n return `queued-msg-${index}`\n}\n\n/**\n * Stacked preview of prompts the user typed while a previous run was in\n * flight. Sits between the transcript and the prompt input; the drain\n * loop in the App pops each entry FIFO and echoes it into the transcript\n * via `runSingleMessage`, at which point it disappears from this block.\n *\n * When the user enters queue-selection mode (`selectionIndex != null`):\n * - the focused row gets a `▶ ` marker + selection background, and\n * - inline action hints (\"ctrl+↵ push · ⌫ drop\") sit right-aligned on\n * that row only — every other row stays clean so the eye lands on\n * the actionable affordance for the focused message.\n *\n * Wraps the rows in a `<scrollbox>` capped at {@link QUEUE_VISIBLE_ROWS}\n * so heavy queues don't push the prompt off the screen; the focused\n * entry is brought into view on every selection change so navigation\n * feels continuous even when the cursor moves past the viewport.\n *\n * Rendered with a thin top border + a \"queued\" left-title so the box\n * reads as a discrete waiting area — distinct from both the transcript\n * (where committed messages live) and the prompt (where the user is\n * typing the next one). Dropped entirely when the queue is empty.\n */\nfunction QueuedMessagesBlock({\n messages,\n selectionIndex,\n shortcuts,\n}: {\n messages: readonly QueuedPreview[]\n selectionIndex: number | null\n shortcuts: QueueShortcuts | undefined\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n // Discoverability hint on the top-right corner. When the user hasn't\n // bound a dedicated `enter queue` shortcut (the default) we surface\n // the natural path — `↑ select` — instead of an empty hint. Once\n // selection is active, swap to `esc exit` since the enter shortcut\n // would be a no-op anyway.\n const enterHintKey = shortcuts?.enter\n ? formatBindingForDisplay(shortcuts.enter)\n : '↑'\n // Auto-scroll the focused row into view on every selection change.\n // Deferred a frame because OpenTUI computes scroll geometry during the\n // post-commit layout pass — synchronous reads after a React commit\n // would see stale measurements. Same dance the settings modal +\n // question wizard do.\n useEffect(() => {\n if (selectionIndex == null)\n return\n const sb = scrollboxRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollChildIntoView(queueRowId(selectionIndex))\n })\n return () => cancelAnimationFrame(handle)\n }, [selectionIndex])\n // Number of rows the scrollbox renders. Caps at QUEUE_VISIBLE_ROWS so\n // the box reaches a steady-state height fast; shorter queues only\n // claim the height they need.\n const visibleRows = Math.min(messages.length, QUEUE_VISIBLE_ROWS)\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n <box\n style={{\n flexDirection: 'column',\n flexShrink: 0,\n border: ['top'],\n borderColor: COLOR.border,\n paddingLeft: 1,\n paddingRight: 1,\n paddingTop: 0,\n }}\n title={` queued · ${messages.length} `}\n titleAlignment=\"left\"\n >\n <scrollbox\n ref={scrollboxRef}\n // Never grab focus — the parent owns navigation via\n // `useKeyboard`. Mouse-wheel scroll still works as a fallback.\n focusable={false}\n // Pin the viewport to the bottom by default so the newest\n // queued entry (closest to the prompt) is always on screen.\n // `scrollChildIntoView` overrides this when the user is\n // actively navigating an older entry.\n stickyScroll\n stickyStart=\"bottom\"\n style={{ height: visibleRows, flexShrink: 0 }}\n >\n {messages.map((msg, i) => {\n const focused = selectionIndex === i\n const bg = focused ? SURFACE.selection : undefined\n return (\n <box\n key={i}\n id={queueRowId(i)}\n style={{\n flexDirection: 'row',\n flexShrink: 0,\n paddingLeft: 1,\n paddingRight: 1,\n backgroundColor: bg,\n }}\n >\n <text wrapMode=\"none\" style={{ flexGrow: 1 }}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={COLOR.mute}>{'❯ '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{previewText(msg.text)}</span>\n </text>\n {focused && shortcuts && (\n <text wrapMode=\"none\" fg={COLOR.dim}>\n <span fg={COLOR.warn}>{formatBindingForDisplay(shortcuts.push)}</span>\n <span fg={COLOR.dim}>{' push'}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.warn}>{formatBindingForDisplay(shortcuts.drop)}</span>\n <span fg={COLOR.dim}>{' drop'}</span>\n </text>\n )}\n </box>\n )\n })}\n </scrollbox>\n </box>\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n {selectionIndex == null\n ? (\n <>\n <span fg={COLOR.warn}>{enterHintKey}</span>\n <span fg={COLOR.dim}>{' select'}</span>\n </>\n )\n : (\n <>\n <span fg={COLOR.warn}>esc</span>\n <span fg={COLOR.dim}>{' exit'}</span>\n </>\n )}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n </box>\n )\n}\n\n/**\n * Squash a multi-line prompt into a single line for the queued-preview\n * row — newlines become `↵` markers so the structure is still\n * discoverable but the row stays exactly 1 cell tall, regardless of how\n * the user composed the message. No truncation: OpenTUI's `wrapMode=\"none\"`\n * clips at the box's right edge already.\n */\nfunction previewText(text: string): string {\n return text.replace(/\\n+/g, ' ↵ ')\n}\n\n/** Stable empty providers reference — avoids `useCompletion` rerun on every render when no providers are wired. */\nconst EMPTY_PROVIDERS: readonly CompletionProvider<unknown>[] = []\n\nfunction PromptBlock({\n userPrompts,\n onSubmit,\n completionProviders,\n onPopupOpenChange,\n selectMode = null,\n triggerHints,\n busy = false,\n onEnterQueueFromEmpty,\n}: {\n userPrompts: string[]\n onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => void\n completionProviders?: readonly CompletionProvider<unknown>[]\n onPopupOpenChange?: (open: boolean) => void\n /**\n * Drives both the textarea-focus gate and the prompt-hint label set:\n *\n * - `null` → textarea focused, normal / busy hints depending on\n * the agent's run state.\n * - `'turn'` → user is navigating the transcript via ctrl+s. Hints\n * show \"↑↓ navigate · ↵ open · esc exit\".\n * - `'queue'` → user is navigating the type-ahead queue box above\n * the prompt. Hints show \"↑↓ navigate · esc exit\"\n * (the per-row push / drop hints live ON the queue\n * rows themselves, so this set stays minimal).\n */\n selectMode?: PromptSelectMode | null\n /** Optional trigger hints appended to the right overlay when there's room. */\n triggerHints?: readonly Hint[]\n /**\n * `true` while the agent is mid-run. The textarea stays interactive so\n * the user can type-ahead; the submit-hint label swaps to \"queue\" so\n * the user knows their `↵` will defer instead of fire immediately. The\n * actual queue depth is rendered by the `QueuedMessagesBlock` above\n * the prompt, so we don't duplicate it on the hint row.\n */\n busy?: boolean\n /**\n * Optional handler invoked when the user presses `↑` on an empty\n * prompt buffer. Returning `true` means \"queue selection has taken\n * over, suppress history cycling for this keypress\"; `false` falls\n * through to the regular history navigation. Lets the queue claim\n * the most natural up-key without owning a global shortcut.\n */\n onEnterQueueFromEmpty?: () => boolean\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const textareaRef = useRef<TextareaRenderable | null>(null)\n /** Auto-grow: visible content rows the textarea currently occupies (clamped 1..5). */\n const [contentLines, setContentLines] = useState(MIN_CONTENT_LINES)\n // Files captured by the textarea's paste pipeline (image-path pastes,\n // multiline pastes folded into `paste.txt`). Cleared on submit so the\n // chip strip only ever reflects the in-progress prompt.\n const [attachments, setAttachments] = useState<Attachment[]>([])\n /**\n * Mirror of the textarea buffer + cursor, updated on every `onContentChange`.\n * Drives `useCompletion` — the textarea stays uncontrolled (we don't push\n * React state back into it on every keystroke), this is a read-only view\n * for the engine.\n */\n const [bufferState, setBufferState] = useState<{ text: string, cursor: number }>({ text: '', cursor: 0 })\n /**\n * History navigation state. `null` = not navigating (textarea owns its content).\n * Once the user enters history (up at top), we snapshot the draft and cycle.\n */\n const historyRef = useRef<{ idx: number, draft: string } | null>(null)\n\n const providers = completionProviders ?? EMPTY_PROVIDERS\n const completion = useCompletion(bufferState, providers)\n const popupOpen = completion.active != null && completion.items.length > 0\n\n useEffect(() => {\n onPopupOpenChange?.(popupOpen)\n }, [popupOpen, onPopupOpenChange])\n\n // Per-provider chip backgrounds on the live textarea. The submitted\n // echo paints chips as flex-row `<text bg=…>` segments; the textarea\n // (one OpenTUI edit buffer, no per-span children possible) gets the\n // same color story via `addHighlightByCharRange`. See `useChipHighlights`\n // for the full contract.\n const chipStyle = useChipStyle()\n useChipHighlights(textareaRef, completion.references, chipStyle)\n\n /**\n * Pull the latest buffer state from the OpenTUI textarea ref. Called from\n * `onContentChange` + `onKeyDown` so cursor moves (without text changes)\n * also re-evaluate the active trigger.\n */\n const syncBuffer = useCallback(() => {\n const ta = textareaRef.current\n if (!ta)\n return\n setBufferState({ text: ta.plainText, cursor: ta.cursorOffset })\n // Auto-grow tracks the TOTAL wrapped line count, not the logical\n // line count and not `virtualLineCount`. Two gotchas in play:\n // - `lineCount` only counts `\\n`-separated logical lines, so a\n // pasted single-logical-line block (or a wrapped paragraph)\n // would pin the box to 1 row.\n // - `ta.virtualLineCount` is VIEWPORT-relative — it counts the\n // wrapped rows currently visible inside the textarea's height,\n // not the total across the buffer. With a 1-row starting\n // viewport it returns ~1 even after pasting 20 wrapped lines,\n // so `setContentLines` never gets the signal to grow.\n // `editorView.getTotalVirtualLineCount()` is the actual all-buffer\n // wrap count and grows as text comes in, which is what the\n // auto-grow needs.\n const total = ta.editorView.getTotalVirtualLineCount()\n setContentLines(Math.max(MIN_CONTENT_LINES, total))\n }, [])\n\n const submit = useCallback(() => {\n const value = textareaRef.current?.plainText ?? ''\n // Submit is allowed with empty text iff there's at least one\n // attachment — drag-and-drop an image and hit enter should work\n // even if the user typed nothing.\n if (!value.trim() && attachments.length === 0)\n return\n onSubmit(value, completion.references, attachments)\n textareaRef.current?.clear()\n historyRef.current = null\n setBufferState({ text: '', cursor: 0 })\n setContentLines(MIN_CONTENT_LINES)\n setAttachments([])\n }, [onSubmit, completion.references, attachments])\n\n const commitCompletion = useCallback(() => {\n const result = completion.commit()\n if (!result)\n return false\n const ta = textareaRef.current\n if (!ta)\n return false\n ta.setText(result.text)\n ta.cursorOffset = result.cursor\n syncBuffer()\n return true\n }, [completion, syncBuffer])\n\n /**\n * Multi-line paste handler. The textarea's default `handlePaste`\n * forwards the decoded bytes to `insertText` in one call, which the\n * native edit buffer treats as raw characters — embedded `\\n` lands\n * as a literal glyph rather than splitting the line, and bracketed\n * paste from macOS Terminal often substitutes CR for LF on top of\n * that. The result was a multi-line paste collapsing to one giant\n * row.\n *\n * We `preventDefault` to skip the textarea's default path, normalize\n * `\\r\\n` / `\\r` to `\\n`, then drive the edit buffer explicitly:\n * `insertText` for each segment, `newLine()` between segments. The\n * cursor is positioned at the end of the pasted block (the natural\n * place to keep typing).\n */\n const onPaste = useCallback((event: PasteEvent) => {\n event.preventDefault()\n const ta = textareaRef.current\n if (!ta)\n return\n const raw = stripAnsiSequences(decodePasteBytes(event.bytes))\n const normalized = raw.replace(/\\r\\n?/g, '\\n')\n if (normalized.length === 0)\n return\n\n // File-path paste: a single-line paste that resolves to a real file on\n // disk gets attached instead of inserted as text. Terminals deliver\n // dragged files as bracketed-paste with the path; some wrap it in\n // quotes or a file:// URL.\n if (!normalized.includes('\\n')) {\n const trimmed = normalized.trim().replace(/^[\"']|[\"']$/g, '')\n let resolved = trimmed.startsWith('file://') ? decodeURIComponent(trimmed.slice(7)) : trimmed\n if (resolved.startsWith('~/'))\n resolved = (process.env.HOME ?? '') + resolved.slice(1)\n if (resolved.length > 0 && (resolved.startsWith('/') || resolved.startsWith('.'))) {\n try {\n const st = statSync(resolved)\n if (st.isFile()) {\n const buf = readFileSync(resolved)\n const ext = (resolved.split('.').pop() ?? '').toLowerCase()\n const mediaType = IMAGE_MEDIA_TYPES[ext] ?? MIME_BY_EXT[ext] ?? 'application/octet-stream'\n setAttachments(prev => [...prev, { name: basename(resolved), content: buf, mediaType }])\n return\n }\n }\n catch {\n // statSync threw (ENOENT / EACCES / …) — fall through to the\n // normal paste path so the literal text still lands in the\n // textarea. The user pasted something path-shaped that didn't\n // resolve; better to show it than silently swallow.\n }\n }\n }\n\n // Multiline paste → fold into a `paste.txt` attachment so the prompt\n // doesn't balloon vertically. Distinct attachments per paste so\n // pasting twice produces two chips (the second is `paste.txt` again\n // — agent.run receives both document parts in submission order).\n if (normalized.includes('\\n')) {\n setAttachments(prev => [...prev, {\n name: 'paste.txt',\n content: Buffer.from(normalized, 'utf-8'),\n mediaType: 'text/plain',\n }])\n return\n }\n\n // Plain single-line paste — original insert pipeline.\n const parts = normalized.split('\\n')\n for (let i = 0; i < parts.length; i++) {\n if (i > 0)\n ta.newLine()\n const part = parts[i]\n if (part && part.length > 0)\n ta.insertText(part)\n }\n syncBuffer()\n }, [syncBuffer])\n\n const cycleHistory = useCallback((direction: -1 | 1) => {\n if (userPrompts.length === 0 || !textareaRef.current)\n return\n\n if (historyRef.current === null) {\n historyRef.current = {\n idx: userPrompts.length,\n draft: textareaRef.current.plainText,\n }\n }\n\n const nextIdx = historyRef.current.idx + direction\n\n if (nextIdx < 0)\n return // already at oldest\n\n if (nextIdx >= userPrompts.length) {\n textareaRef.current.setText(historyRef.current.draft)\n textareaRef.current.gotoBufferEnd()\n historyRef.current = null\n }\n else {\n textareaRef.current.setText(userPrompts[nextIdx])\n textareaRef.current.gotoBufferEnd()\n historyRef.current.idx = nextIdx\n }\n syncBuffer()\n }, [userPrompts, syncBuffer])\n\n /**\n * Key interception. OpenTUI fires `onKeyDown` BEFORE the textarea's\n * binding table (`Renderable.keypressHandler` invokes the user\n * listener first, then gates `handleKeyPress` on `defaultPrevented`).\n * That means a single `event.preventDefault()` here cleanly skips the\n * textarea's default action — no parallel binding-filter dance, no\n * stray newline when the popup owns Enter.\n *\n * Popup-owned keys (up/down/return/tab/escape): forward to the\n * completion engine and `preventDefault` so the textarea never sees\n * them — committing a selection should land the cursor at end-of-\n * insert + the trailing space from `insertText`, never an extra `\\n`.\n *\n * History keys: up/down at the top/bottom row of an idle buffer cycle\n * the prompt history. `preventDefault` after a successful cycle keeps\n * the cursor at the end of the recalled prompt; non-edge presses fall\n * through so the textarea moves the cursor as usual.\n */\n const onKeyDown = useCallback((event: KeyEvent) => {\n if (popupOpen) {\n if (event.ctrl || event.meta)\n return\n switch (event.name) {\n case 'up':\n completion.selectPrev()\n event.preventDefault()\n return\n case 'down':\n completion.selectNext()\n event.preventDefault()\n return\n case 'return':\n if (event.shift)\n return\n commitCompletion()\n event.preventDefault()\n return\n case 'tab':\n commitCompletion()\n event.preventDefault()\n return\n case 'escape':\n completion.dismiss()\n event.preventDefault()\n return\n default:\n }\n return\n }\n if (event.ctrl || event.shift || event.meta)\n return\n if (event.name !== 'up' && event.name !== 'down')\n return\n const buffer = textareaRef.current\n if (!buffer)\n return\n // History only fires at the very edges of the buffer:\n // - `↑` at offset 0 (first line, first column) cycles backwards.\n // - `↓` at offset == plainText.length (end of buffer) cycles forward.\n // Anywhere in between, the textarea owns the keypress and moves\n // the cursor naturally — including visually up/down across wrapped\n // rows. This is what makes a multi-line paste feel right: pressing\n // `↑` from the middle of a wrapped paragraph moves you up one\n // visual row instead of swapping the whole buffer for a recalled\n // prompt.\n //\n // Bridge step on `↑`: when the cursor is on the FIRST visual row\n // but not yet at column 0, we explicitly jump it to offset 0\n // instead of falling through to the textarea. Without this,\n // OpenTUI's `moveCursorUp` on the top row is a no-op — the user\n // would press `↑`, see nothing happen, then have to remember to\n // hit `home` / `ctrl+a` before `↑` would recall. With it, the\n // story is \"↑ on first line → cursor at start; ↑ again →\n // history\" — a clean two-tap from any first-line cursor position,\n // and a predictable N+2 taps from anywhere in a multi-line buffer.\n if (event.name === 'up') {\n if (buffer.cursorOffset !== 0) {\n const { row } = buffer.editorView.getCursor()\n if (row === 0) {\n event.preventDefault()\n buffer.cursorOffset = 0\n syncBuffer()\n }\n return\n }\n event.preventDefault()\n // Empty buffer hands `↑` off to the queue first — the natural\n // step is INTO the stacked queued messages above the prompt.\n if (buffer.plainText.length === 0 && onEnterQueueFromEmpty?.())\n return\n cycleHistory(-1)\n return\n }\n if (buffer.cursorOffset !== buffer.plainText.length)\n return\n event.preventDefault()\n cycleHistory(1)\n }, [popupOpen, completion, commitCompletion, cycleHistory, onEnterQueueFromEmpty, syncBuffer])\n\n // Content area + 2 lines of border = total box height.\n const contentHeight = Math.min(MAX_CONTENT_LINES, contentLines)\n const boxHeight = contentHeight + 2\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {/*\n Completion popup floats ABOVE the prompt as an absolute overlay\n (`bottom: 100%` anchors its bottom edge at the prompt's top edge)\n so opening/closing it doesn't shift the prompt or the transcript —\n previously the popup lived in-flow above the prompt, and every\n commit visibly jumped the prompt downward by the popup's height\n as it disappeared. The empty wrapper renders nothing when\n `CompletionPopup` returns null, so this only paints when there's\n actually something to show. Render order matters: declared after\n the bordered prompt below so the renderer paints it on top of\n the transcript area it overlaps.\n\n Works thanks to OpenTUI's default `overflow: 'visible'` (no scissor\n rect is pushed unless `overflow` is \"hidden\" or \"scroll\") — the\n popup can extend outside its parent's bounds without being clipped.\n */}\n {/*\n Inner wrapper anchors the absolute hint overlay at `top: 0`\n relative to the prompt box's top border — the bordered box\n itself can't host the overlay as a child because its scissor\n rect excludes the border row and would clip it.\n */}\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {attachments.length > 0 && (\n <box style={{ flexDirection: 'row', flexWrap: 'wrap', paddingLeft: 1, paddingRight: 1, paddingBottom: 0 }}>\n {attachments.map((att, idx) => {\n const sz = att.content.length\n const label = sz < 1024\n ? `${sz}B`\n : sz < 1024 * 1024\n ? `${(sz / 1024).toFixed(1)}KB`\n : `${(sz / (1024 * 1024)).toFixed(1)}MB`\n const icon = att.mediaType.startsWith('image/') ? '🖼' : '📎'\n const chipColor = resolveChipColor(SURFACE.chips, 'file')\n return (\n <text key={`${att.name}-${idx}`}>\n {idx > 0 ? ' ' : ''}\n {icon}\n <span fg={chipColor.fg} bg={chipColor.bg}>\n {' '}\n {att.name}\n {' '}\n (\n {label}\n )\n {' '}\n </span>\n </text>\n )\n })}\n </box>\n )}\n <box\n style={{\n border: true,\n borderColor: selectMode ? COLOR.warn : COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: boxHeight,\n flexDirection: 'column',\n }}\n >\n <textarea\n ref={textareaRef}\n // Unfocus during select mode so up/down/return route to the\n // parent's keyboard handler (which navigates turns) instead\n // of being consumed as cursor movement / newline insertion.\n focused={focused && !selectMode}\n keyBindings={TEXTAREA_BINDINGS}\n // Pasting a long line — the canonical \"I dropped a stack\n // trace into the prompt\" flow — needs to wrap inside the\n // bordered box, not overflow off the right edge. `word` is\n // the natural choice here: prose lines break on spaces\n // (no mid-word ugliness), code lines without spaces still\n // wrap thanks to OpenTUI's char-fallback when no word\n // boundary fits. The textarea's auto-grow tracks\n // `virtualLineCount` so the height matches what the user\n // actually sees.\n wrapMode=\"word\"\n placeholder={\n selectMode === 'turn'\n ? '— turn-select mode — press ⎋ to resume typing —'\n : selectMode === 'queue'\n ? '— queue-select mode — press ⎋ to resume typing —'\n : 'Ask zidane…'\n }\n syntaxStyle={chipStyle}\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={submit}\n onContentChange={syncBuffer}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n />\n </box>\n <PromptHints selectMode={selectMode} triggerHints={triggerHints} busy={busy} />\n </box>\n {/*\n Suppress the completion popup entirely while the user is in\n select-turn mode — the textarea is locked there, so the buffer\n can't change and any pre-existing popup state (e.g. `/r` typed\n before pressing ctrl+s) would dangle as stale UI overlapping\n the turn-navigation hints. Completion state itself isn't torn\n down: when the user exits select mode the popup re-renders\n from the current buffer if a trigger is still active.\n */}\n {!selectMode && (\n // `zIndex: 10` puts the popup ABOVE any in-flow sibling that\n // happens to sit between the transcript and the prompt — most\n // visibly the queue-messages block, whose row text would\n // otherwise bleed THROUGH the popup at the rows where they\n // overlap. OpenTUI paints by document order without z by\n // default, and the popup's bottom:100% anchor can extend far\n // enough up to land on top of the queue when the catalog has\n // several matches. The modal layer uses the same trick at a\n // higher z (100) so a settings modal still wins.\n <box style={{ position: 'absolute', bottom: '100%', left: 0, right: 0, flexDirection: 'column', zIndex: 10 }}>\n <CompletionPopup state={completion} />\n </box>\n )}\n </box>\n )\n}\n\n/**\n * Modes that swap the prompt-hint set + blur the textarea so global\n * navigation shortcuts reach the App's keyboard handler.\n */\ntype PromptSelectMode = 'turn' | 'queue'\n\n/** Prompt-box shortcuts in normal mode — order matches reading flow. */\nconst PROMPT_HINTS_NORMAL: readonly Hint[] = [\n { key: '↵', label: 'send' },\n { key: 'shift+↵', label: 'newline' },\n { key: '↑↓', label: 'history' },\n { key: 'ctrl+s', label: 'messages' },\n]\n\n/**\n * Same shape as {@link PROMPT_HINTS_NORMAL} but with the submit verb\n * swapped to \"queue\" — a streaming response means `↵` defers the next\n * prompt instead of firing it immediately.\n */\nconst PROMPT_HINTS_BUSY: readonly Hint[] = [\n { key: '↵', label: 'queue' },\n { key: 'shift+↵', label: 'newline' },\n { key: '↑↓', label: 'history' },\n { key: 'esc', label: 'abort' },\n]\n\n/** Prompt-box shortcuts in select-turn mode — only the selection actions are valid. */\nconst PROMPT_HINTS_SELECT_TURN: readonly Hint[] = [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\n { key: 'esc', label: 'exit' },\n]\n\n/**\n * Prompt-box shortcuts in queue-selection mode. Push / drop hints live\n * on the focused queue row itself (see {@link QueuedMessagesBlock}) so\n * this set stays minimal — navigation + exit, no duplicated actions.\n */\nconst PROMPT_HINTS_SELECT_QUEUE: readonly Hint[] = [\n { key: '↑↓', label: 'navigate' },\n { key: 'esc', label: 'exit' },\n]\n\n/**\n * Inline shortcut hints for the prompt box. Drawn as an absolutely-\n * positioned overlay across the box's top border (right-aligned, one\n * cell from the corner) so it reads like a `<box title>` while keeping\n * the warn / dim / mute palette of the bottom bar — native `title` is\n * painted as part of the border with a single color and can't carry\n * per-segment hue.\n *\n * Must be declared AFTER the bordered box in its parent so the\n * renderer paints it on top, and must be a SIBLING (not a child) of\n * the box: bordered boxes clip their own absolute children via a\n * scissor rect that excludes the border row.\n *\n * Leading + trailing spaces overwrite the border characters underneath\n * the title text, mirroring native titles (`── sessions ──`).\n *\n * Swaps the hint set based on `selectMode` so the user sees only the\n * shortcuts that actually do something in the active mode.\n */\nfunction PromptHints({ selectMode, triggerHints, busy = false }: {\n selectMode: PromptSelectMode | null\n triggerHints?: readonly Hint[]\n /** Swap the submit verb to \"queue\" while a run is streaming. */\n busy?: boolean\n}) {\n const COLOR = useColors()\n const { settings } = useSettings()\n const { width: termWidth } = useTerminalDimensions()\n // Minimal UI mode drops the resting `↵ send · shift+↵ newline · ↑↓\n // history · ctrl+s messages` line from the prompt overlay. Only the\n // `@ files` / `/ skills` triggers remain. Select-turn / queue / busy\n // states keep their full hint sets — those are load-bearing\n // affordances (navigate, abort) that the minimal mode is not trying\n // to hide.\n const minimalResting = !selectMode && !busy && settings.uiMode === 'minimal'\n const primary = selectMode === 'turn'\n ? PROMPT_HINTS_SELECT_TURN\n : selectMode === 'queue'\n ? PROMPT_HINTS_SELECT_QUEUE\n : busy\n ? PROMPT_HINTS_BUSY\n : minimalResting\n ? EMPTY_HINTS\n : PROMPT_HINTS_NORMAL\n const hints = useMemo<readonly Hint[]>(() => {\n // Budget = the top-border row's renderable width = AppShell content\n // area (terminal - 2 cells of host padding) - 2 corner glyphs - 1\n // cell of breathing room from the corner. The overlay aligns to the\n // right, so this is the max width the title can occupy before it\n // crosses into the box's interior or the textarea's first row.\n const budget = Math.max(0, termWidth - 5)\n const tooLongCombined = !!triggerHints\n && triggerHints.length > 0\n && !selectMode\n && primary.length > 0\n && hintsLength(primary) + 3 + hintsLength(triggerHints) > budget\n const candidates = selectMode || !triggerHints || triggerHints.length === 0 || tooLongCombined\n ? primary\n : [...primary, ...triggerHints]\n // Drop hints from the right until the rendered row fits the budget.\n // Without this, a narrow terminal lets OpenTUI wrap the absolute\n // `<text>` overlay over the prompt box's border + first textarea\n // row, painting garbled glyphs where the corners would be.\n return clipHintsToWidth(candidates, budget)\n }, [selectMode, primary, triggerHints, termWidth])\n if (hints.length === 0)\n return null\n return (\n <text style={{ position: 'absolute', top: 0, right: 1 }} fg={COLOR.dim}>\n <span fg={COLOR.mute}>{' '}</span>\n {renderHintSpans(hints, COLOR)}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { KeyBindings } from '../chat/keybindings'\nimport type { SessionExportFormat } from '../chat/session-export'\nimport type { SessionData, SessionRun } from '../session'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useEffect, useRef, useState } from 'react'\nimport { ageString, compactPath, fmtTokens, shortId } from '../chat/format'\nimport { matchesBinding } from '../chat/keybindings'\nimport { useColors } from '../chat/theme-context'\nimport { errorMessage } from '../errors'\nimport { writeToClipboard } from './clipboard'\nimport { Spinner } from './components'\nimport { Modal, useModal } from './modal'\n\n/** Result handed back by the host's export handler — drives the success footer. */\nexport interface SessionExportResult {\n /** Absolute path of the written file. */\n filepath: string\n /** Format that was written — used to label the success banner. */\n format: SessionExportFormat\n}\n\n// ---------------------------------------------------------------------------\n// SessionDetailsModal — actions and stats for an entire session.\n//\n// Mirrors the layout + interaction pattern of TurnDetailsModal:\n// - Top border carries the session title; bottom border carries the\n// short id + turn count summary.\n// - Body lists metadata (id, age, turn count) and aggregate token\n// usage rolled up from `session.runs`.\n// - Footer row owns the action shortcuts (single-letter, like turns).\n//\n// Keyboard:\n// - `g` — generate a title for the session from its recent turns\n// (shows an inline spinner while running, updates the header\n// on success, surfaces error in the footer on failure)\n// - `e` — export the session as Markdown under `.{prefix}/sessions/`\n// - `j` — export the session as JSON under `.{prefix}/sessions/`\n// - `d` — delete the entire session (two-press confirmation, esc cancels)\n// - `c` — copy the full session id to the clipboard via OSC 52\n// - esc — close\n//\n// Delete is destructive AND non-reversible (the on-disk session row goes\n// away). The two-press flow uses the same affordance as the turn modal\n// so muscle memory carries over.\n// ---------------------------------------------------------------------------\n\nexport interface SessionDetailsModalActions {\n /** Delete the entire session from the store. Parent handles teardown / navigation. */\n onDelete: (sessionId: string) => void | Promise<void>\n /**\n * Generate a title for the session via the active provider/model.\n * Resolves to the new title (already persisted by the parent); reject\n * with an Error to surface a failure message in the modal footer.\n *\n * Omit when title generation isn't wired (no provider picked yet, or\n * the host opted out) — the modal hides the `g` shortcut.\n *\n * `signal` is provided so a parent unmount (modal closed, session\n * switched) can abort the in-flight provider stream. Implementations\n * should forward it to whatever transport they use; an aborted\n * generation should reject (e.g. with the signal's reason).\n */\n onGenerateTitle?: (sessionId: string, signal: AbortSignal) => Promise<string>\n\n /**\n * Export the session to disk in the requested format. The host\n * decides where the file lands (typically `.{prefix}/sessions/`) and\n * returns the absolute path so the modal can show a confirmation\n * footer. Reject with an Error to surface a failure message.\n *\n * Omit when export isn't wired — the modal hides the `e` / `j`\n * shortcuts.\n */\n onExport?: (sessionId: string, format: SessionExportFormat) => Promise<SessionExportResult>\n\n /**\n * Compact older turns via an LLM-generated summary. The host calls\n * `compactConversation` and appends the synthetic `compact-summary`\n * marker turn so older turns stop reaching the provider. The model\n * sees everything from the marker forward verbatim; the user can\n * still scroll back to see the original turns.\n *\n * Resolves to a result envelope used by the modal to flash the\n * \"compacted N turns\" success banner. Reject with an Error to\n * surface a failure message.\n *\n * `signal` is provided so a parent unmount (modal closed, session\n * switched) can abort the in-flight summary call.\n *\n * Omit when compaction isn't wired (no provider picked yet, or the\n * host opted out) — the modal hides the `k` shortcut.\n */\n onCompact?: (sessionId: string, signal: AbortSignal) => Promise<SessionCompactResult>\n}\n\n/** What `onCompact` returns. Drives the modal's success banner. */\nexport interface SessionCompactResult {\n /** Number of turns absorbed into the summary. */\n replacedCount: number\n /**\n * Number of turns the PTL-retry path had to drop without summarizing.\n * Zero on first-try success. When non-zero, the modal warns the user\n * that some context was lost — those turns stay in the conversation\n * history but the new summary doesn't describe them.\n */\n droppedCount: number\n /**\n * Number of files re-injected as synthetic `read_file` results after\n * the compaction marker. Zero when the session had no active reads or\n * the host opted out of restoration.\n */\n restoredFiles: number\n /**\n * Number of skills re-injected as synthetic `skills_use` results\n * after the compaction marker.\n */\n restoredSkills: number\n /** Model used for the summary call. */\n model: string\n /** Total input tokens billed (including cache reads + creations). */\n inputTokens: number\n /** Output tokens of the summary itself. */\n outputTokens: number\n /**\n * Estimated wire-level context size the NEXT turn will start from.\n * Sum of the summary's tokens plus the restoration content's\n * byte-based estimate — captures what the model will actually see on\n * its next call, modulo the system prompt and any preserved tail\n * turns whose precise token count we don't know without re-asking\n * the provider.\n *\n * Drives both the post-compact success banner (\"→ N effective\") and\n * the chat footer's `ctx` indicator — `onCompactSession` writes this\n * value into `lastInputTokens` so the user sees the drop immediately\n * instead of waiting for the next real turn's `usage` update.\n */\n effectiveTokens: number\n}\n\nexport function SessionDetailsModal({\n session,\n title,\n isCurrent,\n actions,\n keybindings,\n}: {\n session: SessionData\n /**\n * Display title for the session. Falls back to the first user turn's\n * preview when omitted — the parent usually has a cached `SessionMeta`\n * title and threads it through so the modal header matches what the\n * sessions list / chat title bar are showing.\n */\n title?: string\n /** True when this is the actively-mounted session — gates the \"active\" tag in the header. */\n isCurrent: boolean\n actions: SessionDetailsModalActions\n /** Effective keybindings — the modal-internal letter actions resolve through this. */\n keybindings: KeyBindings\n}) {\n const COLOR = useColors()\n const modal = useModal()\n // Drives left-truncation of the cwd line so a long absolute path can't\n // blow past the modal's width on a narrow terminal. The modal sits at\n // ~60% of the terminal; we reserve ~10 cells for the `cwd ` prefix +\n // padding and floor at 24 so it stays at least somewhat readable.\n const { width: termWidth } = useTerminalDimensions()\n const cwdMaxWidth = Math.max(24, Math.floor(termWidth * 0.6) - 12)\n\n // Initial header — `metadata.title` wins if previously generated; the\n // host's cached `SessionMeta.title` is the next preference; finally\n // the in-memory fallback for sessions with no recorded title at all.\n const initialTitle = title\n ?? (typeof session.metadata?.title === 'string' ? session.metadata.title : undefined)\n ?? 'untitled'\n // Internalized so a successful `g` action updates the header without\n // round-tripping through the parent (the modal stays open afterward\n // so the user sees the new title in place).\n const [displayTitle, setDisplayTitle] = useState(initialTitle)\n const usage = aggregateUsage(session.runs)\n const turnCount = session.turns.length\n // User-message count = subset of turns where the human spoke. Pairs\n // with the chat title's meta (which counts the same thing) so the\n // user sees the same volume signal in both places.\n const userMessageCount = session.turns.filter(t => t.role === 'user').length\n const hasGenerate = actions.onGenerateTitle != null && turnCount > 0\n\n // Two-press confirmation state, mirrors TurnDetailsModal. `null` = first\n // press, action commits on the second. Esc clears it.\n const [pending, setPending] = useState<'delete' | null>(null)\n // Transient copy feedback. Resets to `idle` on the next action so the\n // success / failure line stays informative without lingering forever.\n const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'failed'>('idle')\n // Title-generation lifecycle. `idle` is the default footer hint; on\n // `g` we flip to `loading` and the spinner row replaces the action\n // hints. `failed` carries the error message into the footer; the user\n // can press `g` again to retry.\n const [titleStatus, setTitleStatus] = useState<'idle' | 'loading' | 'failed'>('idle')\n const [titleError, setTitleError] = useState<string | null>(null)\n // Export lifecycle. `writing` shows a spinner; `success` shows the\n // written path; `failed` shows the error with a retry hint. Both\n // `e` and `j` route through the same state machine since the modal\n // only ever runs one export at a time and the success banner\n // already names the format.\n const [exportStatus, setExportStatus] = useState<'idle' | 'writing' | 'success' | 'failed'>('idle')\n const [exportResult, setExportResult] = useState<SessionExportResult | null>(null)\n const [exportError, setExportError] = useState<string | null>(null)\n const hasExport = actions.onExport != null && turnCount > 0\n\n // Compact lifecycle. `running` shows a spinner; `success` shows the\n // \"compacted N turns\" banner; `failed` shows the error with a retry\n // hint. Gated on at least 2 turns so the modal doesn't offer\n // compaction on conversations that have nothing to compact.\n const [compactStatus, setCompactStatus] = useState<'idle' | 'running' | 'success' | 'failed'>('idle')\n const [compactResult, setCompactResult] = useState<SessionCompactResult | null>(null)\n const [compactError, setCompactError] = useState<string | null>(null)\n const hasCompact = actions.onCompact != null && turnCount >= 2\n\n // Abort handles for the in-flight async actions. Captured in refs so\n // (a) the unmount cleanup can abort whatever's outstanding, and (b) the\n // post-await setters can early-return when the modal already vanished\n // mid-stream (rare — the modal blocks Esc while loading — but still safe).\n const generationAbortRef = useRef<AbortController | null>(null)\n const compactAbortRef = useRef<AbortController | null>(null)\n // `mountedRef` flips false on unmount so the async handlers don't push\n // state into a tree React already tore down. The aborts cover the\n // provider calls; this covers the React side of the dance.\n const mountedRef = useRef(true)\n useEffect(() => () => {\n mountedRef.current = false\n generationAbortRef.current?.abort()\n generationAbortRef.current = null\n compactAbortRef.current?.abort()\n compactAbortRef.current = null\n }, [])\n\n // Resetting peer feedback states whenever a new action starts keeps\n // the action row deterministic: only ONE banner is ever live at a\n // time, and it's the most recent thing the user actually did. Without\n // this, a stale `✓ session id copied` would dangle through a\n // subsequent successful export, etc.\n const resetPeerFeedback = (keep: 'copy' | 'export' | 'title' | 'compact' | 'none' = 'none') => {\n if (keep !== 'copy')\n setCopyStatus('idle')\n if (keep !== 'export') {\n setExportStatus('idle')\n setExportResult(null)\n setExportError(null)\n }\n if (keep !== 'title') {\n // `titleStatus === 'loading'` is locked elsewhere (the keyboard\n // returns early), so we only ever reset from `idle` / `failed`.\n setTitleStatus(prev => prev === 'loading' ? prev : 'idle')\n setTitleError(null)\n }\n if (keep !== 'compact') {\n setCompactStatus(prev => prev === 'running' ? prev : 'idle')\n setCompactResult(null)\n setCompactError(null)\n }\n }\n const commitDelete = () => {\n modal.close()\n void actions.onDelete(session.id)\n }\n const handleCopy = () => {\n resetPeerFeedback('copy')\n setCopyStatus(writeToClipboard(session.id) ? 'copied' : 'failed')\n }\n const handleExport = async (format: SessionExportFormat) => {\n if (!actions.onExport || exportStatus === 'writing')\n return\n resetPeerFeedback('export')\n setExportStatus('writing')\n try {\n const result = await actions.onExport(session.id, format)\n if (!mountedRef.current)\n return\n setExportResult(result)\n setExportStatus('success')\n }\n catch (err) {\n if (!mountedRef.current)\n return\n setExportError(errorMessage(err))\n setExportStatus('failed')\n }\n }\n const handleGenerate = async () => {\n if (!actions.onGenerateTitle || titleStatus === 'loading')\n return\n resetPeerFeedback('title')\n setTitleStatus('loading')\n const ac = new AbortController()\n generationAbortRef.current = ac\n try {\n const next = await actions.onGenerateTitle(session.id, ac.signal)\n if (!mountedRef.current || ac.signal.aborted)\n return\n setDisplayTitle(next)\n setTitleStatus('idle')\n }\n catch (err) {\n if (!mountedRef.current || ac.signal.aborted)\n return\n setTitleError(errorMessage(err))\n setTitleStatus('failed')\n }\n finally {\n if (generationAbortRef.current === ac)\n generationAbortRef.current = null\n }\n }\n const handleCompact = async () => {\n if (!actions.onCompact || compactStatus === 'running')\n return\n resetPeerFeedback('compact')\n setCompactStatus('running')\n const ac = new AbortController()\n compactAbortRef.current = ac\n try {\n const result = await actions.onCompact(session.id, ac.signal)\n if (!mountedRef.current || ac.signal.aborted)\n return\n setCompactResult(result)\n setCompactStatus('success')\n }\n catch (err) {\n if (!mountedRef.current || ac.signal.aborted)\n return\n setCompactError(errorMessage(err))\n setCompactStatus('failed')\n }\n finally {\n if (compactAbortRef.current === ac)\n compactAbortRef.current = null\n }\n }\n\n useKeyboard((key) => {\n // Lock all shortcuts while a long-running action is in flight —\n // pressing `d` or another action mid-stream would race the async\n // update. Title generation, export, and compaction all share this lock.\n if (titleStatus === 'loading' || exportStatus === 'writing' || compactStatus === 'running')\n return\n if (key.name === 'escape' && pending) {\n setPending(null)\n return\n }\n if (matchesBinding(key, keybindings.sessionDelete)) {\n if (pending === 'delete')\n commitDelete()\n else\n setPending('delete')\n return\n }\n if (matchesBinding(key, keybindings.sessionCopyId)) {\n setPending(null)\n handleCopy()\n return\n }\n if (matchesBinding(key, keybindings.sessionGenerateTitle) && hasGenerate) {\n setPending(null)\n void handleGenerate()\n return\n }\n if (matchesBinding(key, keybindings.sessionExportMarkdown) && hasExport) {\n setPending(null)\n void handleExport('markdown')\n return\n }\n if (matchesBinding(key, keybindings.sessionExportJson) && hasExport) {\n setPending(null)\n void handleExport('json')\n return\n }\n if (matchesBinding(key, keybindings.sessionCompact) && hasCompact) {\n setPending(null)\n void handleCompact()\n return\n }\n // Any other key cancels a pending confirmation so the user isn't\n // surprised by a delayed delete after they navigated away.\n if (pending)\n setPending(null)\n })\n\n return (\n <Modal\n title={`session · ${displayTitle}`}\n bottomTitle={`#${shortId(session.id)} · ${turnCount} turn${turnCount === 1 ? '' : 's'}`}\n // Esc is handed to this component's own `useKeyboard` whenever it\n // matters: during loading (so Esc is intentionally inert — the footer\n // says \"generating title, please wait\") and during a pending delete\n // (so Esc clears `pending` instead of closing). Outside those states\n // Modal handles Esc normally.\n disableEscape={\n titleStatus === 'loading'\n || exportStatus === 'writing'\n || compactStatus === 'running'\n || pending !== null\n }\n >\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>id </span>\n <span fg={COLOR.model}>{session.id}</span>\n {isCurrent && (\n <>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.accent}>active</span>\n </>\n )}\n </text>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>created </span>\n <span fg={COLOR.dim}>{ageString(session.createdAt)}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>updated </span>\n <span fg={COLOR.dim}>{ageString(session.updatedAt)}</span>\n </text>\n\n {/*\n cwd line — own row so a long path doesn't push the dense\n `turns · user · runs · status` line off the right edge. Left-\n truncates with an ellipsis (the tail of a path is the\n recognizable part) when the terminal is narrow; untagged\n legacy sessions render the placeholder in `mute` so it reads\n as \"no data\" instead of a real value.\n */}\n <text fg={COLOR.dim} wrapMode=\"none\">\n <span fg={COLOR.mute}>cwd </span>\n {session.projectRoot\n ? <span fg={COLOR.dim}>{compactPath(session.projectRoot, cwdMaxWidth)}</span>\n : <span fg={COLOR.mute}>untagged</span>}\n </text>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>turns </span>\n <span fg={COLOR.warn}>{turnCount}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>user </span>\n <span fg={COLOR.warn}>{userMessageCount}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>runs </span>\n <span fg={COLOR.dim}>{session.runs.length}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>status </span>\n <span fg={statusColor(session.status, COLOR)}>{session.status}</span>\n </text>\n\n {usage.total > 0 && (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>tokens </span>\n <span fg={COLOR.model}>{fmtTokens(usage.total)}</span>\n <span fg={COLOR.mute}> · in </span>\n <span fg={COLOR.dim}>{fmtTokens(usage.input)}</span>\n <span fg={COLOR.mute}> · out </span>\n <span fg={COLOR.dim}>{fmtTokens(usage.output)}</span>\n {usage.cacheRead > 0 && (\n <>\n <span fg={COLOR.mute}> · cached </span>\n <span fg={COLOR.dim}>{fmtTokens(usage.cacheRead)}</span>\n </>\n )}\n {usage.cost > 0 && (\n <>\n <span fg={COLOR.mute}> · cost </span>\n <span fg={COLOR.dim}>{`$${usage.cost.toFixed(usage.cost < 0.01 ? 4 : 2)}`}</span>\n </>\n )}\n </text>\n )}\n\n <ActionRow\n pending={pending}\n copyStatus={copyStatus}\n titleStatus={titleStatus}\n titleError={titleError}\n hasGenerate={hasGenerate}\n exportStatus={exportStatus}\n exportResult={exportResult}\n exportError={exportError}\n hasExport={hasExport}\n compactStatus={compactStatus}\n compactResult={compactResult}\n compactError={compactError}\n hasCompact={hasCompact}\n keys={{\n delete: keybindings.sessionDelete,\n copy: keybindings.sessionCopyId,\n generate: keybindings.sessionGenerateTitle,\n markdown: keybindings.sessionExportMarkdown,\n json: keybindings.sessionExportJson,\n compact: keybindings.sessionCompact,\n }}\n />\n </Modal>\n )\n}\n\n/**\n * Footer action row — mirrors the turn-details modal's pattern. Swaps\n * between the default hint row, a delete-confirm prompt, copy\n * success/failure feedback, an in-flight title-generation spinner, and\n * a title-generation error message. The geometry stays a single row\n * across all states so the modal body never shifts.\n */\nfunction ActionRow({\n pending,\n copyStatus,\n titleStatus,\n titleError,\n hasGenerate,\n exportStatus,\n exportResult,\n exportError,\n hasExport,\n compactStatus,\n compactResult,\n compactError,\n hasCompact,\n keys,\n}: {\n pending: 'delete' | null\n copyStatus: 'idle' | 'copied' | 'failed'\n titleStatus: 'idle' | 'loading' | 'failed'\n titleError: string | null\n hasGenerate: boolean\n exportStatus: 'idle' | 'writing' | 'success' | 'failed'\n exportResult: SessionExportResult | null\n exportError: string | null\n hasExport: boolean\n compactStatus: 'idle' | 'running' | 'success' | 'failed'\n compactResult: SessionCompactResult | null\n compactError: string | null\n hasCompact: boolean\n /** Resolved binding strings for every action this row advertises. */\n keys: {\n delete: string\n copy: string\n generate: string\n markdown: string\n json: string\n compact: string\n }\n}) {\n const COLOR = useColors()\n\n if (titleStatus === 'loading')\n return <Spinner label=\"generating title — please wait\" />\n\n if (exportStatus === 'writing')\n return <Spinner label=\"exporting session — please wait\" />\n\n if (compactStatus === 'running')\n return <Spinner label=\"compacting older turns — please wait\" />\n\n if (titleStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>title generation failed</span>\n {titleError ? <span fg={COLOR.mute}>{` — ${titleError}`}</span> : null}\n {' · '}\n <span fg={COLOR.warn}>{keys.generate}</span>\n {' retry · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (exportStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>export failed</span>\n {exportError ? <span fg={COLOR.mute}>{` — ${exportError}`}</span> : null}\n {' · '}\n <span fg={COLOR.warn}>{keys.markdown}</span>\n {' / '}\n <span fg={COLOR.warn}>{keys.json}</span>\n {' retry · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (compactStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>compaction failed</span>\n {compactError ? <span fg={COLOR.mute}>{` — ${compactError}`}</span> : null}\n {' · '}\n <span fg={COLOR.warn}>{keys.compact}</span>\n {' retry · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (pending === 'delete') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>delete this session?</span>\n {' press '}\n <span fg={COLOR.error}>{keys.delete}</span>\n {' again to confirm · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </text>\n )\n }\n\n if (exportStatus === 'success' && exportResult) {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>{`✓ wrote ${exportResult.format === 'json' ? 'JSON' : 'Markdown'}`}</span>\n <span fg={COLOR.mute}>{' → '}</span>\n <span fg={COLOR.model}>{compactPath(exportResult.filepath)}</span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (compactStatus === 'success' && compactResult) {\n const restoredParts: string[] = []\n if (compactResult.restoredFiles > 0)\n restoredParts.push(`${compactResult.restoredFiles} file${compactResult.restoredFiles === 1 ? '' : 's'}`)\n if (compactResult.restoredSkills > 0)\n restoredParts.push(`${compactResult.restoredSkills} skill${compactResult.restoredSkills === 1 ? '' : 's'}`)\n const restoredLabel = restoredParts.length > 0 ? `restored ${restoredParts.join(' + ')}` : null\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>{`✓ compacted ${compactResult.replacedCount} turn${compactResult.replacedCount === 1 ? '' : 's'}`}</span>\n {compactResult.droppedCount > 0 && (\n <>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.warn}>{`${compactResult.droppedCount} dropped (too large)`}</span>\n </>\n )}\n {restoredLabel && (\n <>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.accent}>{restoredLabel}</span>\n </>\n )}\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.dim}>{fmtTokens(compactResult.inputTokens)}</span>\n <span fg={COLOR.mute}> in / </span>\n <span fg={COLOR.dim}>{fmtTokens(compactResult.outputTokens)}</span>\n <span fg={COLOR.mute}> out</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.accent}>{`→ ${fmtTokens(compactResult.effectiveTokens)}`}</span>\n <span fg={COLOR.mute}> effective</span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (copyStatus === 'copied') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>✓ session id copied</span>\n {' · '}\n <span fg={COLOR.warn}>{keys.delete}</span>\n {' delete · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (copyStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>copy failed (terminal may not support OSC 52)</span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n return (\n <text fg={COLOR.dim}>\n {hasGenerate && (\n <>\n <span fg={COLOR.warn}>{keys.generate}</span>\n {' title · '}\n </>\n )}\n {hasExport && (\n <>\n <span fg={COLOR.warn}>{keys.markdown}</span>\n /\n <span fg={COLOR.warn}>{keys.json}</span>\n {' export · '}\n </>\n )}\n {hasCompact && (\n <>\n <span fg={COLOR.warn}>{keys.compact}</span>\n {' compact · '}\n </>\n )}\n <span fg={COLOR.warn}>{keys.delete}</span>\n {' delete · '}\n <span fg={COLOR.warn}>{keys.copy}</span>\n {' copy · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n}\n\ninterface AggregatedUsage {\n input: number\n output: number\n cacheRead: number\n total: number\n cost: number\n}\n\n/**\n * Sum token + cost figures across every {@link SessionRun}. Preference\n * order for a run's tally is `totalUsage` → per-`turnUsage[]` sum.\n * `turnUsage` exists on every completed run; `totalUsage` is provider-\n * specific (set by some pi-ai adapters when the API surfaces it).\n *\n * Returns an aggregate of zeroes when no usage data is available — the\n * caller decides whether to render the tokens row at all.\n */\nfunction aggregateUsage(runs: readonly SessionRun[]): AggregatedUsage {\n const acc = { input: 0, output: 0, cacheRead: 0, cost: 0 }\n for (const run of runs) {\n if (run.totalUsage) {\n acc.input += run.totalUsage.input ?? 0\n acc.output += run.totalUsage.output ?? 0\n acc.cacheRead += run.totalUsage.cacheRead ?? 0\n }\n else if (run.turnUsage) {\n for (const u of run.turnUsage) {\n acc.input += u.input ?? 0\n acc.output += u.output ?? 0\n acc.cacheRead += u.cacheRead ?? 0\n }\n }\n if (run.cost)\n acc.cost += run.cost\n }\n return { ...acc, total: acc.input + acc.output }\n}\n\n/**\n * Color a `SessionData.status` for the metadata row — `idle` reads\n * normal, `running` warm (something's in flight), `error` red. Falls\n * back to dim for any future-added status the palette doesn't know.\n */\nfunction statusColor(status: SessionData['status'], COLOR: { dim: string, warn: string, error: string, accent: string }): string {\n switch (status) {\n case 'completed': return COLOR.accent\n case 'running': return COLOR.warn\n case 'error': return COLOR.error\n default: return COLOR.dim\n }\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable, ScrollBoxRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type { McpAuthStatus } from '../chat/mcp-auth-state'\nimport type { DiscoveredMcp, DiscoveryError } from '../chat/mcps-discovery'\nimport type { BooleanSettingKey } from '../chat/settings-context'\nimport type { Settings } from '../chat/types'\nimport type { SkillConfig } from '../skills'\nimport { homedir } from 'node:os'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useDiscoveryOptional } from '../chat/discovery-context'\nimport { useEnabledToggleSet } from '../chat/enabled-toggle-set'\nimport { useMcpAuthState } from '../chat/mcp-auth-context'\nimport { getMcpAuthStatus } from '../chat/mcp-auth-state'\nimport { SETTINGS_CHOICES, SETTINGS_TOGGLES, useSettings } from '../chat/settings-context'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\nimport { McpAuthorizingPanel } from './oauth-auth-block'\n\n// ---------------------------------------------------------------------------\n// SettingsModal — tabbed: General / Skills / MCP servers.\n//\n// Geometry:\n// - Top: tab strip (active highlighted on selection background).\n// - Search input — single-line, focused on mount; one shared query\n// filters whichever tab is active (skills, MCPs, and general rows\n// all carry pre-lowercased corpora so the filter is `term.every →\n// corpus.includes`).\n// - Content area: windowed list of the active tab's filtered rows.\n// - Hints: dynamic depending on tab + focused row state.\n//\n// Keys:\n// - ←/→ cycle tabs (preventDefault so the input cursor stays put).\n// - ↑/↓ navigate the active tab's list (per-tab cursor).\n// - ↵ toggle / cycle / pick on general; toggle enable on\n// skills/MCPs.\n// - ctrl+L MCP tab: login (needs-auth or error rows).\n// - ctrl+O MCP tab: logout (authed / authorizing / error rows).\n// - esc close, OR cancel an in-flight MCP login when focused\n// on an `authorizing` row (Modal's default esc-close\n// still fires — single keystroke cancels + dismisses).\n// ---------------------------------------------------------------------------\n\ninterface ToggleItem {\n kind: 'toggle'\n key: BooleanSettingKey\n label: string\n description: string\n}\ninterface ChoiceItem {\n kind: 'choice'\n key: keyof Settings\n label: string\n description: string\n options: readonly { value: unknown, label: string }[]\n}\ninterface ActionItem {\n kind: 'action'\n id: string\n label: string\n description: string\n onPick: () => void\n}\ntype GeneralItem = ToggleItem | ChoiceItem | ActionItem\n\nexport interface SettingsActions {\n /** Re-open the auth screen so the user can switch providers. */\n onReauth?: () => void\n /** Open `~/.zidane/keybindings.json` in `$EDITOR`. */\n onOpenKeybindings?: () => void\n /** Start an MCP OAuth flow. Resolves once tokens land or the flow aborts. */\n onLoginMcp?: (name: string) => Promise<void> | void\n /** Wipe stored MCP tokens. */\n onLogoutMcp?: (name: string) => void\n /** Abort an in-flight MCP OAuth flow. */\n onCancelLoginMcp?: (name: string) => void\n /**\n * Force a fresh skills rescan, bypassing the background SWR throttle.\n * Fired by `ctrl+R` on the Skills tab. When wired the host should\n * call `discoverProjectSkills` directly and replace the catalog —\n * an in-flight refresh promise resolves into the modal automatically\n * via the catalog prop.\n */\n onRefreshSkills?: () => Promise<void> | void\n /**\n * Re-parse the project's `mcps.json` files. Fired by `ctrl+R` on\n * the MCPs tab. Sync-shaped under the hood but typed as async so\n * future discovery paths (registry lookups, etc.) can stay\n * non-blocking without changing the contract.\n */\n onRefreshMcps?: () => Promise<void> | void\n}\n\ntype TabId = 'general' | 'skills' | 'mcps'\n\nconst TAB_ORDER: readonly TabId[] = ['general', 'skills', 'mcps']\nconst TAB_LABELS: Record<TabId, string> = {\n general: 'General',\n skills: 'Skills',\n mcps: 'MCP servers',\n}\n\n// Stable scroll-anchor for the focused row inside the modal's scrollbox.\n// One id per visible row index — the `scrollChildIntoView` call below\n// re-uses these whenever the cursor moves or the user switches tab.\nfunction anchorIdFor(index: number): string {\n return `settings-row-${index}`\n}\n\n// Title column alignment — EVERY row's title starts at column 6. Toggle /\n// skill / MCP rows fill columns 2-5 with their `[ ] ` checkbox; choice /\n// action rows pad the same span with whitespace so titles line up in a\n// single column down the list. Reads as a clean grid: the eye doesn't have\n// to jump between indent levels when scanning across kinds.\nconst COL_TITLE = ' ' // 6 spaces (marker [2] + checkbox/spacer [4])\nconst SPACER_CHECKBOX_WIDTH = ' ' // 4 spaces — same visual width as `[ ] `\n\n// ---------------------------------------------------------------------------\n\nexport function SettingsModal({\n skillsCatalog: skillsCatalogProp,\n mcpsCatalog: mcpsCatalogProp,\n mcpsErrors: mcpsErrorsProp,\n actions,\n}: {\n /**\n * Embedder fallback — discovered skills when no `<DiscoveryProvider>`\n * is mounted. In the in-app flow this is ignored; the modal reads\n * live state from `useDiscovery()` so a `ctrl+R` rescan surfaces\n * immediately even with the modal already open.\n */\n skillsCatalog?: readonly SkillConfig[]\n /** Embedder fallback — discovered MCP servers; superseded by context when present. */\n mcpsCatalog?: readonly DiscoveredMcp[]\n /** Embedder fallback — `mcps.json` parse errors; superseded by context when present. */\n mcpsErrors?: readonly DiscoveryError[]\n actions?: SettingsActions\n} = {}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const { settings, toggle: toggleBoolean, setSetting } = useSettings()\n const authState = useMcpAuthState()\n const inputRef = useRef<InputRenderable | null>(null)\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Prefer the live discovery context when the host has wired one\n // (the standard TUI shell). Falling back to the props keeps the\n // standalone modal usable for embedders without `<DiscoveryProvider>`.\n const discovery = useDiscoveryOptional()\n const skillsCatalog = discovery?.skillsCatalog ?? skillsCatalogProp ?? []\n const mcpsCatalog = discovery?.mcpsCatalog ?? mcpsCatalogProp ?? []\n const mcpsErrors = discovery?.mcpsErrors ?? mcpsErrorsProp\n\n const skillsToggle = useEnabledToggleSet<SkillConfig>({\n catalog: skillsCatalog,\n keyOf: s => s.name,\n settingKey: 'enabledSkills',\n })\n const mcpsToggle = useEnabledToggleSet<DiscoveredMcp>({\n catalog: mcpsCatalog,\n keyOf: m => m.config.name,\n settingKey: 'enabledMcps',\n })\n\n const [activeTab, setActiveTab] = useState<TabId>('general')\n const [cursorByTab, setCursorByTab] = useState<Record<TabId, number>>({\n general: 0,\n skills: 0,\n mcps: 0,\n })\n const [query, setQuery] = useState('')\n\n // Grab focus on mount — same reasoning as the model picker: the\n // background renderable owned focus before the modal opened, and\n // OpenTUI's prop diff doesn't always re-route focus to a freshly\n // mounted `focused` input. Force it once.\n useEffect(() => {\n inputRef.current?.focus()\n }, [])\n\n // ---- general rows -----------------------------------------------------\n // Toggle / choice rows are static; action rows depend on which callbacks\n // the host wired. Skills + MCPs used to live here as \"open sub-modal\"\n // actions; they're now dedicated tabs so the rows disappear.\n const generalItems = useMemo<GeneralItem[]>(() => {\n const toggles: GeneralItem[] = SETTINGS_TOGGLES.map(t => ({ kind: 'toggle' as const, ...t }))\n const choices: GeneralItem[] = SETTINGS_CHOICES.map(c => ({ kind: 'choice' as const, ...c }))\n const acts: ActionItem[] = []\n if (actions?.onOpenKeybindings) {\n acts.push({\n kind: 'action',\n id: 'keybindings',\n label: 'Keybindings',\n description: 'open ~/.zidane/keybindings.json (restart to apply)',\n onPick: actions.onOpenKeybindings,\n })\n }\n if (actions?.onReauth) {\n acts.push({\n kind: 'action',\n id: 'reauth',\n label: 'Authentication',\n description: 'switch provider, add another, or re-authenticate',\n onPick: actions.onReauth,\n })\n }\n return [...toggles, ...choices, ...acts]\n }, [actions])\n\n // ---- filtering --------------------------------------------------------\n // One shared query — predictable across tabs and lets the user pivot to\n // another tab without retyping. Each item carries its own corpus so the\n // filter cost stays linear in catalog size.\n const filteredGeneral = useMemo(\n () => generalItems.filter(it => matchesQuery(generalCorpus(it), query)),\n [generalItems, query],\n )\n const filteredSkills = useMemo(\n () => skillsCatalog.filter(s => matchesQuery(skillCorpus(s), query)),\n [skillsCatalog, query],\n )\n const filteredMcps = useMemo(\n () => mcpsCatalog.filter(m => matchesQuery(mcpCorpus(m), query)),\n [mcpsCatalog, query],\n )\n const filteredSize: Record<TabId, number> = {\n general: filteredGeneral.length,\n skills: filteredSkills.length,\n mcps: filteredMcps.length,\n }\n\n // Clamp on read so a query that narrowed the list doesn't leave the\n // cursor pointing past the end — same pattern the old modal used.\n const cursor = Math.min(\n cursorByTab[activeTab],\n Math.max(0, filteredSize[activeTab] - 1),\n )\n\n // Wrap-around on write: ↑ at the top jumps to the last row, ↓ at the\n // bottom comes back to the first. The clamp-on-read above still covers\n // the \"user typed in search and the active row vanished\" case.\n const moveCursor = useCallback(\n (delta: number) =>\n setCursorByTab((prev) => {\n const size = filteredSize[activeTab]\n if (size === 0)\n return prev\n const next = (((prev[activeTab] + delta) % size) + size) % size\n return { ...prev, [activeTab]: next }\n }),\n [activeTab, filteredSize],\n )\n\n const handleQueryChange = useCallback((next: string) => {\n setQuery(next)\n // The cursor probably points at a now-filtered-out row. Anchor at the\n // first match (the natural read position after a search) — only for the\n // active tab. The other tabs keep their cursor; when the user switches,\n // they'll either find their row still in the filtered slice or land at\n // a clamped position.\n setCursorByTab(prev => ({ ...prev, [activeTab]: 0 }))\n }, [activeTab])\n\n // Auto-scroll the focused row into view whenever the cursor moves,\n // the user switches tabs, or filtering changes which row sits at\n // index 0. Deferred a frame because OpenTUI's scrollbox computes its\n // `scrollSize` during the post-commit layout pass — reading row\n // geometry synchronously after a React commit would see stale values\n // and snap to the wrong offset. Same dance the question-wizard does.\n useEffect(() => {\n const sb = scrollboxRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollChildIntoView(anchorIdFor(cursor))\n })\n return () => cancelAnimationFrame(handle)\n }, [cursor, activeTab, query])\n\n // ---- focused-row context (MCP login / cancel) -------------------------\n const focusedMcp = filteredMcps[cursor]\n const focusedMcpStatus = focusedMcp\n ? getMcpAuthStatus(authState, focusedMcp.config.name)\n : undefined\n\n // While the focused MCP row is authorizing with a URL on screen, the\n // paste-back input on the right grabs focus from the search bar so\n // typing flows into it. Picker shortcuts (tab switch, arrows, etc.)\n // also hand off — except the global `escape` cancel, which still\n // reaches the parent so one keystroke cancels the in-flight login.\n const oauthPasteActive = activeTab === 'mcps'\n && focusedMcpStatus?.kind === 'authorizing'\n && !!focusedMcpStatus.url\n\n // ---- keyboard ---------------------------------------------------------\n useKeyboard((key) => {\n // Escape during in-flight OAuth: cancel + dismiss in one keystroke\n // (Modal's own esc-close fires as a sibling listener).\n if (key.name === 'escape' && oauthPasteActive && focusedMcp) {\n actions?.onCancelLoginMcp?.(focusedMcp.config.name)\n return\n }\n // While the paste input is grabbing keys, the picker's keyboard\n // affordances stay out of the way — the user is typing a URL.\n if (oauthPasteActive)\n return\n // Tab navigation — preventDefault so the focused input's cursor doesn't\n // jump alongside the tab switch. Plain ←/→ only (no modifiers) so the\n // user can still use shift-arrow for in-input selection if they need to.\n if (!key.ctrl && !key.meta && !key.shift && (key.name === 'left' || key.name === 'right')) {\n key.preventDefault()\n const idx = TAB_ORDER.indexOf(activeTab)\n const next = key.name === 'left'\n ? TAB_ORDER[(idx - 1 + TAB_ORDER.length) % TAB_ORDER.length]\n : TAB_ORDER[(idx + 1) % TAB_ORDER.length]\n setActiveTab(next)\n return\n }\n // In-list navigation — single-line input treats up/down as no-ops, so\n // no preventDefault needed. ctrl+P / ctrl+N kept as aliases for parity\n // with the prior modals + the other pickers in this app.\n if (key.name === 'up' || (key.ctrl && key.name === 'p')) {\n moveCursor(-1)\n return\n }\n if (key.name === 'down' || (key.ctrl && key.name === 'n')) {\n moveCursor(1)\n return\n }\n\n if (key.name === 'return') {\n if (activeTab === 'general') {\n const it = filteredGeneral[cursor]\n if (!it)\n return\n if (it.kind === 'toggle') {\n toggleBoolean(it.key)\n }\n else if (it.kind === 'choice') {\n const current = settings[it.key]\n const idx = it.options.findIndex(o => o.value === current)\n const next = it.options[(idx + 1) % it.options.length]\n if (next)\n setSetting(it.key, next.value as Settings[typeof it.key])\n }\n else {\n it.onPick()\n }\n return\n }\n if (activeTab === 'skills') {\n const s = filteredSkills[cursor]\n if (s)\n skillsToggle.toggle(s.name)\n return\n }\n if (activeTab === 'mcps') {\n const m = filteredMcps[cursor]\n if (m)\n mcpsToggle.toggle(m.config.name)\n }\n return\n }\n\n // MCP-specific actions. Plain `l` / `o` (used by the old modal) would\n // be swallowed by the search input here, so we promote them to ctrl-\n // variants which the input ignores entirely.\n if (activeTab === 'mcps' && focusedMcp && focusedMcpStatus) {\n if (key.ctrl && key.name === 'l' && canLogin(focusedMcp, focusedMcpStatus)) {\n void actions?.onLoginMcp?.(focusedMcp.config.name)\n return\n }\n if (key.ctrl && key.name === 'o' && canLogout(focusedMcpStatus)) {\n actions?.onLogoutMcp?.(focusedMcp.config.name)\n return\n }\n // Cancel an in-flight authorize on esc — Modal's own esc-close fires\n // alongside ours (sibling listeners, no propagation chain), so this\n // single keystroke both aborts the login AND dismisses the modal.\n // Same UX as the prior standalone MCP modal.\n if (key.name === 'escape' && focusedMcpStatus.kind === 'authorizing') {\n actions?.onCancelLoginMcp?.(focusedMcp.config.name)\n }\n }\n\n // Catalog refresh — Skills + MCPs tabs only. `ctrl+R` because plain\n // `r` would be swallowed by the search input. The host promise\n // resolves into the modal automatically through the catalog prop;\n // no in-flight state needed — discovery is sub-100ms in practice.\n if (key.ctrl && key.name === 'r') {\n if (activeTab === 'skills' && actions?.onRefreshSkills) {\n void actions.onRefreshSkills()\n return\n }\n if (activeTab === 'mcps' && actions?.onRefreshMcps) {\n void actions.onRefreshMcps()\n }\n }\n })\n\n // ---- render -----------------------------------------------------------\n const tabCounts: Record<TabId, { current: number, total: number }> = {\n general: { current: filteredGeneral.length, total: generalItems.length },\n skills: { current: filteredSkills.length, total: skillsCatalog.length },\n mcps: { current: filteredMcps.length, total: mcpsCatalog.length },\n }\n\n return (\n <Modal\n title=\"settings\"\n maxWidth={140}\n minWidth={76}\n maxHeight={44}\n horizontalMargin={2}\n verticalMargin={1}\n >\n {/*\n Fixed-height chrome (tab strip + search input + footer hints) sets\n `flexShrink: 0` so the scrollbox is the only thing that gives\n ground when the terminal is short — the layout never breaks past\n the modal's bottom border, the list just gets shorter and scrolls.\n */}\n <box style={{ flexShrink: 0 }}>\n <TabStrip active={activeTab} counts={tabCounts} />\n </box>\n\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n flexShrink: 0,\n }}\n >\n <input\n ref={inputRef}\n focused={!oauthPasteActive}\n placeholder={searchPlaceholder(activeTab)}\n onInput={handleQueryChange}\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n\n <scrollbox\n ref={scrollboxRef}\n // Never grabs focus — our top-level `useKeyboard` owns navigation.\n // The mouse wheel still scrolls the viewport for manual review.\n focusable={false}\n style={{ flexGrow: 1, flexShrink: 1, minHeight: 4 }}\n // No sticky-to-bottom — settings lists are anchored top-to-bottom\n // and the user expects ↑/↓ to drive position, not \"follow new\n // entries\" semantics.\n stickyScroll={false}\n >\n {activeTab === 'general' && (\n <GeneralList\n items={filteredGeneral}\n cursor={cursor}\n highlightBg={SURFACE.selection}\n query={query}\n />\n )}\n {activeTab === 'skills' && (\n <SkillsList\n items={filteredSkills}\n enabledSet={skillsToggle.enabledSet}\n totalCount={skillsCatalog.length}\n cursor={cursor}\n highlightBg={SURFACE.selection}\n query={query}\n />\n )}\n {activeTab === 'mcps' && (\n <McpsList\n items={filteredMcps}\n enabledSet={mcpsToggle.enabledSet}\n totalCount={mcpsCatalog.length}\n cursor={cursor}\n highlightBg={SURFACE.selection}\n query={query}\n errors={mcpsErrors}\n authState={authState}\n />\n )}\n </scrollbox>\n\n {activeTab === 'mcps' && focusedMcp && focusedMcpStatus && (\n <box style={{ flexShrink: 0 }}>\n {renderMcpDetailPanel(focusedMcp, focusedMcpStatus, COLOR)}\n </box>\n )}\n\n <box style={{ flexShrink: 0 }}>\n <Hints\n activeTab={activeTab}\n focusedMcp={focusedMcp}\n focusedMcpStatus={focusedMcpStatus}\n canRefreshSkills={!!actions?.onRefreshSkills}\n canRefreshMcps={!!actions?.onRefreshMcps}\n />\n </box>\n </Modal>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Tab strip — one row, brand-highlighted active tab on a selection-color\n// background. Inactive tabs use the mute palette so the active one wins\n// the user's attention without any glyphs competing.\n// ---------------------------------------------------------------------------\n\nfunction TabStrip({\n active,\n counts,\n}: {\n active: TabId\n counts: Record<TabId, { current: number, total: number }>\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n return (\n <box style={{ flexDirection: 'row', height: 1 }}>\n {TAB_ORDER.map((id, i) => {\n const isActive = id === active\n const label = TAB_LABELS[id]\n const { current, total } = counts[id]\n // Render the badge only when filtering shrank the list (so the\n // user sees how many entries the search left); otherwise just\n // show the total — keeps the inactive tabs from looking noisy.\n const badge = current !== total ? `${current}/${total}` : `${total}`\n return (\n <box\n key={id}\n style={{\n paddingLeft: 1,\n paddingRight: 1,\n marginRight: i === TAB_ORDER.length - 1 ? 0 : 1,\n backgroundColor: isActive ? SURFACE.selection : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isActive ? COLOR.brand : COLOR.dim}>{label}</span>\n <span fg={isActive ? COLOR.brand : COLOR.mute}>{` ${badge}`}</span>\n </text>\n </box>\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// General tab — toggles + choices + actions (auth, keybindings).\n// Each row renders inside an `<box id=...>` so the modal's scrollbox can\n// `scrollChildIntoView` on cursor moves.\n// ---------------------------------------------------------------------------\n\nfunction GeneralList({\n items,\n cursor,\n highlightBg,\n query,\n}: {\n items: readonly GeneralItem[]\n cursor: number\n highlightBg: string\n query: string\n}) {\n const { settings } = useSettings()\n if (items.length === 0)\n return <EmptyRow label={query ? `no settings match \"${query}\"` : 'no settings'} />\n return (\n <box style={{ flexDirection: 'column' }}>\n {items.map((item, i) => {\n const focused = i === cursor\n const bg = focused ? highlightBg : undefined\n const id = anchorIdFor(i)\n if (item.kind === 'toggle') {\n return (\n <ToggleRow\n key={item.key}\n id={id}\n label={item.label}\n description={item.description}\n enabled={settings[item.key]}\n focused={focused}\n bg={bg}\n />\n )\n }\n if (item.kind === 'choice') {\n const current = settings[item.key]\n const opt = item.options.find(o => o.value === current)\n return (\n <ChoiceRow\n key={item.key}\n id={id}\n label={item.label}\n description={item.description}\n value={opt?.label ?? String(current)}\n cyclable={item.options.length > 1}\n focused={focused}\n bg={bg}\n />\n )\n }\n return (\n <ActionRow\n key={item.id}\n id={id}\n label={item.label}\n description={item.description}\n focused={focused}\n bg={bg}\n />\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Skills tab — checkbox list with name + description.\n// ---------------------------------------------------------------------------\n\nfunction SkillsList({\n items,\n enabledSet,\n totalCount,\n cursor,\n highlightBg,\n query,\n}: {\n items: readonly SkillConfig[]\n enabledSet: ReadonlySet<string>\n totalCount: number\n cursor: number\n highlightBg: string\n query: string\n}) {\n const COLOR = useColors()\n if (totalCount === 0) {\n return (\n <box style={{ flexDirection: 'column' }}>\n <text fg={COLOR.dim}>No skills discovered.</text>\n <text fg={COLOR.mute}>\n Drop a\n <span fg={COLOR.model}>{' SKILL.md '}</span>\n into\n <span fg={COLOR.model}>{' .zidane/skills/<name>/ '}</span>\n or\n <span fg={COLOR.model}>{' .agents/skills/<name>/ '}</span>\n (project or\n <span fg={COLOR.model}>{' ~/'}</span>\n ).\n </text>\n </box>\n )\n }\n if (items.length === 0)\n return <EmptyRow label={`no skills match \"${query}\"`} />\n return (\n <box style={{ flexDirection: 'column' }}>\n {items.map((entry, i) => {\n const focused = i === cursor\n const bg = focused ? highlightBg : undefined\n const enabled = enabledSet.has(entry.name)\n return (\n <box\n key={entry.name}\n id={anchorIdFor(i)}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{entry.name}</span>\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>\n {`${COL_TITLE}${entry.description ?? ''}`}\n </text>\n </box>\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// MCPs tab — checkbox list with name + auth badge on line 1, transport\n// detail on line 2. Errors render as a preamble; per-row status badges\n// come from `useMcpAuthState`.\n// ---------------------------------------------------------------------------\n\nfunction McpsList({\n items,\n enabledSet,\n totalCount,\n cursor,\n highlightBg,\n query,\n errors,\n authState,\n}: {\n items: readonly DiscoveredMcp[]\n enabledSet: ReadonlySet<string>\n totalCount: number\n cursor: number\n highlightBg: string\n query: string\n errors: readonly DiscoveryError[] | undefined\n authState: ReturnType<typeof useMcpAuthState>\n}) {\n const COLOR = useColors()\n const home = homedir()\n if (totalCount === 0) {\n return (\n <box style={{ flexDirection: 'column' }}>\n {renderMcpErrors(errors, home, COLOR.warn)}\n <text fg={COLOR.dim}>No MCP servers discovered.</text>\n <text fg={COLOR.mute}>\n Drop a\n <span fg={COLOR.model}>{' mcps.json '}</span>\n into\n <span fg={COLOR.model}>{' .zidane/ '}</span>\n or\n <span fg={COLOR.model}>{' .agents/ '}</span>\n (project or\n <span fg={COLOR.model}>{' ~/'}</span>\n ).\n </text>\n </box>\n )\n }\n if (items.length === 0) {\n return (\n <box style={{ flexDirection: 'column' }}>\n {renderMcpErrors(errors, home, COLOR.warn)}\n <EmptyRow label={`no servers match \"${query}\"`} />\n </box>\n )\n }\n return (\n <box style={{ flexDirection: 'column' }}>\n {renderMcpErrors(errors, home, COLOR.warn)}\n {items.map((entry, i) => {\n const focused = i === cursor\n const bg = focused ? highlightBg : undefined\n const name = entry.config.name\n const enabled = enabledSet.has(name)\n const status = getMcpAuthStatus(authState, name)\n return (\n <box\n key={name}\n id={anchorIdFor(i)}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{name}</span>\n {renderInlineMcpBadge(status, COLOR)}\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>\n {`${COL_TITLE}${mcpDetail(entry)}`}\n </text>\n </box>\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Row primitives (general tab)\n// ---------------------------------------------------------------------------\n\n// All row variants render on TWO lines: title (+ selection / value /\n// trailing affordance) on line 1, description on line 2 indented to\n// align with the title column. Keeping the description on its own\n// row stops the rows from competing for horizontal space on narrow\n// terminals (where the prior single-line layout truncated descriptions\n// at the visible edge) and gives every entry the same vertical rhythm.\n\n// `flexShrink: 0` on every row keeps the 2-line geometry intact inside\n// the scrollbox — without it Yoga would compress rows to a single line\n// when the modal's height is tight, and the description would vanish.\n\nfunction ToggleRow({\n id,\n label,\n description,\n enabled,\n focused,\n bg,\n}: {\n id: string\n label: string\n description: string\n enabled: boolean\n focused: boolean\n bg: string | undefined\n}) {\n const COLOR = useColors()\n return (\n <box\n id={id}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{label}</span>\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>{`${COL_TITLE}${description}`}</text>\n </box>\n )\n}\n\n// Choice + action rows pad the checkbox slot with whitespace (instead of\n// `[ ]`) so the title text still lands at column 6 — same column the\n// toggle / skill / MCP rows put their labels in. Without the spacer the\n// \"Auto-compact threshold\", \"Theme\", \"Keybindings\", \"Authentication\"\n// rows would sit at column 2 and the list would read as two staggered\n// columns instead of one.\n\nfunction ChoiceRow({\n id,\n label,\n description,\n value,\n cyclable,\n focused,\n bg,\n}: {\n id: string\n label: string\n description: string\n value: string\n cyclable: boolean\n focused: boolean\n bg: string | undefined\n}) {\n const COLOR = useColors()\n return (\n <box\n id={id}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={COLOR.mute}>{SPACER_CHECKBOX_WIDTH}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{label}</span>\n <span fg={COLOR.mute}>: </span>\n <span fg={focused ? COLOR.brand : COLOR.accent}>{value}</span>\n {focused && cyclable && <span fg={COLOR.brand}>{' ↻'}</span>}\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>{`${COL_TITLE}${description}`}</text>\n </box>\n )\n}\n\nfunction ActionRow({\n id,\n label,\n description,\n focused,\n bg,\n}: {\n id: string\n label: string\n description: string\n focused: boolean\n bg: string | undefined\n}) {\n const COLOR = useColors()\n return (\n <box\n id={id}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={COLOR.mute}>{SPACER_CHECKBOX_WIDTH}</span>\n <span fg={focused ? COLOR.brand : COLOR.accent}>{label}</span>\n {focused && <span fg={COLOR.brand}>{' ›'}</span>}\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>{`${COL_TITLE}${description}`}</text>\n </box>\n )\n}\n\nfunction EmptyRow({ label }: { label: string }) {\n const COLOR = useColors()\n return <text fg={COLOR.mute}>{` ${label}`}</text>\n}\n\n// ---------------------------------------------------------------------------\n// MCP detail panel — only renders when there's verbose info that doesn't\n// fit on the row (authorizing URL, error message). Same content the prior\n// standalone McpsSettingsModal showed below its list.\n// ---------------------------------------------------------------------------\n\nfunction renderMcpDetailPanel(\n entry: DiscoveredMcp,\n status: McpAuthStatus,\n COLOR: ReturnType<typeof useColors>,\n): ReactNode {\n if (status.kind === 'authorizing') {\n if (!status.url) {\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.brand}>{`Authorizing ${entry.config.name}`}</text>\n <text fg={COLOR.dim}>Starting login flow…</text>\n </box>\n )\n }\n // `McpAuthorizingPanel` owns the paste-back input + handler.\n // SettingsModal maxWidth=140, padding=2 each side, border=1 each side\n // → content area ≤ 134 cols.\n return (\n <McpAuthorizingPanel\n serverName={entry.config.name}\n authUrl={status.url}\n maxLineWidth={134}\n inputFocused\n />\n )\n }\n if (status.kind === 'error') {\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.error}>{`Login failed: ${entry.config.name}`}</text>\n <text fg={COLOR.dim}>{status.error}</text>\n <text fg={COLOR.mute}>\n Press\n {' '}\n <span fg={COLOR.warn}>ctrl+L</span>\n {' '}\n to retry.\n </text>\n </box>\n )\n }\n return null\n}\n\nfunction renderInlineMcpBadge(status: McpAuthStatus, COLOR: ReturnType<typeof useColors>): ReactNode {\n switch (status.kind) {\n case 'idle':\n return null\n case 'authed':\n return (\n <span fg={COLOR.accent}>\n {' '}\n ✓ authed\n </span>\n )\n case 'needs-auth':\n return (\n <span fg={COLOR.warn}>\n {' '}\n ! needs login\n </span>\n )\n case 'authorizing':\n return (\n <span fg={COLOR.warn}>\n {' '}\n … authorizing\n </span>\n )\n case 'error':\n return (\n <span fg={COLOR.error}>\n {' '}\n ✗ login failed\n </span>\n )\n }\n}\n\nfunction renderMcpErrors(\n errors: readonly DiscoveryError[] | undefined,\n home: string,\n warnColor: string,\n): ReactNode {\n if (!errors || errors.length === 0)\n return null\n return (\n <box style={{ flexDirection: 'column' }}>\n {errors.map(err => (\n <text key={err.path} fg={warnColor}>\n {`! ${displayPath(err.path, home)}: ${err.message}`}\n </text>\n ))}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Footer hints — dynamic per tab + MCP focus state.\n// ---------------------------------------------------------------------------\n\nfunction Hints({\n activeTab,\n focusedMcp,\n focusedMcpStatus,\n canRefreshSkills,\n canRefreshMcps,\n}: {\n activeTab: TabId\n focusedMcp: DiscoveredMcp | undefined\n focusedMcpStatus: McpAuthStatus | undefined\n canRefreshSkills: boolean\n canRefreshMcps: boolean\n}) {\n const COLOR = useColors()\n const showLogin = activeTab === 'mcps'\n && !!focusedMcp\n && !!focusedMcpStatus\n && canLogin(focusedMcp, focusedMcpStatus)\n const showLogout = activeTab === 'mcps'\n && !!focusedMcpStatus\n && canLogout(focusedMcpStatus)\n const showCancel = activeTab === 'mcps' && focusedMcpStatus?.kind === 'authorizing'\n const showRefresh = (activeTab === 'skills' && canRefreshSkills)\n || (activeTab === 'mcps' && canRefreshMcps)\n return (\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>←→</span>\n {' tabs · '}\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {activeTab === 'general' ? ' toggle/cycle/select' : ' toggle'}\n {showLogin && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>ctrl+L</span>\n {' login'}\n </span>\n )}\n {showLogout && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>ctrl+O</span>\n {' logout'}\n </span>\n )}\n {showRefresh && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>ctrl+R</span>\n {' refresh'}\n </span>\n )}\n {showCancel\n ? (\n <span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </span>\n )\n : (\n <span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </span>\n )}\n </text>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Helpers — pure\n// ---------------------------------------------------------------------------\n\nfunction matchesQuery(corpus: string, query: string): boolean {\n const trimmed = query.trim().toLowerCase()\n if (!trimmed)\n return true\n return trimmed.split(/\\s+/).every(term => corpus.includes(term))\n}\n\nfunction generalCorpus(item: GeneralItem): string {\n const parts: string[] = [item.label, item.description]\n if (item.kind === 'choice')\n parts.push(...item.options.map(o => o.label))\n return parts.join(' ').toLowerCase()\n}\n\nfunction skillCorpus(s: SkillConfig): string {\n return `${s.name} ${s.description ?? ''}`.toLowerCase()\n}\n\nfunction mcpCorpus(m: DiscoveredMcp): string {\n return [\n m.config.name,\n m.config.transport,\n m.config.command ?? '',\n m.config.url ?? '',\n (m.config.args ?? []).join(' '),\n ].join(' ').toLowerCase()\n}\n\nfunction mcpDetail(entry: DiscoveredMcp): string {\n const transport = entry.config.transport\n const detail = transport === 'stdio'\n ? entry.config.command ?? ''\n : entry.config.url ?? ''\n return `${transport} · ${detail}`\n}\n\nfunction canLogin(entry: DiscoveredMcp, status: McpAuthStatus): boolean {\n // OAuth only applies to HTTP-style transports; stdio servers can't speak it.\n // `idle` is excluded too — we'd be guessing before bootstrap has had a\n // chance to flip the row to `needs-auth` itself.\n if (entry.config.transport === 'stdio')\n return false\n return status.kind === 'needs-auth' || status.kind === 'error'\n}\n\nfunction canLogout(status: McpAuthStatus): boolean {\n return status.kind === 'authed' || status.kind === 'error' || status.kind === 'authorizing'\n}\n\nfunction searchPlaceholder(tab: TabId): string {\n switch (tab) {\n case 'general':\n return 'search settings — name, description…'\n case 'skills':\n return 'search skills — name, description…'\n case 'mcps':\n return 'search MCP servers — name, transport, command/URL…'\n }\n}\n\nfunction displayPath(path: string, home: string): string {\n if (home && path.startsWith(`${home}/`))\n return `~/${path.slice(home.length + 1)}`\n return path\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ScrollBoxRenderable } from '@opentui/core'\nimport type { Agent } from '../agent'\nimport type { TodoItem, TodoStatus } from '../chat/todos'\nimport type { Session } from '../session'\nimport { useTerminalDimensions } from '@opentui/react'\nimport { useEffect, useRef, useState } from 'react'\nimport { useColors } from '../chat/theme-context'\nimport { TODO_STATUS_GLYPHS, TODOWRITE_TOOL, useActiveTodos } from '../chat/todos'\nimport { Modal } from './modal'\n\n// ---------------------------------------------------------------------------\n// TodosModal — read-only viewer for the active run's `todowrite` list.\n//\n// Layout:\n// ┌─ todos ─────────────────── 1 in progress · 3 completed ─┐\n// │ ☑ first completed task │\n// │ ☑ second completed task │\n// │ ☑ third completed task │\n// │ ◐ currently working on this one │\n// │ ☐ next pending task │\n// │ ☐ another pending task │\n// └──────────────────────────────── 6 items · esc close ────┘\n//\n// The body is purely the list — the title + right-title + bottom-title\n// carry all the meta (status, totals, dismissal hint). When the live\n// slot is empty but an `archive` snapshot exists (auto-clear after a\n// run of all-completed items), the body renders the archived list with\n// no extra banner — the right-title's `N completed` already signals\n// what the user is looking at.\n//\n// Read-only by design — no toggles, no reorder, no delete. The model\n// drives the list via `todowrite`; this surface is purely \"what does\n// the agent think it's doing right now?\".\n// ---------------------------------------------------------------------------\n\ninterface TodosModalProps {\n /** Active session — null on the auth / sessions screens. */\n session: Session | null\n /**\n * Active agent. Used to subscribe to `tool:after` for live updates —\n * `<ModalRoot>` sits ABOVE `<AppShell>` in the React tree, so it\n * doesn't re-render on the streaming-buffer cascade; without an\n * explicit subscription the modal would freeze at the snapshot taken\n * when `ctrl+t` was pressed. Optional so unit tests / non-live hosts\n * can render the modal against a static session.\n */\n agent?: Agent | null\n}\n\n/** Floor on the modal height — keeps the header + at least a few rows visible on tiny terminals. */\nconst MIN_MODAL_HEIGHT = 12\n/** Ceiling on the modal height — prevents a giant list from blanket-filling a tall window. */\nconst MAX_MODAL_HEIGHT = 36\n\n/**\n * Per-status palette for a row — the glyph and the content text pick\n * different tones so a quick scan reads the status from the glyph color\n * while the row content stays in a calm reading tone:\n *\n * pending glyph `mute` · text `dim` — queued / not yet started\n * in_progress glyph `warn` · text `brand` — currently working (loudest)\n * completed glyph `accent` · text `dim` — done (success glyph, calm text)\n * cancelled glyph `error` · text `mute` — explicitly dropped\n *\n * All tokens come from the active theme — a runtime theme switch\n * repaints without touching this component.\n */\nfunction statusColors(\n status: TodoStatus,\n COLOR: ReturnType<typeof useColors>,\n): { glyph: string, text: string } {\n switch (status) {\n case 'in_progress':\n return { glyph: COLOR.warn, text: COLOR.brand }\n case 'completed':\n return { glyph: COLOR.accent, text: COLOR.dim }\n case 'cancelled':\n return { glyph: COLOR.error, text: COLOR.mute }\n case 'pending':\n default:\n return { glyph: COLOR.mute, text: COLOR.dim }\n }\n}\n\nfunction TodoRow({ item, rowId }: { item: TodoItem, rowId?: string }) {\n const COLOR = useColors()\n const colors = statusColors(item.status, COLOR)\n return (\n <box id={rowId} style={{ flexShrink: 0, flexDirection: 'row' }}>\n <text wrapMode=\"word\">\n <span fg={colors.glyph}>{TODO_STATUS_GLYPHS[item.status]}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={colors.text}>{item.content}</span>\n </text>\n </box>\n )\n}\n\nexport function TodosModal({ session, agent }: TodosModalProps) {\n const COLOR = useColors()\n const { height: termHeight } = useTerminalDimensions()\n // Force a re-render on every `todowrite` so the modal reflects the\n // model's latest checkpoint while it's open. `selectActiveTodos`\n // reads `session.metadata.todos` directly — outside React state —\n // so without this tick the modal would stay frozen at its open-time\n // snapshot. Listening for `todowrite` specifically (not every tool)\n // keeps the cost trivial.\n const [, setTick] = useState(0)\n useEffect(() => {\n if (!agent)\n return\n const unregister = agent.hooks.hook('tool:after', (ctx) => {\n if (ctx.name === TODOWRITE_TOOL)\n setTick(t => t + 1)\n })\n return unregister\n }, [agent])\n const state = useActiveTodos(session)\n const scrollRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Auto-scroll to the first in-progress row when the modal opens so a\n // long list doesn't bury the live item below the visible window.\n // Deferred a frame for the same reason `MultiEditApprovalModal` does\n // — `scrollChildIntoView` needs the scrollbox's measured size, which\n // is only available post-commit.\n const inProgressId = state.inProgress?.id ?? null\n useEffect(() => {\n if (!inProgressId)\n return\n const sb = scrollRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollChildIntoView(`todo-row-${inProgressId}`)\n })\n return () => cancelAnimationFrame(handle)\n }, [inProgressId])\n\n // Cap the modal at roughly two-thirds of the terminal — enough for a\n // long list to scroll without overpowering the chat behind it. The\n // floor keeps the header + at least a few rows visible on tiny\n // terminals; the cap prevents a giant list from blanket-filling a\n // tall window.\n const idealHeight = Math.floor((termHeight - 4) * 0.66)\n const maxHeight = Math.max(MIN_MODAL_HEIGHT, Math.min(MAX_MODAL_HEIGHT, idealHeight))\n\n // What the modal actually renders: the live list when present, or\n // the archived snapshot when the live slot is empty (after the\n // auto-clear that fires when every item went `completed`). The\n // right-title badge counts whatever list is on screen, so a viewer\n // of the archive still sees \"N completed\" — no separate banner.\n const display = state.todos.length > 0 ? state.todos : state.archive\n const total = display.length\n\n const bottomTitle = total > 0\n ? `${total} item${total === 1 ? '' : 's'} · esc close`\n : 'esc close'\n\n // Right-aligned top-border overlay — at-a-glance status next to the\n // title slot. Painted via the new `Modal.rightTitle` sibling-overlay\n // hook (sites OpenTUI's scissor rect would otherwise clip).\n const rightTitle = display.length > 0 ? <CountsBadge items={display} /> : null\n\n return (\n <Modal title=\"todos\" bottomTitle={bottomTitle} rightTitle={rightTitle} maxWidth={100} maxHeight={maxHeight}>\n {total === 0\n ? (\n <text>\n <span fg={COLOR.mute}>{'(no todos yet — the agent hasn\\'t called '}</span>\n <span fg={COLOR.warn}>todowrite</span>\n <span fg={COLOR.mute}>)</span>\n </text>\n )\n : (\n // Scrollable list — `flexGrow: 1` claims the remaining\n // modal body height; `flexShrink: 1` plus the `Modal`\n // panel's own height cap keeps the action footer in view\n // on tall lists. Same containment contract as the\n // file-edit modal (see `test/tui/modal-overflow-\n // containment.test.tsx`).\n <box\n style={{\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n }}\n >\n <scrollbox\n ref={scrollRef}\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n {display.map(item => (\n <TodoRow key={item.id} item={item} rowId={`todo-row-${item.id}`} />\n ))}\n </scrollbox>\n </box>\n )}\n </Modal>\n )\n}\n\n/**\n * Right-aligned top-border badge — \"{in-progress} in progress ·\n * {completed} completed\". Empty buckets are dropped so a homogeneous\n * list reads cleanly. Status order is fixed (in-progress first, then\n * completed) regardless of which buckets are populated — the badge's\n * shape stays predictable so the eye lands on the live count instantly.\n */\nfunction CountsBadge({ items }: { items: readonly TodoItem[] }) {\n const COLOR = useColors()\n const counts: Record<TodoStatus, number> = { pending: 0, in_progress: 0, completed: 0, cancelled: 0 }\n for (const item of items)\n counts[item.status] += 1\n const parts: Array<{ count: number, label: string, color: string }> = []\n if (counts.in_progress)\n parts.push({ count: counts.in_progress, label: 'in progress', color: COLOR.warn })\n if (counts.completed)\n parts.push({ count: counts.completed, label: 'completed', color: COLOR.accent })\n if (counts.pending)\n parts.push({ count: counts.pending, label: 'pending', color: COLOR.dim })\n if (counts.cancelled)\n parts.push({ count: counts.cancelled, label: 'cancelled', color: COLOR.error })\n if (parts.length === 0)\n return null\n return (\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' '}</span>\n {parts.map((p, i) => (\n <span key={p.label}>\n {i > 0 && <span fg={COLOR.mute}>{' · '}</span>}\n <span fg={p.color}>{String(p.count)}</span>\n <span fg={COLOR.mute}>{` ${p.label}`}</span>\n </span>\n ))}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { TextareaRenderable } from '@opentui/core'\nimport type { KeyBindings } from '../chat/keybindings'\nimport type { SessionContentBlock, SessionTurn } from '../types'\nimport { defaultTextareaKeyBindings } from '@opentui/core'\nimport { useKeyboard } from '@opentui/react'\nimport { useRef, useState } from 'react'\nimport { ageString, shortId } from '../chat/format'\nimport { matchesBinding } from '../chat/keybindings'\nimport { useColors } from '../chat/theme-context'\nimport { turnAsText } from '../chat/turn-operations'\nimport { writeToClipboard } from './clipboard'\nimport { Modal, useModal } from './modal'\n\n// ---------------------------------------------------------------------------\n// TurnDetailsModal — actions for the currently-selected turn.\n//\n// Geometry: pinned `maxHeight` with a scrollable preview pane so a long\n// assistant reply doesn't overflow the viewport and bury the action hint\n// row. Header sits in the modal's top border, before/after counter on the\n// bottom border (right-aligned), preview in the middle, action row at the\n// bottom.\n//\n// Keyboard:\n// - `f` — fork session from this turn (truncate, branch off)\n// - `d` — delete this turn (with orphan tool-pair cleanup)\n// - `c` — copy turn content to clipboard via OSC 52\n// - `e` — edit the turn's text content\n// - esc — close\n//\n// `f`/`d` are destructive of conversation flow; both confirm via a brief\n// transition state showing what's about to happen, then commit on a\n// second press. Copy is non-destructive and runs immediately.\n// ---------------------------------------------------------------------------\n\n/** Max chars surfaced in the scrollable preview pane. Long enough that almost everything fits without truncation. */\nconst PREVIEW_CHAR_MAX = 8000\n\n/**\n * Visible rows allocated to the modal. Smaller terminals shrink down via\n * the Modal's own clamp; this is the cap on wide terminals so the modal\n * keeps a comfortable shape rather than stretching to the full height.\n */\nconst MAX_MODAL_HEIGHT = 28\n\nconst EDIT_TEXTAREA_BINDINGS = (() => {\n const base = defaultTextareaKeyBindings.filter(\n b => b.name !== 'return' && !(b.name === 'a' && b.ctrl && !b.shift && !b.meta),\n )\n return [\n ...base,\n { name: 'a' as const, ctrl: true, action: 'select-all' as const },\n { name: 'return' as const, action: 'submit' as const },\n { name: 'return' as const, shift: true, action: 'newline' as const },\n ]\n})()\n\nexport interface TurnDetailsModalActions {\n /** Fork the session at this turn — new session keeps history up to here. */\n onFork: (turnId: string) => void\n /** Delete this turn (with cleanup of orphan tool blocks in neighbors). */\n onDelete: (turnId: string) => void\n /** Edit the text content of this turn. */\n onEdit: (turnId: string, newText: string) => void\n}\n\n/**\n * Extract the editable text from a turn — joins all `text` blocks.\n * Non-text blocks (tool_call, tool_result, thinking, etc.) are structural\n * and not included in the editable surface.\n */\nfunction editableText(turn: SessionTurn): string {\n return turn.content\n .filter((b): b is Extract<SessionContentBlock, { type: 'text' }> => b.type === 'text')\n .map(b => b.text)\n .join('\\n\\n')\n}\n\nexport function TurnDetailsModal({\n turn,\n index,\n total,\n actions,\n keybindings,\n}: {\n turn: SessionTurn\n /** 1-based position of this turn in the session, for the header. */\n index: number\n /** Total turn count, for the `n / N` header. */\n total: number\n actions: TurnDetailsModalActions\n /** Effective keybindings — the modal-internal letter actions resolve through this. */\n keybindings: KeyBindings\n}) {\n const COLOR = useColors()\n const modal = useModal()\n\n const fullText = turnAsText(turn)\n const preview = fullText.length > PREVIEW_CHAR_MAX\n ? `${fullText.slice(0, PREVIEW_CHAR_MAX)}\\n\\n…(${fullText.length - PREVIEW_CHAR_MAX} more chars)`\n : fullText\n const summary = blockSummary(turn)\n const before = index - 1\n const after = total - index\n const bottomTitle = `${before} before · ${after} after`\n\n // Pending confirmation state: `null` = first press, action committed on\n // second press. Esc clears the pending state without committing.\n const [pending, setPending] = useState<'fork' | 'delete' | null>(null)\n // Transient copy feedback. Flips back automatically — the modal itself\n // doesn't tick; the success message stays until the user presses\n // another key or closes the modal.\n const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'failed'>('idle')\n // Edit mode: when true, the preview pane is replaced by a textarea.\n const [editing, setEditing] = useState(false)\n const textareaRef = useRef<TextareaRenderable | null>(null)\n\n const hasEditableText = turn.content.some(b => b.type === 'text')\n\n const commitFork = () => {\n modal.close()\n actions.onFork(turn.id)\n }\n const commitDelete = () => {\n modal.close()\n actions.onDelete(turn.id)\n }\n const handleCopy = () => {\n if (!fullText) {\n setCopyStatus('failed')\n return\n }\n setCopyStatus(writeToClipboard(fullText) ? 'copied' : 'failed')\n }\n const commitEdit = () => {\n const newText = textareaRef.current?.plainText ?? ''\n modal.close()\n actions.onEdit(turn.id, newText)\n }\n\n useKeyboard((key) => {\n // In edit mode, only handle esc (cancel) — everything else goes to\n // the textarea. Submit is handled via the textarea's onSubmit.\n if (editing) {\n if (key.name === 'escape') {\n setEditing(false)\n }\n return\n }\n\n // `<Modal disableEscape={pending !== null}>` suppresses Modal's default\n // Esc handler whenever a confirmation is pending, so the handler below\n // is the sole responder for that keystroke. When no confirmation is\n // pending, Modal handles Esc normally (close).\n if (key.name === 'escape' && pending) {\n setPending(null)\n return\n }\n if (matchesBinding(key, keybindings.turnFork)) {\n setCopyStatus('idle')\n if (pending === 'fork')\n commitFork()\n else\n setPending('fork')\n return\n }\n if (matchesBinding(key, keybindings.turnDelete)) {\n setCopyStatus('idle')\n if (pending === 'delete')\n commitDelete()\n else\n setPending('delete')\n return\n }\n if (matchesBinding(key, keybindings.turnCopy)) {\n setPending(null)\n handleCopy()\n return\n }\n if (matchesBinding(key, keybindings.turnEdit)) {\n if (!hasEditableText)\n return\n setPending(null)\n setCopyStatus('idle')\n setEditing(true)\n return\n }\n // Any other key cancels a pending confirmation so the user isn't\n // surprised by a delayed fork/delete after they navigated away.\n if (pending)\n setPending(null)\n })\n\n return (\n <Modal\n title={editing ? `edit turn ${index} / ${total} · ${turn.role}` : `turn ${index} / ${total} · ${turn.role}`}\n bottomTitle={editing ? undefined : bottomTitle}\n maxHeight={MAX_MODAL_HEIGHT}\n disableEscape={editing || pending !== null}\n >\n {!editing && (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>id </span>\n <span fg={COLOR.model}>{shortId(turn.id)}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>created </span>\n <span fg={COLOR.dim}>{ageString(turn.createdAt)}</span>\n {turn.runId && (\n <>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>run </span>\n <span fg={COLOR.dim}>{turn.runId}</span>\n </>\n )}\n </text>\n )}\n\n {!editing && (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>blocks </span>\n <span fg={COLOR.dim}>{summary}</span>\n </text>\n )}\n\n {editing\n ? (\n <box\n title=\" edit \"\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 1,\n minHeight: 5,\n }}\n >\n <textarea\n ref={textareaRef}\n focused\n keyBindings={EDIT_TEXTAREA_BINDINGS}\n initialValue={editableText(turn)}\n placeholder=\"enter text…\"\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={commitEdit}\n />\n </box>\n )\n : (\n <box\n title=\" preview \"\n style={{\n border: true,\n borderColor: COLOR.mute,\n paddingLeft: 1,\n paddingRight: 1,\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 1,\n minHeight: 5,\n }}\n >\n {preview\n ? (\n <scrollbox\n focusable={false}\n style={{ flexGrow: 1 }}\n stickyScroll={false}\n >\n <text fg={COLOR.dim}>{preview}</text>\n </scrollbox>\n )\n : <text fg={COLOR.mute}>— no text content —</text>}\n </box>\n )}\n\n {editing\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↵</span>\n {' save · '}\n <span fg={COLOR.warn}>shift+↵</span>\n {' newline · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </text>\n )\n : (\n <ActionRow\n pending={pending}\n copyStatus={copyStatus}\n canCopy={fullText.length > 0}\n canEdit={hasEditableText}\n forkKey={keybindings.turnFork}\n deleteKey={keybindings.turnDelete}\n copyKey={keybindings.turnCopy}\n editKey={keybindings.turnEdit}\n />\n )}\n </Modal>\n )\n}\n\n/**\n * Footer row showing the action shortcuts. When a destructive action\n * (fork / delete) is pending confirmation, the row swaps to a clear\n * \"press <key> again to confirm\" prompt so the user can't trigger it\n * by accident. The copy result rides the same row when present — same\n * geometry, no layout shift.\n */\nfunction ActionRow({\n pending,\n copyStatus,\n canCopy,\n canEdit,\n forkKey,\n deleteKey,\n copyKey,\n editKey,\n}: {\n pending: 'fork' | 'delete' | null\n copyStatus: 'idle' | 'copied' | 'failed'\n canCopy: boolean\n canEdit: boolean\n forkKey: string\n deleteKey: string\n copyKey: string\n editKey: string\n}) {\n const COLOR = useColors()\n\n if (pending === 'fork') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>fork from here?</span>\n {' press '}\n <span fg={COLOR.warn}>{forkKey}</span>\n {' again to confirm · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </text>\n )\n }\n\n if (pending === 'delete') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>delete this turn?</span>\n {' press '}\n <span fg={COLOR.error}>{deleteKey}</span>\n {' again to confirm · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </text>\n )\n }\n\n // Copy feedback overrides the default hint row while it's relevant —\n // resets to idle on any other action keypress (see modal keyboard).\n if (copyStatus === 'copied') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>✓ copied</span>\n {' · '}\n <span fg={COLOR.warn}>{forkKey}</span>\n {' fork · '}\n <span fg={COLOR.warn}>{deleteKey}</span>\n {' delete · '}\n <span fg={canEdit ? COLOR.warn : COLOR.mute}>{editKey}</span>\n {canEdit ? ' edit · ' : ' (no text) · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (copyStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>copy failed (terminal may not support OSC 52)</span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>{forkKey}</span>\n {' fork · '}\n <span fg={COLOR.warn}>{deleteKey}</span>\n {' delete · '}\n <span fg={canCopy ? COLOR.warn : COLOR.mute}>{copyKey}</span>\n {canCopy ? ' copy · ' : ' (nothing to copy) · '}\n <span fg={canEdit ? COLOR.warn : COLOR.mute}>{editKey}</span>\n {canEdit ? ' edit · ' : ' (no text) · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n}\n\n/**\n * Human-readable per-kind block tally — e.g. `1 text · 2 tool_call`. Skips\n * zero-count kinds so the line stays scannable; uses canonical block-type\n * names so they line up with what the LLM and persistence layer see.\n */\nfunction blockSummary(turn: SessionTurn): string {\n const counts: Record<string, number> = {}\n for (const block of turn.content)\n counts[block.type] = (counts[block.type] ?? 0) + 1\n const parts: string[] = []\n for (const [type, n] of Object.entries(counts))\n parts.push(`${n} ${type}`)\n return parts.length === 0 ? '(empty)' : parts.join(' · ')\n}\n","/** @jsxImportSource @opentui/react */\nimport type { Agent } from '../agent'\nimport type { AgentProfile } from '../chat/agents'\nimport type { ProviderAuth, ProviderKey } from '../chat/auth'\nimport type { CompletionReference } from '../chat/completion'\nimport type { ResolvedConfig } from '../chat/config'\nimport type { Hint } from '../chat/hints'\nimport type { InteractionRequest, InteractionResponse, PendingInteractionEntry } from '../chat/interactions'\nimport type { ProviderDescriptor } from '../chat/providers'\nimport type { ApprovalOriginator } from '../chat/safe-mode-context'\nimport type { SessionExportFormat } from '../chat/session-export'\nimport type { EditOutcome, EditPayload, Picked, Screen, SessionMeta, Settings, StreamEvent } from '../chat/types'\nimport type { Session, SessionData } from '../session'\nimport type { ToolDef } from '../tools/types'\nimport type { PromptPart, SessionContentBlock, SessionTurn, ThinkingLevel, ToolResultContent } from '../types'\nimport type { InFlightToolCall } from './cancel-tool-modal'\nimport type { ContextUsage } from './components'\nimport type { PickedModel } from './model-picker'\nimport type { Attachment } from './screens'\nimport type { SessionCompactResult } from './session-details-modal'\nimport { spawn } from 'node:child_process'\nimport { useKeyboard, useRenderer, useSelectionHandler } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { createAgent } from '../agent'\nimport { buildBuildSystem, buildPlanSystem, envSection } from '../chat/agent-prompt'\nimport { detectAuth } from '../chat/auth'\nimport { AUTO_COMPACT_MIN_GROWTH_FRACTION, shouldAutoCompact } from '../chat/auto-compact'\nimport { buildUpdateHint, useUpdateCheck } from '../chat/auto-update-hook'\nimport { bootTick } from '../chat/boot-profiler'\nimport { tryOpenBrowser } from '../chat/browser'\nimport { createFilesCompletionProvider } from '../chat/completion-files'\nimport { createSkillsCompletionProvider, uniqueSkillNamesFromReferences } from '../chat/completion-skills'\nimport { ConfigProvider, useConfig } from '../chat/config-context'\nimport { useDiscovery } from '../chat/discovery-context'\nimport {\n buildEditOutcomesAnnotation,\n mergeApprovalAndBodyOutcomes,\n parseEditOutcomesFromResult,\n resolveApprovalForPayload,\n rewriteMultiEditHeader,\n stripEditOutcomesAnnotation,\n} from '../chat/edit-approval'\nimport { extractEditPayload } from '../chat/edit-diff'\nimport { buildHints } from '../chat/footer-hints'\nimport { formatTaskSummary, previewLine } from '../chat/format'\nimport { generateSessionTitle } from '../chat/generate-title'\nimport {\n buildResumedToolResultsTurn,\n createInteractionTools,\n InteractionsProvider,\n makeRequestInteraction,\n pendingInteractionsFromTurns,\n useInteractionsActions,\n useInteractionsQueue,\n} from '../chat/interactions'\nimport { ensureKeybindingsFile, keybindingsPath, matchesBinding } from '../chat/keybindings'\nimport { McpAuthProvider, useMcpAuthDispatch } from '../chat/mcp-auth-context'\nimport { createFileMcpCredentialStore } from '../chat/mcp-credentials'\nimport { buildMcpServers } from '../chat/mcps-discovery'\nimport { formatPathForCwd } from '../chat/path-display'\nimport { findGitRoot } from '../chat/project-root'\nimport { getContextWindow, modelSupportsReasoning, piIdOf } from '../chat/providers'\nimport { addToSafelist, getSafelist, isOnSafelist, suggestSafelistEntry } from '../chat/safe-mode'\nimport { SafeModeProvider, useSafeModeActions, useSafeModeQueue } from '../chat/safe-mode-context'\nimport { writeSessionExport } from '../chat/session-export'\nimport { clampFps, DEFAULT_SETTINGS, SettingsProvider, useSettings } from '../chat/settings-context'\nimport { buildSkillsConfig, defaultSkillScanPaths } from '../chat/skills-discovery'\nimport {\n deriveSessionTitle,\n EDIT_TOOL_NAMES,\n eventsFromTurns,\n lastContextSizeFromTurns,\n listSessionMeta,\n selectableTurnIds,\n stripSpawnTokensLine,\n sumRunCosts,\n toolCallPreview,\n toolResultText,\n updateToolEventOutcomes,\n} from '../chat/store'\nimport {\n finalizeStreamingMarkdown,\n finalizeStreamingMarkdownForOwner,\n turnContextSize,\n useStreamBuffer,\n} from '../chat/streaming'\nimport { resolveChipColor, resolveTheme } from '../chat/theme'\nimport { ThemeProvider, useColors, useSurfaces } from '../chat/theme-context'\nimport { deleteTurnSafely, truncateTurnsAt } from '../chat/turn-operations'\nimport {\n buildPostCompactAttachments,\n compactConversation,\n selectFilesFromSession,\n summaryToTurn,\n} from '../compact'\nimport { createProcessContext } from '../contexts'\nimport { errorMessage } from '../errors'\nimport { cleanupPersistedSession, resolvePersistDir, resolveTasksDir } from '../loop-persistence'\nimport { connectMcpServers } from '../mcp'\nimport { loginMcpServer } from '../mcp/login'\nimport { McpOAuthProvider } from '../mcp/oauth-provider'\nimport { createSession, loadSession } from '../session'\nimport { formatTokenUsage } from '../stats'\nimport { accentColor } from './agent-picker'\nimport { CancelToolModal } from './cancel-tool-modal'\nimport { writeToClipboard } from './clipboard'\nimport { Footer } from './components'\nimport { CwdPickerModal } from './cwd-picker'\nimport { DiscoveryShell } from './discovery-shell'\nimport { EffortPickerModal } from './effort-picker'\nimport { KeybindingsModal } from './keybindings-modal'\nimport { ModalRoot, useModal } from './modal'\nimport { ModelPickerModal } from './model-picker'\nimport { AuthScreen, ChatScreen, isSessionRowId, SessionsScreen } from './screens'\nimport { SessionDetailsModal } from './session-details-modal'\nimport { SettingsModal } from './settings-modal'\nimport { ChipStyleProvider, MdStyleProvider } from './theme'\nimport { TodosModal } from './todos-modal'\nimport { TurnDetailsModal } from './turn-details-modal'\n\n/**\n * One entry in the user-prompt queue. Captured at submit time alongside\n * the original prompt's resolved `CompletionReference`s so the drain loop\n * can re-run the same skill-activation / chip-highlight flow it would\n * have run for an immediately-submitted prompt.\n */\ninterface QueuedMessage {\n prompt: string\n references: readonly CompletionReference<unknown>[]\n attachments: readonly Attachment[]\n}\n\n/**\n * Surface failures that are normally silenced (teardown / save) when the\n * `ZIDANE_DEBUG` env var is set. Logging via `console.error` would otherwise\n * trigger OpenTUI's error console overlay and clutter the UI for end users.\n */\nconst debugLog: (label: string, err: unknown) => void\n = process.env.ZIDANE_DEBUG\n ? (label, err) => console.error(`[zidane/tui] ${label}:`, err)\n : () => {}\n\n/**\n * Launch the user's preferred editor on `path`. Tries `$EDITOR` /\n * `$VISUAL` first (the standard *nix convention for \"open this\n * config file\"); falls back to the platform's default opener\n * (`open` on macOS, `xdg-open` on Linux, `start` on Windows). The\n * spawned process is detached + unref'd so closing the TUI doesn't\n * kill the editor (and vice versa).\n *\n * Resolves once the spawn is dispatched — we don't wait for the\n * editor to exit. The user is expected to save and either restart\n * the TUI (for changes that need to be picked up at launch, like\n * keybindings) or simply continue working.\n */\nasync function launchEditor(path: string): Promise<void> {\n const envEditor = (process.env.VISUAL ?? process.env.EDITOR ?? '').trim()\n const [cmd, args] = (() => {\n if (envEditor)\n return [envEditor, [path]] as const\n if (process.platform === 'darwin')\n return ['open', [path]] as const\n if (process.platform === 'win32')\n return ['cmd', ['/c', 'start', '\"\"', path]] as const\n return ['xdg-open', [path]] as const\n })()\n spawn(cmd, args, {\n detached: true,\n stdio: 'ignore',\n shell: process.platform === 'win32',\n }).unref()\n}\n\n/**\n * Session-metadata key under which the TUI persists the user's \"pinned\"\n * active skills — the set the user toggled on via `/skill-name` and\n * expects to stay active across run boundaries (and TUI restarts).\n *\n * Separate from the agent's `skillActivationState` for two reasons:\n * 1. The framework's run-end pass deactivates everything; the pinned\n * set survives that pass so the next prompt can re-activate.\n * 2. The agent's session-resume rehydrator only sees `skills_use`\n * `tool_call` blocks in history. Slash-command activations bypass\n * that path; this metadata key is the system-of-record for them.\n */\nconst ACTIVE_SKILLS_META_KEY = 'zidane.activeSkills'\n\n/**\n * Read the pinned-skills set out of session metadata. Tolerant by\n * design — older sessions, manually-edited metadata, or a future\n * type drift all degrade to \"no pins\", never throw. Returns a fresh\n * Set so callers can mutate-and-store without aliasing the input.\n */\nfunction readPinnedSkills(raw: unknown): ReadonlySet<string> {\n if (!Array.isArray(raw))\n return new Set<string>()\n return new Set(raw.filter((v): v is string => typeof v === 'string' && v.length > 0))\n}\n\n/**\n * Mirror the pinned-skills set into session metadata. Sorted for\n * stable on-disk ordering (diffs / debugging). `session.setMeta`\n * already routes through `session:meta` hooks, so observers see a\n * single normalized payload regardless of insertion order.\n *\n * Imported lazily via the session ref — calling sites already\n * captured `session` in scope, so we accept it as an argument\n * rather than re-fetching from a ref.\n */\nfunction persistPinnedSkills(\n session: { setMeta: (key: string, value: unknown) => void },\n pins: ReadonlySet<string>,\n): void {\n session.setMeta(ACTIVE_SKILLS_META_KEY, Array.from(pins).sort())\n}\n\n/**\n * Filter a `multi_edit` (or single `edit` / `write_file`) input's hunk\n * list down to the approved subset, in original order. Used by the\n * approval gate to rebind `ctx.input.edits` after a partial decision —\n * the tool body then runs its legacy atomic semantics against the\n * smaller batch. Non-array inputs short-circuit to an empty list; the\n * caller treats that as \"nothing to apply\".\n */\nfunction reduceEditsByOutcomes(\n edits: unknown,\n outcomes: readonly EditOutcome[],\n): unknown[] {\n if (!Array.isArray(edits))\n return []\n const out: unknown[] = []\n for (let i = 0; i < edits.length; i++) {\n const outcome = outcomes[i]\n if (!outcome || outcome.kind === 'applied')\n out.push(edits[i])\n }\n return out\n}\n\n/**\n * Top-level TUI component. Accepts a fully-resolved `ResolvedConfig` and wires\n * everything (settings, modal layer, screens, footer) underneath it.\n *\n * Hosts can either drive this via `runTui()` for the standard bootstrap or\n * mount `<App config={resolveConfig(...)} />` themselves inside a renderer\n * they already own.\n */\nexport function App({ config }: { config: ResolvedConfig }) {\n const initialSettings = useMemo(\n () => ({ ...DEFAULT_SETTINGS, ...config.initialSettings }),\n [config.initialSettings],\n )\n\n const onSettingsChange = useCallback(\n (settings: Settings) => config.stateStore.save({ ...config.stateStore.load(), settings }),\n [config.stateStore],\n )\n\n return (\n <ConfigProvider config={config}>\n <SettingsProvider initial={initialSettings} onChange={onSettingsChange}>\n <ThemedShell />\n </SettingsProvider>\n </ConfigProvider>\n )\n}\n\n/**\n * Reads `settings.theme` from the surrounding `SettingsProvider`, resolves\n * it to a `Theme`, and mounts everything else underneath a `ThemeProvider`.\n * Split out so `App` doesn't need to call `useSettings` (which would force\n * it to live inside its own provider — invalid).\n *\n * `resolveTheme` falls back to `DEFAULT_THEME` on unknown ids, so an\n * out-of-date `state.json` (theme renamed / removed) never breaks rendering.\n */\nfunction ThemedShell() {\n const { settings } = useSettings()\n const theme = useMemo(() => resolveTheme(settings.theme), [settings.theme])\n return (\n <ThemeProvider theme={theme}>\n <MdStyleProvider>\n <ChipStyleProvider>\n <SafeModeProvider>\n <InteractionsProvider>\n <McpAuthProvider>\n <DiscoveryShell>\n <ModalRoot>\n <AppShell />\n </ModalRoot>\n </DiscoveryShell>\n </McpAuthProvider>\n </InteractionsProvider>\n </SafeModeProvider>\n </ChipStyleProvider>\n </MdStyleProvider>\n </ThemeProvider>\n )\n}\n\nfunction AppShell() {\n // Boot profiler: AppShell evaluating means React's first reconciliation\n // pass has reached us. The `useEffect` further down emits another tick\n // once that pass is committed (i.e. the first frame is on screen).\n bootTick('AppShell:render-enter')\n\n const renderer = useRenderer()\n const modal = useModal()\n const config = useConfig()\n const { settings } = useSettings()\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n\n // Fires once after React commits the very first render. The boot\n // profiler turns this into the \"time to first paint\" marker.\n useEffect(() => {\n bootTick('AppShell:first-effect (post-first-paint)')\n }, [])\n // `useSafeModeQueue` re-renders on every push/pop; `useSafeModeActions`\n // hands back a stable object, so anything memoizing over the actions\n // (gate handlers, abort callback) keeps a single identity across queue\n // churn. See safe-mode-context.tsx.\n const queue = useSafeModeQueue()\n const { requestApproval, resolveHead, denyAll } = useSafeModeActions()\n // Head of the safe-mode approval queue. `null` means \"nothing\n // pending\" and the chat screen shows the prompt input as usual.\n // Hoisted here (above every callback that reads it) so React doesn't\n // hit a temporal-deadzone when the model-picker / details callbacks\n // — declared earlier in source order — depend on it.\n const pendingApproval = queue[0] ?? null\n\n // Interactions queue — `present_plan` / `ask_user` tool calls awaiting\n // a user reply. Same two-context pattern as safe-mode. The head drives\n // the chat screen's prompt-slot UI; the actions stay stable so the\n // gate / resumed-flow callbacks don't rebind on every queue change.\n const interactionsQueue = useInteractionsQueue()\n const interactions = useInteractionsActions()\n const pendingInteractionEntry = interactionsQueue[0] ?? null\n const pendingInteraction = pendingInteractionEntry?.request ?? null\n\n // Destructure stable identities up-front so callbacks/effects can depend on\n // them directly (avoids the \"whole config in deps\" footgun — see S1 review).\n const {\n providers: providerRegistry,\n agents: agentRegistry,\n initialAgentId,\n store,\n stateStore,\n modelsFor,\n resumeProvider,\n initialPicked,\n initialState,\n keybindings,\n autoUpdate: autoUpdateConfig,\n } = config\n const lastResumedSessionId = initialState.lastSessionId\n // `resumeLastSession` is snapshotted at mount — toggling the setting\n // mid-session shouldn't replay the resume-on-launch effect against a\n // session that's already torn down. Reads from `initialState.settings`\n // (what was on disk when we started); defaults to `true` so users\n // who pre-date this setting get the historical resume behavior.\n const resumeLastSessionOnLaunch = initialState.settings?.resumeLastSession ?? true\n // User-scoped data root — credentials + safelist live here regardless\n // of whether project mode is on, so they're never committed alongside\n // a project's checked-in `.{prefix}/` directory.\n const dataDir = config.paths.userDir\n\n // Active agent profile. Held in a ref alongside state so `buildAgent` —\n // which the resume effect depends on — stays referentially stable across\n // profile switches (otherwise the effect would re-fire and re-activate\n // the originally-resumed session, clobbering any session the user\n // navigated to in between). State drives the picker's UI; the ref carries\n // the latest value into `buildAgent` synchronously.\n const [pickedAgent, setPickedAgent] = useState<AgentProfile>(\n () => agentRegistry[initialAgentId] ?? Object.values(agentRegistry)[0],\n )\n const pickedAgentRef = useRef(pickedAgent)\n\n // -------------------------------------------------------------------------\n // Safe-mode plumbing.\n //\n // Hook handlers run inside the agent loop — they need stable references to\n // the latest \"is safe-mode on?\" flag and to the cwd (the project key in\n // `projects.json`). Refs decouple registration from React re-renders so we\n // don't re-register on every settings flip.\n // -------------------------------------------------------------------------\n\n const safeModeEnabledRef = useRef(settings.safeMode)\n useEffect(() => { safeModeEnabledRef.current = settings.safeMode }, [settings.safeMode])\n\n // Mirror the user's `targetFps` setting onto the live renderer. The\n // boot value was already seeded inside `runTui`; this effect handles\n // every subsequent flip via the Settings modal (30 / 60 / 120). We\n // pin both `targetFps` and `maxFps` to the same value so an idle\n // frame doesn't try to render faster than the configured target.\n useEffect(() => {\n const fps = clampFps(settings.targetFps)\n if (renderer.targetFps !== fps)\n renderer.targetFps = fps\n if (renderer.maxFps !== fps)\n renderer.maxFps = fps\n }, [renderer, settings.targetFps])\n\n // Mirror finalized in-app selections to the OS clipboard. Terminal\n // apps eat `cmd+c` at the OS keymap level so we can't intercept it,\n // but `writeToClipboard` (OSC 52 + native pbcopy / wl-copy / xclip /\n // clip) populates the clipboard on drag-end — so the user just hits\n // `cmd+v` in any other app and the dragged text is there.\n //\n // `lastCopiedRef` debounces redundant copies — the selection handler\n // fires on every state change (drag-update, finalize, clear) and\n // each invocation forks `pbcopy` on macOS; we keep that to one fork\n // per distinct selection.\n const lastCopiedRef = useRef('')\n useSelectionHandler((selection) => {\n if (selection.isDragging)\n return\n const text = selection.getSelectedText()\n if (!text || text === lastCopiedRef.current)\n return\n lastCopiedRef.current = text\n writeToClipboard(text)\n })\n\n // Persistence toggle — same pattern: ref keeps `buildAgent` independent\n // of `settings` for dependency-array purposes. A flip takes effect on\n // the NEXT session activation; an in-flight stream isn't torn down.\n const persistToolResultsRef = useRef(settings.persistToolResults)\n useEffect(() => { persistToolResultsRef.current = settings.persistToolResults }, [settings.persistToolResults])\n // Interactive-tools gating — same NEXT-activation semantics as\n // `persistToolResults`. Read at `buildAgent` time; flipping mid-stream\n // doesn't tear down an in-flight `ask_user` / `present_plan`.\n const allowInteractionRef = useRef(settings.allowInteraction)\n useEffect(() => { allowInteractionRef.current = settings.allowInteraction }, [settings.allowInteraction])\n\n // Auto-compaction toggle + threshold — refs so the post-turn trigger\n // check inside `onSubmitPrompt` reads the latest values without forcing\n // the callback to depend on `settings` (which would re-bind it on every\n // unrelated setting flip).\n const autoCompactRef = useRef(settings.autoCompact)\n useEffect(() => { autoCompactRef.current = settings.autoCompact }, [settings.autoCompact])\n const autoCompactThresholdRef = useRef(settings.autoCompactThreshold)\n useEffect(() => { autoCompactThresholdRef.current = settings.autoCompactThreshold }, [settings.autoCompactThreshold])\n /**\n * Post-compaction baseline for the hysteresis check in\n * {@link shouldAutoCompact}. Latched to the effective post-compact token\n * count whenever a compaction lands (see {@link onCompactSession}), reset\n * to `undefined` on session change so the first compaction in a fresh\n * session fires off the absolute threshold without any growth gating.\n * Pairs with {@link AUTO_COMPACT_MIN_GROWTH_FRACTION} in the predicate.\n */\n const lastCompactedInputTokensRef = useRef<number | undefined>(undefined)\n\n // Smooth-streaming toggle — read on every tick by the stream buffer, so\n // flipping the setting mid-stream takes effect on the next drain.\n const smoothStreamingRef = useRef(settings.smoothStreaming)\n useEffect(() => { smoothStreamingRef.current = settings.smoothStreaming }, [settings.smoothStreaming])\n\n // Project anchor — used as the key in `projects.json` (safelist),\n // as the `cwd` for skill / MCP / file discovery, as the scope tag\n // on new sessions, and as the export resolver's anchor. Walks up\n // from `process.cwd()` to the enclosing git root so two cwds inside\n // the same repo (e.g. `repo/` and `repo/packages/foo`) share ONE\n // project — one safelist, one session list, one set of discovered\n // skills/MCPs/files. Outside a git repo we fall back to the bare\n // cwd, keeping the historical \"this directory is its own project\"\n // behavior for non-repo work.\n //\n // Captured once per AppShell mount with `useState` lazy init —\n // `useMemo([])` would technically allow React to recompute under\n // memory pressure, `useState` is a hard guarantee.\n const [projectDir, setProjectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd())\n\n // Dynamic working directory — the cwd tools resolve paths against.\n // Starts at `process.cwd()` and can be changed at runtime (e.g.\n // Ctrl+G). When changed, `process.chdir()` is also called so child\n // processes inherit the new cwd. `projectDir` is recomputed to the\n // new cwd's git root (or the cwd itself outside a repo) so safelists,\n // sessions, and discovery follow the user across projects.\n const [cwd, setCwdRaw] = useState(process.cwd)\n const cwdRef = useRef(cwd)\n cwdRef.current = cwd\n const setCwd = useCallback((next: string) => {\n process.chdir(next)\n setCwdRaw(next)\n setProjectDir(findGitRoot(next) ?? next)\n }, [])\n\n // In-memory cache for the project's safelist so `gateDecision` doesn't\n // re-read `projects.json` from disk on every tool call (matters when a\n // parallel batch fires dozens of gate hooks in the same microtask). The\n // ref is seeded lazily and explicitly refreshed when we persist an entry\n // — the TUI is the only writer, so external invalidation isn't needed.\n const safelistRef = useRef<readonly string[] | null>(null)\n const readSafelist = useCallback((): readonly string[] => {\n if (safelistRef.current === null)\n safelistRef.current = getSafelist(dataDir, projectDir)\n return safelistRef.current\n }, [dataDir, projectDir])\n // Drop the cache on a project switch (only happens across mounts today,\n // but cheap insurance for future code that swaps `projectDir`).\n useEffect(() => { safelistRef.current = null }, [dataDir, projectDir])\n\n // Session-scoped safelist — tool entries approved via \"accept for session\"\n // live here until the session tears down or the TUI exits. Checked in\n // `gateDecision` after the disk safelist, before prompting.\n const sessionSafelistRef = useRef<Set<string>>(new Set())\n\n // Forward ref to the stream buffer — populated below where the buffer\n // is created. The gate-side handlers (`gateDecision`, `applyGate`) need\n // to emit synthetic `tool` events for denied edits but are declared\n // before `useStreamBuffer` runs; reading via the ref breaks the cycle\n // without re-ordering the hook block.\n const streamRef = useRef<ReturnType<typeof useStreamBuffer> | null>(null)\n\n // Per-edit annotation pending map — populated on partial approvals\n // (keyed by `callId`), consumed by the `tool:transform` /\n // `child:tool:transform` hooks to append the `<edit-outcomes>` block\n // onto the tool's result text. Lives at AppShell scope so the map\n // outlives any individual `buildAgent` call (a session switch tears\n // down the agent but the next agent gets a fresh map by construction\n // — entries from a previous run never match a new run's callIds).\n //\n // Entries are normally deleted by the matching `tool:transform` /\n // `child:tool:transform` handler. Validation reject (no transform\n // fires) and abort/steer short-circuits inside `executeToolBatch`\n // (synthesize a result without firing transform) can strand entries\n // for the same callId. We catch both via the run-end `agent:done`\n // sweep registered in `buildAgent`, and the teardown path clears\n // the map outright as a belt-and-suspenders for partial destroys.\n const pendingAnnotationsRef = useRef<Map<string, readonly EditOutcome[]>>(new Map())\n\n // Discovery state — catalogs (skills, mcps, mcpsErrors, files) and\n // their ensure/refresh thunks live in <DiscoveryShell>, mounted ABOVE\n // <ModalRoot>. That layering is load-bearing: modals are rendered as\n // siblings of <AppShell> by <ModalRoot>, so a `setSkillsCatalog`\n // inside AppShell wouldn't reach an open modal. Hoisting the state\n // above ModalRoot lets context propagation drive live updates into\n // the active modal's subtree (same path as MCP auth state).\n //\n // AppShell still owns:\n // - `enabledSkills` / `enabledMcps` (user toggle allowlists, lives in `Settings`)\n // - `mcpCredentialStore` (used by `buildAgent`, `onLoginMcp`, `onLogoutMcp`)\n // - `dispatchAuth` (for login/logout flows)\n // `mcpsErrors` is consumed directly by `<SettingsModal>` via the\n // same context — AppShell doesn't read it, so it's not destructured\n // here. Same for `refreshFiles` (no UI affordance yet).\n const {\n skillsCatalog,\n mcpsCatalog,\n filesCatalog,\n ensureFiles: ensureFilesCatalog,\n ensureSkills: ensureSkillsCatalog,\n refreshSkills: onRefreshSkills,\n refreshMcps: onRefreshMcps,\n } = useDiscovery()\n\n // Memoized credential store — single file-backed store reused across both\n // bootstrap (`buildAuthProvider`) and the interactive login flow. Both\n // paths need to share so a login save is immediately visible to the next\n // bootstrap, and tokens refreshed by the SDK on a tool call are visible\n // to the modal's \"authed\" badge on the next reconciliation.\n const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir])\n\n // Dispatch comes from <McpAuthProvider> (mounted above <DiscoveryShell>);\n // the modal layer reads the same context, so login state propagates live\n // through the open settings modal as well. The ref pattern keeps long-\n // lived agent hooks (`mcp:auth:url`, …) firing the LATEST dispatcher\n // without re-binding the hook on every state update.\n const dispatchAuth = useMcpAuthDispatch()\n const dispatchAuthRef = useRef(dispatchAuth)\n dispatchAuthRef.current = dispatchAuth\n\n // Refs mirror the discovery + enabled state so closures (completion\n // provider, buildAgent) don't capture stale snapshots. The provider\n // re-reads on each `suggest` / `parseReferences` call; `buildAgent`\n // re-reads at session activation. Toggling a skill or MCP server takes\n // effect on the next session activation — by design, so a stream\n // already in flight isn't torn down.\n const skillsCatalogRef = useRef(skillsCatalog)\n skillsCatalogRef.current = skillsCatalog\n const enabledSkillsRef = useRef(settings.enabledSkills)\n enabledSkillsRef.current = settings.enabledSkills\n const mcpsCatalogRef = useRef(mcpsCatalog)\n mcpsCatalogRef.current = mcpsCatalog\n const enabledMcpsRef = useRef(settings.enabledMcps)\n enabledMcpsRef.current = settings.enabledMcps\n const filesCatalogRef = useRef(filesCatalog)\n filesCatalogRef.current = filesCatalog\n\n // `ensureFilesCatalog` / `ensureSkillsCatalog` close over project\n // state via refs (the thunks themselves are stable across renders),\n // so the providers can be memoized once and survive project swaps —\n // the thunks just re-target the new slot internally.\n const ensureFilesCatalogRef = useRef(ensureFilesCatalog)\n ensureFilesCatalogRef.current = ensureFilesCatalog\n const ensureSkillsCatalogRef = useRef(ensureSkillsCatalog)\n ensureSkillsCatalogRef.current = ensureSkillsCatalog\n\n // `@`-completion path formatter — rewrites discovery's\n // project-root-relative paths into the form the agent's\n // CWD-resolving tools will actually find. Captures `process.cwd()`\n // at mount; the TUI doesn't chdir during its lifetime so a stable\n // closure is correct.\n const formatFilePathForCwd = useMemo(() => {\n return (entry: { path: string }) => formatPathForCwd(entry.path, projectDir, cwd)\n }, [projectDir, cwd])\n\n // Building the array reference against the catalog identities — not\n // []! — is the cheap way to force `useCompletion` to rerun `suggest`\n // when a background rescan lands fresh entries while the popover is\n // already open. The provider OBJECTS still read live state via refs,\n // so this is just an identity bump; the engine's `useMemo` chain\n // (rawTrigger → active → suggest effect) cascades from it.\n const completionProviders = useMemo(\n () => [\n createSkillsCompletionProvider({\n getCatalog: () => skillsCatalogRef.current,\n getEnabled: () => enabledSkillsRef.current,\n ensureCatalog: () => ensureSkillsCatalogRef.current(),\n }),\n createFilesCompletionProvider({\n getCatalog: () => filesCatalogRef.current,\n ensureCatalog: () => ensureFilesCatalogRef.current(),\n formatPath: formatFilePathForCwd,\n }),\n ] as const,\n [filesCatalog, skillsCatalog, formatFilePathForCwd],\n )\n\n /**\n * Outcome of the approval gate.\n *\n * - `'allow'` — the call proceeds untouched.\n * - `'deny'` — the call is refused. `outcomes` (when set, for an\n * edit-family tool) drive the synthetic transcript event so the\n * user can see which hunks would have been applied vs denied.\n * - `'partial'` — a subset of an edit-family call. The caller MUST\n * rebind the tool ctx's `input.edits` to the approved subset; the\n * `outcomes` array is 1:1 with the ORIGINAL hunks (so the renderer\n * shows the full picture). The TUI's `tool:transform` hook reads\n * `outcomes` back out of a pending-annotation map and appends the\n * `<edit-outcomes>` block onto the tool's result text — this is\n * what carries the per-hunk view through to the wire / persisted\n * tool_result on the next agent turn.\n */\n type GateOutcome\n = | { kind: 'allow' }\n | { kind: 'deny', outcomes?: readonly EditOutcome[], editPayload?: EditPayload }\n | { kind: 'partial', outcomes: readonly EditOutcome[], editPayload: EditPayload, reducedEdits: readonly unknown[] }\n\n /**\n * Single source of truth for \"should this call execute?\". Three short-circuits:\n *\n * - Safe-mode globally off → always allow.\n * - Call covered by the project safelist or the implicit read-only set\n * → always allow without prompting.\n * - Otherwise → prompt the user and act on their decision (including\n * persisting a new safelist entry on \"accept + safelist\", or\n * injecting per-edit outcomes on a partial multi_edit approval).\n *\n * Wired into the parent agent via `tool:gate` / `mcp:tool:gate`, and to\n * every subagent (transitively, for free) via `child:tool:gate` /\n * `child:mcp:tool:gate` — see the bubble in `src/tools/spawn.ts`.\n */\n const gateDecision = useCallback(\n async (\n tool: string,\n input: Record<string, unknown>,\n turnId: string | undefined,\n callId: string,\n originator: ApprovalOriginator,\n ): Promise<GateOutcome> => {\n if (!safeModeEnabledRef.current)\n return { kind: 'allow' }\n if (isOnSafelist(readSafelist(), tool, input))\n return { kind: 'allow' }\n if (isOnSafelist([...sessionSafelistRef.current], tool, input))\n return { kind: 'allow' }\n\n const decision = await requestApproval(tool, input, originator)\n\n // Safelist side effects fire only on the explicit accept-* paths.\n // `partial` collapses to a one-off mixed decision — no entry written.\n if (decision === 'accept-session') {\n sessionSafelistRef.current.add(suggestSafelistEntry(tool, input))\n }\n else if (decision === 'accept-safelist') {\n const entry = suggestSafelistEntry(tool, input)\n addToSafelist(dataDir, projectDir, entry)\n safelistRef.current = null\n }\n\n // File-edit tools get the per-edit outcome treatment. For everything\n // else, `partial` is meaningless — collapse to `allow`.\n const editPayload = extractEditPayload(tool, input)\n if (editPayload) {\n const resolved = resolveApprovalForPayload(decision, editPayload)\n if (resolved.shouldBlock) {\n // Always emit a synthetic `tool` event for denied edits so the\n // transcript shows the intended diff (with denied badges) instead\n // of silently swallowing the call.\n if (resolved.syntheticEvent) {\n streamRef.current?.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(tool, input),\n tool,\n input,\n edit: resolved.syntheticEvent,\n callId,\n ...(turnId ? { turnId } : {}),\n })\n }\n return {\n kind: 'deny',\n outcomes: resolved.outcomes,\n ...(resolved.syntheticEvent ? { editPayload: resolved.syntheticEvent } : {}),\n }\n }\n // Partial — emit the synthetic event with the FULL hunks + outcomes\n // so the live transcript shows every hunk badged (applied / denied),\n // then return so `applyGate` reduces `ctx.input.edits` to the\n // approved subset. The tool body sees the smaller batch and runs\n // legacy single-mode atomic semantics on it; `tool:transform`\n // appends the `<edit-outcomes>` annotation to the result so the\n // wire / persisted history carries the per-hunk decisions through\n // to replay. The original `ctx.input` reference (still on the\n // assistant turn's `tool_call` block in `ctx.turns`) stays clean\n // since we rebind to a fresh shallow-clone in `applyGate`.\n if (resolved.syntheticEvent) {\n const reducedEdits = reduceEditsByOutcomes(input.edits, resolved.outcomes)\n streamRef.current?.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(tool, input),\n tool,\n input,\n edit: resolved.syntheticEvent,\n callId,\n ...(turnId ? { turnId } : {}),\n })\n return {\n kind: 'partial',\n outcomes: resolved.outcomes,\n editPayload: resolved.syntheticEvent,\n reducedEdits,\n }\n }\n // accept-* (or partial that resolved to all-applied) → allow.\n return { kind: 'allow' }\n }\n\n if (decision === 'deny')\n return { kind: 'deny' }\n return { kind: 'allow' }\n },\n [dataDir, projectDir, requestApproval, readSafelist],\n )\n\n // Initial screen + picked seed from the resolved config so a returning user\n // lands straight on chat (or sessions) without an auth-screen flash.\n const [screen, setScreen] = useState<Screen>(() => {\n if (!resumeProvider)\n return 'auth'\n return lastResumedSessionId ? 'chat' : 'sessions'\n })\n const [picked, setPicked] = useState<Picked | null>(() => initialPicked)\n // Mirror of `picked` for callbacks (resumed-interaction resolver) that\n // are captured before a model / effort swap and would otherwise carry\n // a stale snapshot — the picker can change mid-pause so the next run\n // should use whatever the user has most-recently chosen.\n const pickedRef = useRef(picked)\n pickedRef.current = picked\n const [sessions, setSessions] = useState<SessionMeta[]>([])\n const [currentSession, setCurrentSession] = useState<SessionMeta | null>(null)\n const [events, setEvents] = useState<StreamEvent[]>([])\n const [busy, setBusy] = useState(false)\n /**\n * React-state mirror of {@link autoCompactInFlightRef}. The ref drives\n * the gate logic (sync read during submits); this state drives the UI\n * indicator — the small spinner painted over the session title. Without\n * it the title wouldn't re-render when compaction starts / ends.\n */\n const [compacting, setCompacting] = useState(false)\n /**\n * FIFO of user prompts the user typed while a previous run was still in\n * flight. The transcript echoes each one immediately (so the user sees\n * \"I sent that\"), but only the head of the queue is fed to `agent.run()`\n * at any given moment — see {@link drainMessageQueue}. Mirrored via a\n * ref so the drain loop reads the freshest value synchronously and the\n * UI re-renders only when the visible length changes.\n *\n * Esc / abort / teardown clears the queue — pending submits are dropped\n * along with the in-flight run (same \"esc = stop everything\" rule the\n * approval queue follows).\n */\n const messageQueueRef = useRef<QueuedMessage[]>([])\n const [messageQueue, setMessageQueue] = useState<readonly QueuedMessage[]>([])\n /**\n * `true` while {@link drainMessageQueue} is iterating. Gates re-entry —\n * a second user submit during a run pushes onto {@link messageQueueRef}\n * and returns; the loop picks the new entry up on its next iteration\n * instead of spawning a parallel `agent.run()` (which `createAgent`\n * would refuse with \"Agent is already running\").\n *\n * A ref (not state) because the gate must be synchronous: two submits\n * in the same tick would both observe the same stale `busy` boolean\n * otherwise.\n */\n const runningRef = useRef(false)\n /** Token count from the most recent assistant turn (caching-aware). */\n const [lastInputTokens, setLastInputTokens] = useState(0)\n /**\n * Cumulative USD cost across every run in the active session — seeded\n * from `session.runs` on activation and topped up by each `turn:after`'s\n * `usage.cost`. Only providers that report cost (currently OpenRouter)\n * contribute; everywhere else this stays at 0 and the footer hides it.\n */\n const [sessionCost, setSessionCost] = useState(0)\n /**\n * Synchronous mirror of {@link lastInputTokens} for callbacks that need\n * to read the freshest value after `await agent.run()` resolves. The\n * `turn:after` hook updates both the state (drives the footer) and this\n * ref (drives the auto-compact trigger check) in lock-step.\n */\n const lastInputTokensRef = useRef(0)\n /**\n * Active turn id when the user is in \"select turn\" mode (ctrl+s on the\n * chat screen). `null` means normal mode — typing is enabled, transcript\n * has no highlight. When set, the prompt textarea is unfocused so up/down\n * navigate the turn list, ↵ opens the details modal, ⎋ exits the mode.\n */\n const [selectedTurnId, setSelectedTurnId] = useState<string | null>(null)\n /**\n * Highlighted entry in the type-ahead queue box. `null` means the user\n * is in normal mode (textarea focused, queue purely informational);\n * `0+` is an index into `messageQueue` and unfocuses the textarea so\n * up/down navigate the queue, `ctrl+return` pushes, `delete` drops, esc\n * exits back to the prompt. Auto-clears when the queue empties.\n *\n * Mirrored in {@link queueSelectionIndexRef} so callbacks with `[]`\n * deps can read the current selection synchronously without forcing\n * either a re-bind on every selection change or a side-effecting\n * state updater (React requires updaters to be pure).\n */\n const [queueSelectionIndex, setQueueSelectionIndex] = useState<number | null>(null)\n const queueSelectionIndexRef = useRef<number | null>(null)\n queueSelectionIndexRef.current = queueSelectionIndex\n\n const agentRef = useRef<Agent | null>(null)\n const sessionRef = useRef<Session | null>(null)\n\n // Sync cwd changes to the live agent's execution handle so the\n // current session's tools resolve paths against the new directory\n // immediately — no session restart needed. A `system:transform` hook\n // rewrites the `<env>` block on every turn so the model always sees\n // the current cwd and project root.\n useEffect(() => {\n const handle = agentRef.current?.handle\n if (handle)\n handle.cwd = cwd\n }, [cwd])\n\n // Rewrite the system prompt's <env> block with the live cwd/projectDir\n // on every turn via `system:transform`. The hook is re-registered\n // whenever cwd or projectDir changes so the closure captures the\n // latest values. The unregister returned by `hook()` tears down the\n // previous hook before the new one is installed.\n useEffect(() => {\n const agent = agentRef.current\n if (!agent)\n return\n const profile = pickedAgentRef.current\n if (profile.id !== 'build' && profile.id !== 'plan')\n return\n const unregister = agent.hooks.hook('system:transform', (ctx) => {\n const allowInteraction = allowInteractionRef.current !== false\n const envOpts = {\n cwd,\n ...(projectDir !== cwd ? { projectRoot: projectDir } : {}),\n allowInteraction,\n }\n const freshEnv = envSection(envOpts)\n ctx.system = ctx.system.replace(/<env>[\\s\\S]*?<\\/env>/, freshEnv)\n })\n return unregister\n }, [cwd, projectDir])\n\n /**\n * Live registry of in-flight tool calls — populated by `tool:before`\n * (and `child:tool:before`), drained by `tool:after` / `tool:error` /\n * `tool:cancelled` (and their `child:*` siblings). Drives the\n * \"cancel tool call\" picker (see {@link CancelToolModal}).\n *\n * Mirrored in `inFlightToolsRef` for callbacks that read the latest\n * value synchronously without listing `inFlightTools` in their deps —\n * the picker open path needs both: a state snapshot for the modal's\n * rendered rows, and the live ref for any subsequent cancel actions\n * that fire after the snapshot was taken.\n *\n * Excludes `mcp:tool:before` / `mcp:tool:after` deliberately for\n * v1 — `agent.cancelTool(callId)` operates on the unified loop-side\n * callId registry, which MCP tools also live in (they're dispatched\n * through the same `executeSingleTool`), so the cancel works for\n * them too; we just don't surface them in the picker UI yet to\n * avoid drowning the list with high-cardinality MCP transient calls.\n */\n const [inFlightTools, setInFlightTools] = useState<readonly InFlightToolCall[]>([])\n const inFlightToolsRef = useRef<readonly InFlightToolCall[]>([])\n inFlightToolsRef.current = inFlightTools\n\n /**\n * Live registry of running background tasks — populated by\n * `background:start` (and `child:background:start`), drained by\n * `background:exit` (and the child sibling). Same shape as\n * {@link InFlightToolCall} so it merges cleanly into the cancel-tool\n * picker's snapshot; the `kind: 'task'` discriminator routes the\n * cancel callback to `agent.killBackgroundTask(taskId)` instead of\n * `agent.cancelTool(callId)`.\n *\n * Tracked separately from `inFlightTools` because the two have\n * different lifetimes: a tool call is in-flight only while\n * `tool:before` and `tool:after` straddle, but a background task\n * survives PAST its spawning tool call — the `shell` body returns\n * the handle and `tool:after` fires while the task keeps running.\n * Without this separate registry, `ctrl+k` would never see a task\n * because the tool entry has already drained.\n */\n const [backgroundTasks, setBackgroundTasks] = useState<readonly InFlightToolCall[]>([])\n const backgroundTasksRef = useRef<readonly InFlightToolCall[]>([])\n backgroundTasksRef.current = backgroundTasks\n\n /**\n * Names of currently-active skills, tracked via `skills:activate` /\n * `skills:deactivate` hooks. Drives the footer's \"✦ N skill(s)\"\n * chip — the user's only passive surface for noticing that a skill\n * (and its `allowed-tools` restrictions) is in effect. Cleared on\n * session teardown alongside the rest of the per-session live state.\n *\n * Stored as a Set rather than an array so dedup is structural (a\n * runaway `skills:activate` for the same name doesn't inflate the\n * count). React state is the snapshot we render against; a fresh\n * Set per update gives React identity-based change detection.\n */\n const [activeSkillNames, setActiveSkillNames] = useState<ReadonlySet<string>>(() => new Set<string>())\n /**\n * Mirror of {@link activeSkillNames} for synchronous reads in\n * `onSubmitPrompt`. The submit path runs outside React's render cycle\n * and needs to pre-activate every user-pinned skill before\n * `agent.run()` — listing the state in `useCallback`'s deps would\n * re-bind the handler on every activation change, which would\n * invalidate the textarea's submit binding on every `/skill` trigger.\n * The ref keeps the binding stable and the read fresh.\n */\n const activeSkillNamesRef = useRef<ReadonlySet<string>>(activeSkillNames)\n activeSkillNamesRef.current = activeSkillNames\n\n // Stable helpers — register/unregister are wired into the agent's\n // tool:* hooks (see `buildAgent` below) and into the cancel modal's\n // close path. Keeping them as `useCallback([])` keeps the hook\n // closures referentially stable across re-renders.\n const registerInFlightTool = useCallback((entry: InFlightToolCall) => {\n setInFlightTools((prev) => {\n // Idempotent: callId is the unique key. A duplicate `tool:before`\n // (shouldn't happen, but defensive) replaces rather than dupes.\n const filtered = prev.filter(e => e.callId !== entry.callId)\n return [...filtered, entry]\n })\n }, [])\n const unregisterInFlightTool = useCallback((callId: string) => {\n setInFlightTools(prev => prev.filter(e => e.callId !== callId))\n }, [])\n /**\n * In-flight auto-compaction promise. Held in a ref so the next\n * `onSubmitPrompt` invocation can `await` it before calling\n * `agent.run()` — this is what prevents a user-submitted prompt from\n * racing the compaction's `appendTurns(summaryTurn, …restored)` and\n * picking up the pre-compaction history mid-write.\n *\n * `null` between compactions. The promise itself resolves on success,\n * rejects on compaction error — callers should always await with\n * `.catch(() => {})` if they don't care to propagate the failure (the\n * compaction's own error path already surfaces it as a transcript\n * event).\n */\n const autoCompactInFlightRef = useRef<Promise<void> | null>(null)\n /**\n * `AbortController` paired with {@link autoCompactInFlightRef}. Aborted\n * by `teardown()` so navigating away from a session mid-compaction\n * cancels the LLM call instead of letting it run to completion against\n * a now-stale view of the session.\n */\n const autoCompactAbortRef = useRef<AbortController | null>(null)\n /**\n * Forward ref to {@link triggerAutoCompactIfNeeded}, hoisted up here so\n * callbacks declared earlier in the file (`continueResumedInteractions`,\n * `onSubmitPrompt`) can fire the trigger without depending on the\n * callback's lexical position. Initialized with a no-op and populated\n * via `useEffect` once `triggerAutoCompactIfNeeded` is defined below.\n */\n const triggerAutoCompactRef = useRef<(sessionId: string) => void>(() => {})\n\n const stream = useStreamBuffer(setEvents, {\n getSmooth: useCallback(() => smoothStreamingRef.current, []),\n })\n // Mirror into the forward ref so callbacks declared earlier (gate\n // handlers) can reach the latest buffer identity without listing it\n // as a dep. `useStreamBuffer` returns a stable object via `useMemo`,\n // so this assignment lands once and never re-fires.\n streamRef.current = stream\n\n const makePicked = useCallback((provider: ProviderAuth, modelId?: string): Picked | null => {\n // If the host narrowed the provider registry we may have a detected auth\n // with no descriptor to back it. Return null and let the caller bail; the\n // AuthScreen already filters these out, so this is a belt-and-braces guard.\n const descriptor = providerRegistry[provider.key]\n if (!descriptor)\n return null\n const remembered = initialState.lastModelByProvider?.[provider.key]\n // Prefer remembered → descriptor default → factory probe. Factories may\n // throw on missing credentials at construction time; by the time we get\n // here we've already detected auth, so the probe is safe, but the\n // descriptor default lets us avoid constructing in the common case.\n const model = modelId ?? remembered ?? descriptor.defaultModel ?? descriptor.factory().meta.defaultModel\n const effort = effortForModel(descriptor, model, initialState.lastEffortByModel)\n return effort ? { provider, model, effort } : { provider, model }\n }, [providerRegistry, initialState])\n\n // Shared abort sequence — flushes every awaiter (approval queue,\n // interactions queue, message queue) and signals the in-flight run to\n // stop. Hoisted above `buildAgent` so the gate handlers wired in there\n // can trigger it on user denial (see `applyGate`). The user-facing esc\n // path (`onAbort` below) wraps this with its popup short-circuit.\n const cancelRunOnDenial = useCallback((reason: string) => {\n denyAll()\n interactions.cancelAll(reason)\n messageQueueRef.current = []\n setMessageQueue([])\n setQueueSelectionIndex(null)\n agentRef.current?.abort()\n }, [denyAll, interactions])\n\n // -------------------------------------------------------------------------\n // Agent lifecycle: build a fresh Agent bound to the active Session and wire\n // streaming hooks. Re-created on every session switch so context never bleeds.\n // -------------------------------------------------------------------------\n\n const buildAgent = useCallback((session: Session, key: ProviderKey): Agent => {\n const descriptor = providerRegistry[key]\n if (!descriptor)\n throw new Error(`No provider registered for key \"${key}\"`)\n // Read the latest profile via the ref so a switch made between renders\n // (onPickAgent → activateSession in the same tick) picks up the new\n // preset without forcing `buildAgent` to depend on `pickedAgent`.\n const profile = pickedAgentRef.current\n // Compose the runtime preset:\n // - `skills`: project + user discovery paths, filtered by the user's\n // `enabledSkills` allowlist. Profile-supplied skills overlay last\n // so a preset that customizes (e.g.) `maxActive` still wins.\n // - `mcpServers`: discovered `.{prefix}/mcps.json` entries filtered\n // by `enabledMcps`. Profile-supplied servers concat after, so a\n // hard-coded \"test-only\" server in the preset isn't masked by a\n // project file that didn't declare it.\n //\n // All inputs read via refs — toggling skills / MCPs takes effect at\n // the NEXT session activation, not mid-stream.\n const skillsScan = defaultSkillScanPaths({ cwd: projectDir, prefix: config.prefix })\n const skillsConfig = buildSkillsConfig({ scan: skillsScan, enabled: enabledSkillsRef.current })\n const projectMcps = buildMcpServers({\n discovered: mcpsCatalogRef.current,\n enabled: enabledMcpsRef.current,\n })\n // `present_plan` + `ask_user` — wired here so the React-side queue\n // resolves the tool's Promise. The profile's own tools win on name\n // collision (a host that registers a custom `ask_user` for testing\n // shouldn't see ours silently shadow it).\n // `Settings.allowInteraction=false` strips both `ask_user` and\n // `present_plan` so the model can't pause the conversation. Gating\n // happens at the host (not inside `createInteractionTools`) because\n // it's a policy decision, not a property of the tools themselves.\n // The system prompt's interaction guidance + plan-mode doctrine\n // adapt to the same flag below.\n const allowInteraction = allowInteractionRef.current !== false\n const interactionTools: Record<string, ToolDef> = allowInteraction\n ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) })\n : {}\n // Re-compose the system prompt with the agent's real cwd + project\n // root injected into the env section. ONLY for the two built-in\n // profiles — a host-supplied profile keeps its own `preset.system`\n // verbatim (otherwise we'd silently overwrite it).\n //\n // The TUI's `projectDir` is the git root (or `process.cwd()` outside\n // a repo). The agent's tools resolve paths against `process.cwd()`\n // — which can be a subdirectory of the project. Telling the model\n // both anchors closes the gap that otherwise lets `@`-completion\n // paths (project-root-relative) get misinterpreted as cwd-relative\n // by `read_file` etc. The env block's path-resolution note kicks\n // in only when the two differ; running from the project root keeps\n // the prompt unchanged.\n const actualCwd = cwdRef.current\n const envOpts = {\n cwd: actualCwd,\n ...(projectDir !== actualCwd ? { projectRoot: projectDir } : {}),\n allowInteraction,\n }\n const builtInSystem = profile.id === 'build'\n ? buildBuildSystem(envOpts)\n : profile.id === 'plan'\n ? buildPlanSystem(envOpts)\n : null\n // Disk persistence wiring:\n // - persistDir: per-session subdir under <userDir>, always pinned to\n // the user dir (not project dir) so blobs never leak into a\n // checked-in `.{prefix}/` directory.\n // - off-switch: when `Settings.persistToolResults` is false we override\n // `behavior.persistThreshold = 0`, which the loop reads as \"disabled\"\n // (the persistence branch in `emitToolResult` short-circuits before\n // touching disk). The exclude list + dir are still set, so flipping\n // back on takes effect at the next session activation without rewiring.\n const persistDir = resolvePersistDir({ userDir: dataDir, sessionId: session.id })\n const persistDisabled = persistToolResultsRef.current === false\n const persistBehavior = persistDisabled\n ? { persistThreshold: 0, persistDir }\n : { persistDir }\n // Per-session background-tasks dir — pairs with `persistDir`'s layout\n // but lives under `<userDir>/<sessionId>/tasks/` so the session's\n // ephemeral data (task logs) clusters separately from the chat-wide\n // tool-result blobs. Created on first write by the context.\n const tasksDir = resolveTasksDir({ userDir: dataDir, sessionId: session.id })\n const agent = createAgent({\n ...profile.preset,\n ...(builtInSystem ? { system: builtInSystem } : {}),\n skills: { ...skillsConfig, ...(profile.preset.skills ?? {}) },\n mcpServers: [...projectMcps, ...(profile.preset.mcpServers ?? [])],\n tools: { ...interactionTools, ...(profile.preset.tools ?? {}) },\n behavior: { ...(profile.preset.behavior ?? {}), ...persistBehavior, tasksDir },\n execution: createProcessContext({ cwd: actualCwd }),\n provider: descriptor.factory(),\n session,\n // Inject OAuth-aware connector. The connector builds a non-interactive\n // McpOAuthProvider per OAuth-tagged server — bootstrap uses stored\n // tokens / refresh only. Missing tokens surface via\n // `mcp:auth:required` (see bootstrapServer in src/mcp/index.ts) and\n // the server is skipped. Interactive login lives in `onLoginMcp`\n // below; it writes to the same `mcpCredentialStore` so the next\n // session's bootstrap picks the tokens up.\n //\n // Closure captures `agent` for `agent.hooks` — referenced lazily\n // (first run / warmup), by which point the const is initialized.\n mcpConnector: configs => connectMcpServers(configs, undefined, agent.hooks, {\n buildAuthProvider: cfg => new McpOAuthProvider({\n name: cfg.name,\n store: mcpCredentialStore,\n }),\n }),\n })\n\n // Safe-mode gates -----------------------------------------------------\n // Four hooks: native vs MCP, parent vs subagent. The `child:*:gate`\n // events bubble from every subagent's hook bus via `bubbleHooks` (see\n // `src/tools/spawn.ts`) with the **same** gate ctx, so writes to\n // `block` / `reason` here propagate straight back to the child's loop.\n // A denied call never reaches `tool:before`, so it doesn't pollute the\n // transcript — the model receives `Blocked: …` as the denied tool's\n // result and the turn continues. Each pending approval resolves on\n // its own; the user must hit `esc abort run` (→ `onAbort`) to stop\n // the whole run.\n const emitUpstreamDeniedEvent = (\n name: string,\n input: Record<string, unknown>,\n ctx: { callId: string, reason: string, turnId?: string },\n ): void => {\n const payload = extractEditPayload(name, input)\n if (!payload)\n return\n const reason = ctx.reason || 'denied'\n const enriched: EditPayload = {\n ...payload,\n outcomes: payload.hunks.map(() => ({ kind: 'denied', reason })),\n }\n streamRef.current?.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(name, input),\n tool: name,\n input,\n edit: enriched,\n callId: ctx.callId,\n ...(ctx.turnId ? { turnId: ctx.turnId } : {}),\n })\n }\n\n const applyGate = async (\n name: string,\n input: Record<string, unknown>,\n ctx: {\n callId: string\n block: boolean\n reason: string\n turnId?: string\n // `tool:gate` / `child:tool:gate` / `mcp:tool:gate` ctx all carry\n // a mutable `input` slot (see `loop.ts:executeSingleTool`). The\n // typed declaration is a structural superset so this handler\n // works across all four gate hooks without per-hook overloads.\n input: Record<string, unknown>\n // Set by `bubbleHooks` (see `src/tools/spawn.ts`) on child\n // gate ctxs; absent on parent ctxs. Drives the modal's\n // `· child-N` attribution label and the gate's\n // {@link ApprovalOriginator} payload.\n childId?: string\n },\n ): Promise<void> => {\n // Upstream gate (skills / budgets / dedup) already refused this call.\n // For file-edit tools we still want the transcript to show the\n // intended diff with a \"denied (<reason>)\" tag — otherwise the user\n // is left guessing what the assistant tried to do.\n if (ctx.block) {\n emitUpstreamDeniedEvent(name, input, ctx)\n return\n }\n const originator: ApprovalOriginator = ctx.childId\n ? { kind: 'child', label: ctx.childId }\n : { kind: 'parent' }\n const outcome = await gateDecision(name, input, ctx.turnId, ctx.callId, originator)\n if (outcome.kind === 'deny') {\n // Refuse THIS call only — the harness writes `Blocked: <reason>`\n // as the tool result and the model continues the turn. Crucially\n // we do NOT call `cancelRunOnDenial()` here: when several tool\n // calls fire in parallel, a single user deny would otherwise\n // resolve every queued approval with `deny` (via `denyAll()`)\n // and cancel the run. The user's explicit \"stop everything\"\n // gesture is the `esc abort run` shortcut → `onAbort()`.\n ctx.block = true\n ctx.reason = 'User denied this tool call'\n // For an edit-family tool with hunks, emit a synthetic\n // `tool-result` event carrying the `<edit-outcomes>` block so\n // the live transcript reconstructs the same per-hunk badges\n // a partial-with-all-denied call would produce. Persisted\n // history still shows `Blocked: ...` (the substitute path\n // isn't used here so the wire stays terse) — replay of a\n // denied call from disk will only show the synthetic `tool`\n // event's diff badges, not the `tool-result` annotation.\n if (outcome.outcomes && outcome.outcomes.length > 0) {\n streamRef.current?.appendImmediate({\n kind: 'tool-result',\n text: `[fully denied] ${buildEditOutcomesAnnotation(outcome.outcomes)}`,\n tool: name,\n callId: ctx.callId,\n ...(ctx.turnId ? { turnId: ctx.turnId } : {}),\n })\n }\n return\n }\n if (outcome.kind === 'partial') {\n // Rebind `ctx.input` to a fresh shallow-clone whose `edits`\n // list contains only the approved subset. The tool body sees\n // the smaller batch and runs its single-mode atomic semantics\n // (multi_edit) against it. The model's ORIGINAL `tool_call`\n // block in `ctx.turns` stays untouched because the rebind\n // produces a new object — it never mutates the shared `input`\n // reference. `tool:transform` (registered below) appends the\n // `<edit-outcomes>` annotation to the resulting tool_result\n // so the wire / persisted history carries the per-hunk\n // decisions for replay.\n ctx.input = { ...input, edits: outcome.reducedEdits }\n pendingAnnotationsRef.current.set(ctx.callId, outcome.outcomes)\n }\n // `allow` → call proceeds with the model's original input.\n }\n\n agent.hooks.hook('tool:gate', ctx => applyGate(ctx.name, ctx.input, ctx))\n agent.hooks.hook('child:tool:gate', ctx => applyGate(ctx.name, ctx.input, ctx))\n agent.hooks.hook('mcp:tool:gate', ctx => applyGate(ctx.displayName, ctx.input, ctx))\n agent.hooks.hook('child:mcp:tool:gate', ctx => applyGate(ctx.displayName, ctx.input, ctx))\n\n // Per-edit annotation — runs after the tool body produces a result,\n // before persistence / wire emission. Mutates `ctx.result` in place\n // so the annotation rides the canonical tool_result text into both\n // the live transcript (via `tool:after`) and the persisted history\n // (via the loop's user-turn tool_result block).\n //\n // Three sources of truth, merged in this order:\n // 1. **Approval-side outcomes** (`pendingAnnotationsRef`) — 1:1\n // with the model's ORIGINAL `edits` list. `applied` is a\n // placeholder for hunks the user kept; `denied` / `skipped`\n // reflect the user's gate decision.\n // 2. **Body-side outcomes** — `multi_edit`'s best-effort body\n // emits an `<edit-outcomes>` block when any step failed. Keyed\n // against the APPROVED SUBSET (the rebound `ctx.input.edits`\n // the body actually saw), in subset-position order.\n // 3. **Merged outcomes** — `mergeApprovalAndBodyOutcomes` walks\n // approval and replaces each `applied` placeholder with the\n // body's next outcome, so the canonical block carries denied,\n // skipped, AND failed entries 1:1 with the original edits.\n //\n // The body's block is stripped before re-appending the merged one;\n // otherwise the parser (anchored on the FIRST block) would see the\n // subset-keyed body annotation and lose the gate decisions. The\n // body's header is rewritten via `rewriteMultiEditHeader` so the\n // subset-relative `N of M` count is replaced by the original total\n // (`M = approval.length`).\n //\n // Returns the post-transform result AND the final outcomes (or\n // `null` when no annotation work was needed). The caller uses the\n // outcomes to update the in-flight `tool` event so live diff badges\n // catch up to body-side failures the gate couldn't anticipate.\n const annotateEditResult = (\n toolName: string,\n callId: string,\n result: string | ToolResultContent[],\n input: Record<string, unknown>,\n ): {\n result: string | ToolResultContent[]\n mergedOutcomes: readonly EditOutcome[] | null\n } => {\n const approval = pendingAnnotationsRef.current.get(callId)\n if (approval)\n pendingAnnotationsRef.current.delete(callId)\n\n // Plain-text path — overwhelmingly the common shape for edit\n // tool results. Structured content arrays only show up for\n // tools that return images (read_file with `media: 'image'`);\n // edit-family tools always return strings.\n if (typeof result === 'string') {\n const bodyOutcomes = parseEditOutcomesFromResult(result)\n\n // Pass-through fast path: no approval state AND body didn't\n // emit a block — the legacy \"all applied, summary-only\" shape.\n if (!approval && !bodyOutcomes)\n return { result, mergedOutcomes: null }\n\n // No approval — body's annotation (when present) is already\n // keyed 1:1 with the model's input since no subset rebinding\n // happened. Pass the text through unchanged; surface\n // `bodyOutcomes` so the in-flight `tool` event picks up live\n // badges for body-side failures.\n if (!approval)\n return { result, mergedOutcomes: bodyOutcomes }\n\n // Approval present — merge with body (or just approval if body\n // didn't emit). ALWAYS strip a body-side `<edit-outcomes>`\n // block (well-formed OR malformed-but-textually-present) before\n // re-appending the merged version, so the parser doesn't see\n // duplicates. `stripEditOutcomesAnnotation` is a no-op on input\n // without a properly-anchored block, so this is safe in the\n // \"no body block\" case too.\n const merged = mergeApprovalAndBodyOutcomes(approval, bodyOutcomes)\n const stripped = stripEditOutcomesAnnotation(result)\n // Only multi_edit emits \"applied N of M edits\" headers; edit /\n // write_file headers (`Edited X: replaced N occurrences.` /\n // `Created/Updated X (N bytes).`) don't carry counts the merge\n // could disturb, so the rewrite is a no-op on those by virtue\n // of the regex not matching — but we still gate on `toolName`\n // for clarity.\n const path = typeof input.path === 'string' ? input.path : ''\n const rebuilt = toolName === 'multi_edit' && path\n ? rewriteMultiEditHeader(stripped, merged, path)\n : stripped\n const annotation = buildEditOutcomesAnnotation(merged)\n const out = rebuilt.length === 0 ? annotation : `${rebuilt}\\n\\n${annotation}`\n return { result: out, mergedOutcomes: merged }\n }\n\n // Structured-content path — only hit when an edit tool returns a\n // ToolResultContent[]. None of the built-in edit tools do; the\n // fallback appends an annotation text block so replay can still\n // recover outcomes.\n if (!approval)\n return { result, mergedOutcomes: null }\n const annotation = buildEditOutcomesAnnotation(approval)\n return {\n result: [...result, { type: 'text', text: `\\n${annotation}` }],\n mergedOutcomes: approval,\n }\n }\n\n // Wire-level annotation lands on `ctx.result` (so the persisted\n // tool_result and the LLM-facing wire both carry the merged block).\n // The in-flight `tool` event's `edit.outcomes` is updated in the\n // same tick so the live diff badges reflect the merged truth —\n // without it, a body-side failure on a partial-approval call would\n // paint `applied` badges until the next session reload.\n const applyEditAnnotationsToStream = (\n callId: string,\n outcomes: readonly EditOutcome[],\n ): void => {\n streamRef.current?.flushAndUpdate(events =>\n updateToolEventOutcomes(events, callId, outcomes),\n )\n }\n\n agent.hooks.hook('tool:transform', (ctx) => {\n const { result, mergedOutcomes } = annotateEditResult(ctx.name, ctx.callId, ctx.result, ctx.input)\n ctx.result = result\n if (mergedOutcomes)\n applyEditAnnotationsToStream(ctx.callId, mergedOutcomes)\n })\n agent.hooks.hook('child:tool:transform', (ctx) => {\n const { result, mergedOutcomes } = annotateEditResult(ctx.name, ctx.callId, ctx.result, ctx.input)\n ctx.result = result\n if (mergedOutcomes)\n applyEditAnnotationsToStream(ctx.callId, mergedOutcomes)\n })\n\n // MCP OAuth status — feed the reducer that backs the picker badge.\n // `dispatchAuthRef` so a hook firing from a long-lived agent doesn't\n // capture a stale dispatch from a previous render.\n agent.hooks.hook('mcp:auth:required', ({ name, reason }) => {\n dispatchAuthRef.current({ type: 'auth-required', name, reason })\n })\n agent.hooks.hook('mcp:auth:url', ({ name, url }) => {\n dispatchAuthRef.current({ type: 'auth-url', name, url })\n })\n agent.hooks.hook('mcp:auth:success', ({ name }) => {\n dispatchAuthRef.current({ type: 'auth-success', name })\n })\n agent.hooks.hook('mcp:auth:error', ({ name, error }) => {\n dispatchAuthRef.current({ type: 'auth-error', name, error: error.message })\n })\n agent.hooks.hook('mcp:connect', ({ name }) => {\n // OAuth-backed servers should land on `authed` even when bootstrap\n // succeeded silently (token already stored, no `needs-auth` blip).\n // Without this branch the row would stay `idle` and the picker\n // wouldn't expose the `o` (logout) action — the user couldn't\n // revoke their own stored credentials.\n if (mcpCredentialStore.load(name)?.tokens)\n dispatchAuthRef.current({ type: 'auth-success', name })\n else\n dispatchAuthRef.current({ type: 'connected', name })\n })\n\n // Parent streams ------------------------------------------------------\n // `turnId` rides every event so the TUI's select-turn mode can group\n // all events emitted by one SessionTurn — matches what the historical\n // path stamps via `eventsFromTurns`.\n agent.hooks.hook('stream:thinking', ({ delta, turnId }) => stream.queueStreamDelta('thinking', delta, { turnId }))\n agent.hooks.hook('stream:text', ({ delta, turnId }) => stream.queueStreamDelta('markdown', delta, { turnId }))\n agent.hooks.hook('tool:before', async ({ callId, name, input, turnId }) => {\n // Track the in-flight call so the cancel picker (Ctrl+K) sees it.\n // We register here rather than on `tool:dispatched` because:\n // - the picker reads \"the model is waiting on this\" semantics,\n // not \"the loop accounted for this call\" semantics; gate-blocked\n // and gate-substituted paths never reach `tool:before` and\n // correctly stay out of the picker (there's nothing for the\n // user to cancel).\n // - the matching `tool:after` / `tool:error` / `tool:cancelled`\n // hooks drain the same entry, so the lifecycle is symmetric.\n registerInFlightTool({ callId, tool: name, startedAt: Date.now() })\n\n // Partial-approval already emitted a synthetic `tool` event with\n // the FULL hunks + outcomes from `gateDecision`; suppressing the\n // default emit here avoids painting a second (reduced-input) row\n // for the same call. The pending entry is cleared in\n // `tool:transform` once the result lands.\n if (pendingAnnotationsRef.current.has(callId))\n return\n // Snapshot the file's current bytes BEFORE the tool runs so the\n // diff renderer can paint real-file line numbers in the gutter\n // (via `buildContextualDiff`) and the compact view can anchor each\n // hunk summary to its actual position. Reading is awaited so the\n // snapshot is guaranteed to be the pre-write state — the cost is\n // one extra fs read per edit call (cheap on local disk; matches\n // what the approval modal already does for previews).\n let priorContent: string | undefined\n if (EDIT_TOOL_NAMES.has(name) && agent.handle && typeof input.path === 'string') {\n try {\n priorContent = await agent.execution.readFile(agent.handle, input.path)\n }\n catch {\n // File doesn't exist (fresh create for `write_file`, or\n // model-side mistake for `edit` / `multi_edit`) — fall through\n // with `priorContent: undefined`. For `write_file`,\n // extractEditPayload normalizes to `oldString: ''` so the diff\n // renders as all-add; for the others the snippet diff path\n // takes over and synthetic line numbers stand in.\n }\n }\n const edit = extractEditPayload(name, input, priorContent)\n stream.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(name, input),\n tool: name,\n input,\n ...(edit ? { edit } : {}),\n callId,\n turnId,\n })\n })\n agent.hooks.hook('tool:after', ({ callId, name, result, turnId }) => {\n // Drain the in-flight registry on successful / errored completion.\n // Symmetric with `tool:before`'s register — the cancel picker only\n // shows truly-live calls.\n unregisterInFlightTool(callId)\n\n // Spawn tool-results carry a `Tokens: …` summary that's already shown\n // by the spawn-end marker right above the tool-result — strip it from\n // the displayed string to avoid the duplicate / format-drift block.\n const raw = toolResultText(result)\n const text = name === 'spawn' ? stripSpawnTokensLine(raw) : raw\n stream.appendImmediate({ kind: 'tool-result', text, tool: name, callId, turnId })\n })\n agent.hooks.hook('tool:error', ({ callId }) => {\n // The loop fires `tool:error` BEFORE `tool:after` only on the\n // execute-branch error path — `tool:after` still fires with the\n // synthesized error result and would also drain the registry. We\n // drain here too so the registry can't survive a pathological\n // hook setup where `tool:after` doesn't fire (e.g. a consumer's\n // own `tool:error` throws); the second `unregisterInFlightTool`\n // in `tool:after` is then a no-op (Map.delete on a missing key).\n unregisterInFlightTool(callId)\n })\n agent.hooks.hook('tool:cancelled', ({ callId }) => {\n // User-issued per-call cancel: the loop short-circuited `tool:after`\n // entirely, so this hook is the only opportunity to drain.\n unregisterInFlightTool(callId)\n })\n\n // Background-task lifecycle — LIVE banner rendering.\n //\n // The framework injects a `<task-notification>` text block onto the\n // next user-turn (see `pendingTaskNotifications` in `src/agent.ts`),\n // which `eventsFromTurns` synthesizes back into a `task-notification`\n // StreamEvent on REPLAY. But during a LIVE session the user-turn\n // is built when they submit a prompt — they'd never see the\n // completion until they typed something. That defeats the whole\n // value prop of backgrounding (\"model goes back to work, user sees\n // task done when it lands\").\n //\n // So we mirror the replay path: when `background:exit` fires,\n // synthesize the SAME StreamEvent shape and push it into the\n // stream immediately. The subsequent prompt's notification block\n // still hits the wire (model side), and `eventsFromTurns` still\n // produces the banner on replay — but we don't double-paint\n // because the live banner doesn't carry a `turnId` (no committed\n // user turn yet) while the replay banner does. The two coexist\n // cleanly: live shows the user, replay shows the wire-equivalent.\n // Background-task lifecycle wiring.\n //\n // Two registries, two event streams, three event sources:\n // - `backgroundTasks` state — feeds the `ctrl+k` picker. Pushed on\n // start, dropped on exit. `kind: 'task'` is the cancel-modal\n // discriminator (routes to `agent.killBackgroundTask` instead\n // of `agent.cancelTool`).\n // - `stream` — feeds the transcript. `background:exit` emits a\n // `task-notification` event so the user sees the completion\n // LIVE; on session reload `eventsFromTurns` reconstructs the\n // same event from the persisted `<task-notification>` XML.\n //\n // Three sources of events: parent's direct `background:*` and\n // bubbled `child:background:*` (one per nesting level — the chain\n // bubbler forwards grandchildren too). All converge through the\n // same two helpers below.\n const registerBackgroundTask = (ctx: { taskId: string, command: string, startedAt: number, childId?: string }) => {\n setBackgroundTasks(prev => [\n ...prev,\n {\n kind: 'task' as const,\n callId: ctx.taskId,\n tool: `shell (background): ${previewLine(ctx.command, 60)}`,\n startedAt: ctx.startedAt,\n ...(ctx.childId ? { childId: ctx.childId } : {}),\n },\n ])\n }\n const dropBackgroundTaskAndEmitBanner = (ctx: {\n taskId: string\n status: 'exited' | 'killed'\n exitCode: number\n signal?: NodeJS.Signals\n outputPath: string\n durationMs: number\n command: string\n childId?: string\n depth?: number\n }) => {\n // Drain the ctrl+k entry — the task is no longer cancellable.\n setBackgroundTasks(prev => prev.filter(t => t.callId !== ctx.taskId))\n streamRef.current?.appendImmediate({\n kind: 'task-notification',\n text: formatTaskSummary(ctx),\n task: {\n taskId: ctx.taskId,\n status: ctx.status,\n exitCode: ctx.exitCode,\n outputPath: ctx.outputPath,\n command: ctx.command,\n durationMs: ctx.durationMs,\n },\n ...(ctx.childId ? { childId: ctx.childId } : {}),\n ...(typeof ctx.depth === 'number' ? { depth: ctx.depth } : {}),\n })\n }\n\n agent.hooks.hook('background:start', ctx => registerBackgroundTask(ctx))\n agent.hooks.hook('background:exit', ctx => dropBackgroundTaskAndEmitBanner(ctx))\n // Reassignment — the spawn tool promotes a subagent's still-running\n // tasks up to the parent's handle when the subagent finishes (see\n // `src/tools/spawn.ts`). Drop the `childId` tag on the matching\n // ctrl+k entry so the picker surfaces it as parent-level and\n // `agent.killBackgroundTask(taskId)` actually finds it. Without\n // this hook the entry would stay marked as a subagent's and be\n // filtered out of the picker even though the task is now reachable.\n agent.hooks.hook('background:reassign', (ctx) => {\n setBackgroundTasks(prev => prev.map(entry =>\n entry.callId === ctx.taskId\n ? { kind: 'task' as const, callId: entry.callId, tool: entry.tool, startedAt: entry.startedAt }\n : entry,\n ))\n })\n // Subagent background tasks bubble through with `childId` / `depth`\n // already attached. The ctrl+k picker filters child entries out\n // (the kill primitive walks the parent's map), but the\n // `background:reassign` listener above promotes them to parent-\n // level once the subagent finishes — so the user gets at them via\n // the picker the moment spawn.ts's finally block runs.\n agent.hooks.hook('child:background:start', ctx => registerBackgroundTask(ctx))\n agent.hooks.hook('child:background:exit', ctx => dropBackgroundTaskAndEmitBanner(ctx))\n\n // Skill activation tracking — drives:\n // 1. the footer's `✦ N skill(s)` chip (passive visibility), and\n // 2. the \"re-activate user-pinned skills on every run\" path in\n // `onSubmitPrompt` (see `userActivatedSkillsRef` below).\n //\n // The set must SURVIVE the framework's run-end deactivate-all, so we\n // filter by reason: `'run-end'` (framework-initiated) keeps the\n // chip's view stable across runs, while `'model'` / `'explicit'` /\n // `'reset'` (user / model intent) drain the entry.\n //\n // Without this filter, every successful run would silently undo the\n // user's \"I activated /read-only\" pin: run ends → set empties → next\n // prompt re-runs but `userActivatedSkillsRef` no longer knows to\n // pre-activate `read-only` → the gate is wide open. That's the\n // exact \"skill activated but bash ran\" bug the user reported.\n agent.hooks.hook('skills:activate', ({ skill }) => {\n setActiveSkillNames((prev) => {\n if (prev.has(skill.name))\n return prev\n const next = new Set(prev)\n next.add(skill.name)\n persistPinnedSkills(session, next)\n return next\n })\n })\n agent.hooks.hook('skills:deactivate', ({ skill, reason }) => {\n // `'run-end'` is the framework's per-run cleanup pass — purely\n // mechanical, no user intent. We keep the set populated so the\n // pre-run re-activation in `onSubmitPrompt` can restore them\n // on the next turn. Model-driven `'model'` deactivates and\n // host-driven `'explicit'` / `'reset'` deactivates remove the\n // entry (the user / model is asking for the pin to be released).\n if (reason === 'run-end')\n return\n setActiveSkillNames((prev) => {\n if (!prev.has(skill.name))\n return prev\n const next = new Set(prev)\n next.delete(skill.name)\n persistPinnedSkills(session, next)\n return next\n })\n })\n agent.hooks.hook('mcp:tool:after', ({ callId, displayName, result, turnId }) => {\n stream.appendImmediate({ kind: 'tool-result', text: toolResultText(result), tool: displayName, callId, turnId })\n })\n agent.hooks.hook('turn:after', ({ usage }) => {\n if (usage) {\n const tokens = turnContextSize(usage)\n // Skip zero-context placeholder turns — the loop synthesizes\n // an assistant turn with `usage: { input: 0, output: 0 }` when\n // streaming aborts/errors before any usage is reported. Letting\n // that overwrite the footer would flash `ctx 0` even though the\n // prior real turn's context is still what the next prompt rides\n // on top of. Same rationale as `lastContextSizeFromTurns`.\n if (tokens > 0) {\n setLastInputTokens(tokens)\n // Mirror in the ref so `onSubmitPrompt`'s post-run auto-compact\n // check reads the freshest value — React state hasn't committed\n // yet by the time `await agent.run()` returns to the caller.\n lastInputTokensRef.current = tokens\n }\n const turnCost = usage.cost ?? 0\n if (turnCost > 0)\n setSessionCost(prev => prev + turnCost)\n }\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n })\n\n // Subagent streams ----------------------------------------------------\n agent.hooks.hook('spawn:before', ({ id, task, depth }) => {\n // `previewLine` collapses inline newlines + tabs into single\n // spaces before truncating — without it, a task whose first\n // line is short but has trailing `\\n\\n …` content paints the\n // spawn-start marker across multiple visual rows and the next\n // event lands with leading whitespace that visually drifts to\n // the right. Same shape for the same reason for every other\n // preview path in this file.\n const taskPreview = previewLine(task, 80)\n stream.appendImmediate({\n kind: 'spawn-start',\n text: taskPreview,\n childId: id,\n depth: depth ?? 1,\n })\n })\n agent.hooks.hook('spawn:complete', ({ id, depth, status, stats }) => {\n const tag = status === 'aborted' ? 'aborted' : status === 'error' ? 'error' : 'done'\n stream.appendImmediate({\n kind: 'spawn-end',\n text: `${tag} ${formatTokenUsage(stats)}`,\n childId: id,\n depth: depth ?? 1,\n })\n })\n agent.hooks.hook('spawn:error', ({ id, depth, error }) => {\n stream.appendImmediate({\n kind: 'error',\n text: `[${id}] ${error.message}`,\n childId: id,\n depth: depth ?? 1,\n })\n })\n agent.hooks.hook('child:stream:thinking', ({ delta, childId, depth, turnId }) => {\n stream.queueStreamDelta('thinking', delta, { childId, depth, turnId })\n })\n agent.hooks.hook('child:stream:text', ({ delta, childId, depth, turnId }) => {\n stream.queueStreamDelta('markdown', delta, { childId, depth, turnId })\n })\n agent.hooks.hook('child:tool:before', ({ callId, name, input, childId, depth, turnId, priorContent }) => {\n // Subagent in-flight tracking mirrors the parent's path.\n // `childId` lets the picker label these \"· child-2\" so the user\n // knows which subagent the call belongs to.\n registerInFlightTool({ callId, tool: name, startedAt: Date.now(), childId })\n\n // Mirror of the parent's `tool:before` suppression — the gate\n // already emitted a synthetic `tool` event with the full hunks\n // + outcomes for a partial-approval call.\n if (pendingAnnotationsRef.current.has(callId))\n return\n // `priorContent` is populated by spawn.ts's `tool:before` enricher\n // when the child writes a file — it reads via the child's own\n // execution context (which may be a docker / sandbox handle the\n // parent can't reach), then mutates the bubbled ctx. For\n // `edit` / `multi_edit` the old content rides `input.old_string`\n // / `input.edits[]` directly and the enricher no-ops.\n const edit = extractEditPayload(name, input, priorContent)\n stream.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(name, input),\n tool: name,\n input,\n ...(edit ? { edit } : {}),\n callId,\n childId,\n depth,\n turnId,\n })\n })\n agent.hooks.hook('child:tool:after', ({ callId, name, result, childId, depth, turnId }) => {\n unregisterInFlightTool(callId)\n stream.appendImmediate({\n kind: 'tool-result',\n text: toolResultText(result),\n tool: name,\n callId,\n childId,\n depth,\n turnId,\n })\n })\n agent.hooks.hook('child:tool:error', ({ callId }) => {\n unregisterInFlightTool(callId)\n })\n agent.hooks.hook('child:tool:cancelled', ({ callId }) => {\n unregisterInFlightTool(callId)\n })\n agent.hooks.hook('child:stream:end', ({ childId }) => {\n // Finalize the child's trailing markdown block so the next child event\n // (tool call, spawn-end) doesn't keep appending into it.\n stream.flushAndUpdate(prev => finalizeStreamingMarkdownForOwner(prev, childId))\n })\n\n // Run-end sweep — clear any pending edit-outcome annotations whose\n // matching `tool:transform` never fired. Reachable paths that strand\n // an entry after a partial-approval gate:\n // - `validation:reject` returns from `executeSingleTool` before\n // `tool:transform` runs.\n // - A throwing `tool:before` / `tool:gate` handler unwinds via\n // `executeToolBatch`'s per-call onrejected branch, which\n // synthesizes an error tool_result without firing `tool:transform`.\n // Both are bounded by run lifetime; we sweep here so the map can't\n // accumulate across many runs on the same agent. New runs mint fresh\n // callIds, so wiping at run end never drops a still-needed entry.\n agent.hooks.hook('agent:done', () => {\n pendingAnnotationsRef.current.clear()\n })\n\n return agent\n }, [providerRegistry, stream, gateDecision, cancelRunOnDenial, projectDir, config.prefix, interactions, dataDir, mcpCredentialStore, registerInFlightTool, unregisterInFlightTool])\n\n // -------------------------------------------------------------------------\n // Session activation — load (or create) a Session, rebuild the agent, replay\n // turns into the transcript, and persist the resume hint.\n // -------------------------------------------------------------------------\n\n const refreshSessions = useCallback(async () => {\n // Scope the list to the current project unless the user has opted\n // into the cross-project view via `Settings.showAllProjects`. The\n // store filter does the heavy lifting (SQL `WHERE project_root =\n // ?`), so the cost is one round-trip even on a huge global DB.\n const filter = settings.showAllProjects ? undefined : { projectRoot: projectDir }\n const list = await listSessionMeta(store, filter)\n setSessions(list)\n return list\n }, [store, settings.showAllProjects, projectDir])\n\n const teardown = useCallback(async () => {\n // Abort the active run + drain the safe-mode queue *before* destroying\n // the agent. Without this:\n // - `agent.destroy()` is not an abort — it just closes MCP and the\n // execution handle, leaving any in-flight `run()` to keep firing\n // stream/tool hooks. Those write into the shared `stream` buffer,\n // so deltas from the doomed run land in the new session's\n // transcript after `activateSession` mounts it.\n // - Pending approvals (head-of-queue + tail) stay parked, so the\n // next prompt that opens the chat screen inherits them.\n //\n // Order matters: `denyAll()` first so any gate handler currently\n // awaiting `requestApproval(...)` resolves with `deny` and the loop\n // unwinds cleanly; `abort()` second so the abort signal interrupts the\n // next iteration; `destroy()` last to close handles after the loop\n // unwound. Each step is independently best-effort — exceptions are\n // logged and swallowed so a partial failure doesn't leak state.\n try {\n denyAll()\n }\n catch (err) {\n debugLog('teardown: denyAll failed', err)\n }\n try {\n // Same rationale as `denyAll()` above: any pending `present_plan` /\n // `ask_user` tool whose Promise is parked must unwind before\n // `agent.abort()` so the loop can finish unwinding cleanly.\n // Resumed-flow entries (no live Promise) are also drained — their\n // `cancel` is a no-op resolver on the App-side, so it's safe.\n interactions.cancelAll('session teardown')\n }\n catch (err) {\n debugLog('teardown: interactions.cancelAll failed', err)\n }\n try {\n agentRef.current?.abort()\n }\n catch (err) {\n debugLog('teardown: agent.abort failed', err)\n }\n stream.reset()\n await agentRef.current?.destroy().catch(err => debugLog('agent.destroy failed', err))\n agentRef.current = null\n sessionRef.current = null\n // Cancel any in-flight auto-compaction and drop the references so\n // the next session's first `onSubmitPrompt` doesn't gate itself on\n // the prior session's background compaction. Aborting the LLM call\n // also saves the wasted tokens that would otherwise be spent\n // summarising a session the user has navigated away from. The\n // compaction's own handlers check `sessionRef.current?.id` against\n // the captured sessionId and skip emitting transcript events for a\n // dead session, so this abort is purely about cost.\n autoCompactAbortRef.current?.abort()\n autoCompactAbortRef.current = null\n autoCompactInFlightRef.current = null\n setCompacting(false)\n // Drop the user-prompt queue and clear the running gate so the next\n // session's first submit re-enters the drain loop cleanly. Without\n // resetting `runningRef`, a teardown mid-drain would leave the gate\n // stuck and every subsequent submit would silently enqueue forever.\n // Also drop `queueSelectionIndex` — leaving stale selection state\n // would survive into the next session and put the user in a bogus\n // selection mode with no visible target.\n messageQueueRef.current = []\n setMessageQueue([])\n setQueueSelectionIndex(null)\n runningRef.current = false\n sessionSafelistRef.current.clear()\n // Belt-and-suspenders for the `agent:done` sweep registered in\n // `buildAgent`: a teardown mid-run (abort + destroy) races the\n // run's own `agent:done` emission — `destroy()` doesn't fire it,\n // and the aborted path inside `run()` may not have committed yet\n // when we get here. Wiping the map outright on session swap keeps\n // partial-approval residue from surviving into the next session.\n pendingAnnotationsRef.current.clear()\n // Drop the in-flight tools snapshot — a teardown mid-run leaves\n // entries the next session would otherwise inherit, and the cancel\n // picker would happily try to call `agent.cancelTool(callId)` on\n // an agent that no longer owns those calls.\n setInFlightTools([])\n // Same for background tasks. The execution context's `destroy()`\n // (inside `agent.destroy()`) SIGTERMs every still-running task,\n // so the previous agent's tasks are actually killed by the time\n // we reach this state-clear; this just clears the React mirror.\n setBackgroundTasks([])\n // Same rationale for the active-skills chip: the next session's\n // fresh agent will re-emit `skills:activate` events (resume\n // rehydration or model-driven), so we start empty.\n setActiveSkillNames(new Set<string>())\n }, [stream, denyAll, interactions])\n\n /**\n * Continue a session from where pending interactions left it. Appends\n * a single user turn containing one `tool_result` per pending request\n * (in pending order) and triggers a prompt-less `agent.run()` so the\n * model resumes.\n *\n * The single-turn / multi-`tool_result` shape is load-bearing: if the\n * model emitted parallel interaction calls (e.g. `present_plan` +\n * `ask_user` in the same assistant turn) the agent loop's history\n * contract requires every `tool_use` block to be matched by exactly\n * one `tool_result` in the very next user turn. Writing one user\n * turn per submission would persist a turn missing the sibling's\n * match and the next API call would reject the history — see\n * {@link buildResumedToolResultsTurn} for the protocol notes.\n */\n const continueResumedInteractions = useCallback(async (\n requests: readonly InteractionRequest[],\n responses: ReadonlyMap<string, InteractionResponse>,\n ) => {\n const session = sessionRef.current\n const agent = agentRef.current\n const currentPicked = pickedRef.current\n if (!session || !agent || !currentPicked) {\n debugLog('resume-interaction: missing session/agent/picked', { count: requests.length })\n return\n }\n const turnId = await session.generateTurnId()\n // The run id is shared across all requests in a batch (parallel\n // tool calls live in the same assistant turn → same `runId`). Pick\n // the first request's run id; missing ones fall back to `undefined`\n // which `buildResumedToolResultsTurn` silently drops.\n const runId = requests[0]?.runId\n let turn: SessionTurn\n try {\n turn = buildResumedToolResultsTurn(requests, responses, { turnId, runId })\n }\n catch (err) {\n // Defensive — `activateSession` only flushes when `responses` is\n // complete, so this path is unreachable. Surface in debug logs\n // so a future flush-too-early bug is loud.\n debugLog('resume-interaction: turn build failed', err)\n return\n }\n await session.appendTurns([turn])\n setEvents(eventsFromTurns(session.turns, session.runs))\n setBusy(true)\n try {\n await agent.run({\n model: currentPicked.model,\n ...(currentPicked.effort ? { thinking: currentPicked.effort } : {}),\n })\n await session.save().catch(err => debugLog('resume-interaction: session.save failed', err))\n setCurrentSession(prev => prev\n ? {\n ...prev,\n title: deriveSessionTitle(session.turns, session.metadata),\n turnCount: session.turns.length,\n userMessageCount: session.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n runCount: session.runs.length,\n updatedAt: Date.now(),\n }\n : prev)\n // Post-run auto-compaction check — same hook as `onSubmitPrompt`.\n // Interaction-resolution turns can grow the context just as much\n // as user-prompted turns (the model may execute an approved plan\n // that consumes thousands of tokens), so the trigger has to fire\n // here too or sessions that loop through `present_plan` /\n // `ask_user` would never auto-compact.\n triggerAutoCompactRef.current(session.id)\n }\n catch (err) {\n stream.appendImmediate({ kind: 'error', text: errorMessage(err) })\n }\n finally {\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n setBusy(false)\n }\n }, [stream])\n\n const activateSession = useCallback(async (id: string | null, key: ProviderKey) => {\n await teardown()\n\n const loaded = id ? await loadSession(store, id) : null\n // Stamp the current project root on FRESH sessions only. A loaded\n // session keeps whatever `projectRoot` it was created with — the\n // tag is sticky for the session's lifetime regardless of where\n // the user is when they reopen it.\n const session = loaded\n ?? await createSession({ store, projectRoot: projectDir, ...(id ? { id } : {}) })\n\n sessionRef.current = session\n agentRef.current = buildAgent(session, key)\n\n // Seed the pinned active-skills set from session metadata. Slash-\n // command activations (`/skill-name`) don't produce a `skills_use`\n // tool_call block — they go through `agent.activateSkill()` directly\n // — so the agent's session-resume rehydrator wouldn't restore them\n // on its own. The TUI persists the pinned set to\n // `metadata['zidane.activeSkills']` and reads it back here so a TUI\n // restart picks the user's pins right back up.\n //\n // The hook handlers in `buildAgent` mirror live mutations into the\n // same metadata key, so the persisted value stays in sync without\n // an explicit save step on every `/skill` keystroke. Falls back to\n // an empty Set when the field is missing or shaped wrong (older\n // sessions, hand-edited metadata) — never throws.\n const persistedPins = readPinnedSkills(session.metadata['zidane.activeSkills'])\n setActiveSkillNames(persistedPins)\n\n setEvents(eventsFromTurns(session.turns, session.runs))\n const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs)\n setLastInputTokens(replayedTokens)\n lastInputTokensRef.current = replayedTokens\n // New session activation — drop any prior compaction baseline so the\n // first compaction in this session fires off the absolute threshold.\n // We deliberately do NOT try to re-derive the baseline from persisted\n // turns: the marker turn doesn't record `effectiveTokens` and the\n // per-turn input usage at compaction time is lost to truncation. A\n // fresh hysteresis window after reload is the correct semantic.\n lastCompactedInputTokensRef.current = undefined\n setSessionCost(sumRunCosts(session.runs))\n setCurrentSession({\n id: session.id,\n title: deriveSessionTitle(session.turns, session.metadata),\n turnCount: session.turns.length,\n userMessageCount: session.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n runCount: session.runs.length,\n updatedAt: Date.now(),\n })\n setScreen('chat')\n stateStore.save({\n ...stateStore.load(),\n lastProvider: key,\n lastSessionId: session.id,\n })\n\n // Resumed-interaction enqueue. When a session was killed mid-prompt\n // (`present_plan` / `ask_user` awaiting a reply), the persisted\n // assistant turn carries one or more `tool_call`s without matching\n // `tool_result`s. We surface them in the same picker UI as a live\n // call; the resolver chain below collects each answer and flushes\n // a single batched user turn once they're all in. Graceful aborts\n // persist a `Tool error` result through the live tool path, so\n // they never re-enqueue here.\n //\n // The collected map is shared across every entry's resolver so\n // multi-call batches (the model emitted parallel interaction\n // tools) end up in ONE user turn — the agent loop's history\n // contract requires every `tool_use` to be paired with a\n // `tool_result` in the immediately-following user turn.\n const pending = pendingInteractionsFromTurns(session.turns)\n if (pending.length > 0) {\n const collected = new Map<string, InteractionResponse>()\n for (const request of pending) {\n const entry: PendingInteractionEntry = {\n request,\n resolve: (response) => {\n collected.set(request.id, response)\n if (collected.size >= pending.length)\n void continueResumedInteractions(pending, collected)\n },\n // `cancel` is intentionally a no-op: dropping the entry from\n // the queue (via `cancelAll` on teardown) leaves the\n // session's unresolved `tool_call` intact, so the next\n // activation re-enqueues it — the user can come back to the\n // question. A run-level abort, by contrast, has already\n // persisted a `Tool error` result through the live tool path\n // before this function ever runs.\n cancel: () => {},\n }\n interactions.enqueue(entry)\n }\n }\n }, [teardown, buildAgent, store, stateStore, projectDir, interactions, continueResumedInteractions])\n\n // -------------------------------------------------------------------------\n // Resume on launch — when a previous provider was remembered, jump straight\n // into the last session (or the sessions list if it's been deleted).\n // -------------------------------------------------------------------------\n\n // Narrow the deps to exactly what the effect reads. Depending on `config`\n // as a whole would re-fire if any future refactor made the resolved config\n // non-stable — and re-firing here re-runs `activateSession`, destroying and\n // rebuilding the agent in a loop (this was the original leak).\n useEffect(() => {\n if (!resumeProvider)\n return\n let cancelled = false\n void (async () => {\n // Resume-on-launch gate. Skips the `lastSessionId` lookup\n // entirely when the user has turned off `Start from last session`\n // — they then land on the sessions list (or a fresh session if\n // empty) regardless of what `state.json` remembers. We don't\n // clear `state.lastSessionId` on skip: flipping the setting\n // back on should restore the resume pointer without losing\n // wherever the user last was.\n if (resumeLastSessionOnLaunch && lastResumedSessionId) {\n const data = await store.load(lastResumedSessionId)\n if (cancelled)\n return\n // Project gate — only auto-resume sessions that belong to the\n // current project root (matching the same scope the sessions\n // list applies). Without this, a `lastSessionId` carried over\n // from another cwd would silently reopen here, while the list\n // filtered by the current project would render empty — a\n // confusing mismatch where the user lands inside a \"ghost\"\n // session they have no way to see in the sidebar.\n //\n // Opt-out via `Settings.showAllProjects`: when the user has\n // already chosen the cross-project view, resume follows the\n // same liberal rule the list uses.\n //\n // We deliberately do NOT clear `state.lastSessionId` on a\n // mismatch — when the user cd's back to the original project\n // the resume should re-engage. `activateSession` overwrites\n // `lastSessionId` with whatever they pick next anyway.\n const sessionMatchesProject = settings.showAllProjects\n || (data?.projectRoot != null && data.projectRoot === projectDir)\n if (data && sessionMatchesProject) {\n await activateSession(lastResumedSessionId, resumeProvider.key)\n bootTick(`resume:activate (sessionId=${lastResumedSessionId.slice(-8)})`)\n return\n }\n }\n const list = await refreshSessions()\n if (cancelled)\n return\n if (list.length === 0) {\n await activateSession(null, resumeProvider.key)\n bootTick('resume:fresh-session (empty list)')\n }\n else {\n setScreen('sessions')\n bootTick(`resume:sessions-list (${list.length})`)\n }\n })()\n return () => { cancelled = true }\n }, [activateSession, refreshSessions, resumeProvider, lastResumedSessionId, store, settings.showAllProjects, projectDir, resumeLastSessionOnLaunch])\n\n // -------------------------------------------------------------------------\n // Screen actions.\n // -------------------------------------------------------------------------\n\n // Available providers (with credentials configured) for the\n // cross-provider model picker. Seeded on mount + refreshed whenever\n // the user re-runs the auth wizard (via `onPickProvider`). Stored\n // as state so a credential added mid-session (e.g. setting a key\n // through the wizard while the chat is open) propagates to the\n // model picker on its next open.\n const [availableProviders, setAvailableProviders] = useState<readonly ProviderAuth[]>([])\n const refreshAvailableProviders = useCallback(() => {\n const detected = detectAuth(config.paths.userDir, providerRegistry)\n setAvailableProviders(detected.filter(p => p.available))\n }, [config.paths.userDir, providerRegistry])\n useEffect(() => {\n refreshAvailableProviders()\n }, [refreshAvailableProviders])\n\n const onPickProvider = useCallback(async (p: ProviderAuth) => {\n const next = makePicked(p)\n if (!next)\n return // provider isn't in the registry — guarded by AuthScreen, defensive here\n setPicked(next)\n stateStore.save({ ...stateStore.load(), lastProvider: p.key })\n // Re-detect — the wizard may have just added a credential for a\n // provider that wasn't authed before, and the model picker needs\n // to see it on its next open.\n refreshAvailableProviders()\n const list = await refreshSessions()\n if (list.length === 0)\n await activateSession(null, p.key)\n else\n setScreen('sessions')\n }, [refreshSessions, activateSession, makePicked, stateStore, refreshAvailableProviders])\n\n const onCreateSession = useCallback(async () => {\n if (picked)\n await activateSession(null, picked.provider.key)\n }, [picked, activateSession])\n\n const onSwitchSession = useCallback(async (id: string) => {\n if (picked)\n await activateSession(id, picked.provider.key)\n }, [picked, activateSession])\n\n const onOpenSessions = useCallback(async () => {\n await refreshSessions()\n setScreen('sessions')\n }, [refreshSessions])\n\n // `popupOpenRef` tracks the completion popup's visibility — set by\n // `ChatScreen`'s `onPopupOpenChange` callback. The esc handler short-\n // circuits when the popup is open so esc dismisses the popover instead\n // of aborting the run / cancelling pending approvals. Stored in a ref\n // (not state) so the abort callback stays referentially stable.\n const popupOpenRef = useRef(false)\n\n // Memoize the callback so `<ChatScreen onPopupOpenChange>` keeps a stable\n // identity across `setEvents` flushes. Without this, the inline arrow\n // recreated on every render lands in `PromptBlock`'s `useEffect` dep\n // array, re-firing the effect on every streaming delta and adding work\n // to each paint frame.\n const onPopupOpenChange = useCallback((open: boolean) => {\n popupOpenRef.current = open\n }, [])\n\n const onAbort = useCallback(() => {\n if (popupOpenRef.current)\n return // popup will consume esc via PromptBlock's onKeyDown\n // Flush every awaiter so the run can unwind cleanly; otherwise\n // `agent.abort()` only flips the run's signal and our gate /\n // interaction handlers would still hang in `await requestApproval(...)`\n // / `await requestInteraction(...)`. Esc == \"stop everything\", same rule\n // the approval queue follows; otherwise the drain loop would happily\n // start the next queued message right after the abort unwinds.\n cancelRunOnDenial('user aborted run')\n }, [cancelRunOnDenial])\n\n // Plan rejection aborts the run after the response is forwarded — the\n // model still records the `{ decision: 'reject', … }` tool_result, then\n // the loop unwinds. `revise` and `approve` continue to flow back unchanged.\n const onInteractionResolve = useCallback((response: InteractionResponse) => {\n interactions.resolveHead(response)\n if (response.kind === 'plan' && response.decision === 'reject')\n cancelRunOnDenial('user rejected a plan')\n }, [interactions, cancelRunOnDenial])\n\n // -----------------------------------------------------------------------\n // Message-queue navigation\n //\n // Selection state lives outside the queue box because the keyboard\n // shortcuts (`ctrl+up` enter, `ctrl+return` push, `delete` drop — all\n // bound through `keybindings.json` so a user can swap them) ride the\n // global `useKeyboard` listener and gate other shortcuts (esc abort)\n // on the selection being null. Centralizing it here keeps the\n // ChatScreen / QueuedMessagesBlock as render-only.\n // -----------------------------------------------------------------------\n\n const enterQueueSelection = useCallback(() => {\n if (messageQueueRef.current.length === 0)\n return\n // Land on the most-recently-added entry (the bottom of the stacked\n // list) — that's the one the user has freshest mental contact with,\n // and `↑` reads naturally as \"step up from the prompt\".\n setQueueSelectionIndex(messageQueueRef.current.length - 1)\n }, [])\n\n /**\n * Try to enter queue selection on an empty prompt buffer. Wired into\n * `PromptBlock`'s onKeyDown for `↑` at row 0 so the user moves\n * naturally from \"typing nothing\" into the queue box without needing\n * a dedicated shortcut. Returns `true` when selection was entered so\n * the caller can short-circuit history cycling on the same keypress.\n *\n * Falls back to the regular history-cycle path on:\n * - non-empty buffer (the user might be re-editing a draft and `↑`\n * should still pull a past prompt down for them to modify), or\n * - empty queue (nothing to step into).\n */\n const enterQueueSelectionFromEmptyPrompt = useCallback((): boolean => {\n if (messageQueueRef.current.length === 0)\n return false\n setQueueSelectionIndex(messageQueueRef.current.length - 1)\n return true\n }, [])\n\n const exitQueueSelection = useCallback(() => {\n setQueueSelectionIndex(null)\n }, [])\n\n /**\n * Move the queue cursor by `delta`. `↑` stops at the first entry\n * (clamps); `↓` past the last entry exits back to the prompt — the\n * mirror of \"↑ from the empty prompt enters the queue at the last\n * entry\", so navigation feels continuous across the queue ↔ prompt\n * boundary.\n */\n const moveQueueSelection = useCallback((delta: -1 | 1) => {\n setQueueSelectionIndex((prev) => {\n if (prev == null)\n return prev\n const len = messageQueueRef.current.length\n if (len === 0)\n return null\n const next = prev + delta\n if (next < 0)\n return 0\n if (next >= len)\n return null // step off the bottom → back to the prompt\n return next\n })\n }, [])\n\n const dropSelectedQueuedMessage = useCallback(() => {\n const idx = queueSelectionIndexRef.current\n if (idx == null)\n return\n const queue = messageQueueRef.current\n if (queue.length === 0) {\n setQueueSelectionIndex(null)\n return\n }\n const dropIdx = Math.min(idx, queue.length - 1)\n messageQueueRef.current = queue.filter((_, i) => i !== dropIdx)\n setMessageQueue(messageQueueRef.current.slice())\n // Empty queue → bail back to the prompt. Otherwise stay on the\n // entry that slid up into the dropped slot (or the new last entry\n // when we just removed the tail).\n if (messageQueueRef.current.length === 0)\n setQueueSelectionIndex(null)\n else\n setQueueSelectionIndex(Math.min(dropIdx, messageQueueRef.current.length - 1))\n }, [])\n\n /**\n * Push the selected queued message onto the live run's steering queue —\n * `agent.steer()` delivers it between tool calls in the agent loop, so\n * the model sees it as the next user input once the current turn\n * completes. Skill chips ride the same `activateSkill` path the regular\n * submit uses; the message text itself is what reaches the model.\n *\n * Best-effort: when no run is in flight (rare — the queue is only\n * populated while running) we silently exit selection. Adding the\n * message back to the queue on failure would be more confusing than\n * useful.\n */\n const pushSelectedQueuedMessage = useCallback(() => {\n const agent = agentRef.current\n if (!agent)\n return\n const idx = queueSelectionIndexRef.current\n if (idx == null)\n return\n const queue = messageQueueRef.current\n if (queue.length === 0) {\n setQueueSelectionIndex(null)\n return\n }\n const pushIdx = Math.min(idx, queue.length - 1)\n const entry = queue[pushIdx]\n messageQueueRef.current = queue.filter((_, i) => i !== pushIdx)\n setMessageQueue(messageQueueRef.current.slice())\n // Activate referenced skills BEFORE steering so the catalog has\n // the skill body in the system prompt by the time the model gets\n // to read the steered message. Best-effort — same `debugLog`\n // path as the regular submit; failures don't block the steer.\n const skillNames = uniqueSkillNamesFromReferences(entry.references)\n for (const name of skillNames) {\n agent.activateSkill(name).catch(err => debugLog(`activateSkill(\"${name}\")`, err))\n }\n agent.steer(entry.prompt)\n if (messageQueueRef.current.length === 0)\n setQueueSelectionIndex(null)\n else\n setQueueSelectionIndex(Math.min(pushIdx, messageQueueRef.current.length - 1))\n }, [])\n\n /**\n * Pick a `{ providerKey, modelId }` tuple from the cross-provider\n * model picker. Two branches:\n *\n * - **Same provider, different model** — update `picked.model` +\n * remember it in `lastModelByProvider`. The active agent keeps\n * running; `agent.run({ model })` accepts the model per-call so\n * no session rebuild is needed.\n * - **Different provider** — swap the active `ProviderAuth`,\n * persist BOTH `lastProvider` + `lastModelByProvider`, then\n * re-activate the current session against the new provider's\n * factory. The session id (and conversation history) is\n * preserved; only the bound agent + provider instance change.\n *\n * Either branch re-resolves the reasoning effort: a non-reasoning\n * model loses its `effort`, a reasoning model gets the remembered\n * per-model value (falling back to a sensible default).\n */\n const onPickModel = useCallback(async (next: PickedModel) => {\n const nextProvider = availableProviders.find(p => p.key === next.providerKey)\n if (!nextProvider) {\n // Catalog was built from `availableProviders`, so a stale row\n // should be unreachable — guard anyway in case auth detection\n // races with the picker (provider revoked credentials between\n // catalog assembly and commit).\n debugLog('onPickModel: unknown provider key', next.providerKey)\n modal.close()\n return\n }\n const descriptor = providerRegistry[nextProvider.key]\n const prior = stateStore.load()\n const providerChanged = picked?.provider.key !== nextProvider.key\n stateStore.save({\n ...prior,\n ...(providerChanged ? { lastProvider: nextProvider.key } : {}),\n lastModelByProvider: { ...prior.lastModelByProvider, [nextProvider.key]: next.modelId },\n })\n const nextEffort = descriptor\n ? effortForModel(descriptor, next.modelId, prior.lastEffortByModel)\n : undefined\n const updated: Picked = nextEffort\n ? { provider: nextProvider, model: next.modelId, effort: nextEffort }\n : { provider: nextProvider, model: next.modelId }\n setPicked(updated)\n modal.close()\n // Re-activate the current session against the new provider so the\n // next agent.run is bound to the right factory. Skipped on\n // same-provider picks (per-run model swap is handled inside\n // `agent.run`) and when there's no active session (we'd have\n // nothing to re-bind anyway).\n if (providerChanged && currentSession && !busy && !pendingApproval)\n await activateSession(currentSession.id, nextProvider.key)\n }, [\n availableProviders,\n providerRegistry,\n stateStore,\n modal,\n picked,\n currentSession,\n busy,\n pendingApproval,\n activateSession,\n ])\n\n const onPickEffort = useCallback((effort: ThinkingLevel) => {\n setPicked((prev) => {\n if (!prev)\n return prev\n const prior = stateStore.load()\n stateStore.save({\n ...prior,\n lastEffortByModel: { ...prior.lastEffortByModel, [prev.model]: effort },\n })\n return { ...prev, effort }\n })\n modal.close()\n }, [modal, stateStore])\n\n // Reasoning support is a property of the active model — derived here so\n // the bottom-bar hint and the ctrl+n gate read from a single source.\n // Declared above the keyboard handler so its closure sees the value.\n const modelHasReasoning = useMemo(() => {\n if (!picked)\n return false\n const descriptor = providerRegistry[picked.provider.key]\n return !!descriptor && modelSupportsReasoning(descriptor, picked.model)\n }, [picked, providerRegistry])\n\n // Agent profile switch. The ref is written synchronously so a follow-up\n // `activateSession` call in the same tick rebuilds against the new\n // preset; the state update keeps the footer badge + picker re-render in\n // sync. When the agent change happens mid-session we re-activate the\n // active session, which destroys the old agent and creates a fresh one\n // bound to the new profile (`buildAgent` reads the ref).\n const onPickAgent = useCallback(async (id: string) => {\n const profile = agentRegistry[id]\n if (!profile)\n return // unknown id — picker already filters, this is a defensive guard\n pickedAgentRef.current = profile\n setPickedAgent(profile)\n stateStore.save({ ...stateStore.load(), lastAgent: id })\n modal.close()\n // Rebuild the active agent so the new system prompt + tool set take\n // effect on the *next* turn. Conversation history is preserved (we\n // load the same session id); only the live agent's bindings change.\n if (picked && currentSession && !busy)\n await activateSession(currentSession.id, picked.provider.key)\n }, [agentRegistry, picked, currentSession, busy, activateSession, stateStore, modal])\n\n // Shift+Tab — cycle to the next registered profile, wrapping. Insertion\n // order in `agentRegistry` is the cycle order (matches the picker), so\n // pressing the key twice in a 2-profile registry returns to the original\n // — the same toggle muscle memory other agentic TUIs train. Reads the\n // ref directly so the lookup always sees the latest profile even if\n // state hasn't flushed yet between rapid presses.\n const onCycleAgent = useCallback(async () => {\n const ids = Object.keys(agentRegistry)\n if (ids.length <= 1)\n return\n const currentIdx = ids.indexOf(pickedAgentRef.current.id)\n const nextId = ids[(currentIdx + 1) % ids.length]\n await onPickAgent(nextId)\n }, [agentRegistry, onPickAgent])\n\n // `events.length` is read through a ref so the callback's identity stays\n // stable across delta-by-delta state updates (events grow on every\n // streamed markdown token). Without the ref the callback rebinds every\n // delta, which cascades into `ChatScreen` → `PromptBlock` → the textarea's\n // `onKeyDown` rebinding, defeating any future memoization downstream.\n const eventsLengthRef = useRef(0)\n eventsLengthRef.current = events.length\n\n /**\n * Run a single prompt end-to-end. Echoes the user-prompt event to the\n * transcript synchronously (BEFORE awaiting compaction / skill\n * activation) so the message lands in the conversation at the moment\n * the drain loop decides to run it — that's the cue for the\n * \"queued → user message\" transition the queue box above the prompt\n * reflects visually.\n *\n * Intentionally does NOT toggle `busy` — the drain loop owns the\n * busy-state lifecycle so the title spinner stays mounted continuously\n * across back-to-back queued messages.\n */\n const runSingleMessage = useCallback(async (prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => {\n const agent = agentRef.current\n const session = sessionRef.current\n if (!agent || !session || !picked)\n return\n\n // Echo the prompt into the transcript right as we commit to running\n // it. Queued messages live in the queue box ABOVE the prompt (see\n // {@link messageQueue}); calling the echo here is what visually\n // \"pushes\" them out of that box and into the conversation.\n if (eventsLengthRef.current > 0)\n stream.appendImmediate({ kind: 'separator', text: '' })\n const refSpans = references\n .filter(r => r.start >= 0 && r.end > r.start)\n .map(r => ({ start: r.start, end: r.end, providerId: r.providerId }))\n const attachmentMeta = attachments.map(a => ({\n name: a.name,\n mediaType: a.mediaType,\n size: a.content.length,\n }))\n stream.appendImmediate({\n kind: 'user-prompt',\n text: prompt,\n ...(refSpans.length > 0 ? { refs: refSpans } : {}),\n ...(attachmentMeta.length > 0 ? { attachments: attachmentMeta } : {}),\n })\n\n // Wait for any background auto-compaction to land BEFORE we run.\n // The user's submit IS allowed during compaction (they can type\n // freely), but the resulting `agent.run()` has to start against\n // the post-compaction history — otherwise we'd race the\n // summary's `appendTurns` and produce a corrupt message order.\n // The catch is a no-op because the compaction's own error path\n // already surfaced a transcript event; we just don't want a\n // rejected compaction to kill the user's prompt.\n if (autoCompactInFlightRef.current)\n await autoCompactInFlightRef.current.catch(() => {})\n\n // Activate every skill referenced via slash-commands AND every skill\n // the user has previously pinned in this session, BEFORE the run.\n //\n // Why the pinned set: the framework's run-end deactivate-all wipes\n // the activation state at every run boundary (see `deactivateAllSkills`\n // in `src/agent.ts`). Without re-activating here, a user typing\n // `/read-only` once would see the skill take effect for that one run\n // and then silently lose its `allowed-tools` enforcement on the next\n // prompt — exactly the \"skill activated but bash ran\" bug. The pinned\n // set survives `'run-end'` deactivates (see the `skills:deactivate`\n // hook in `buildAgent`) and is drained on `'model'` / `'explicit'`\n // / `'reset'` deactivates so a deliberate \"release this pin\" call\n // sticks.\n //\n // Idempotent — `activateSkill` is safe on already-active skills,\n // auto-resolves the catalog on first call, and de-dups via the\n // union below.\n const newRefs = uniqueSkillNamesFromReferences(references)\n const skillNames = Array.from(new Set([...activeSkillNamesRef.current, ...newRefs]))\n for (const name of skillNames) {\n try {\n await agent.activateSkill(name)\n }\n catch (err) {\n debugLog(`activateSkill(\"${name}\")`, err)\n }\n }\n\n try {\n // Build a multimodal PromptPart[] iff there are attachments; the\n // plain-string fast path stays for the typing-only case so the\n // existing single-text-part wire shape is preserved bit-for-bit.\n const runPrompt: string | PromptPart[] = attachments.length === 0\n ? prompt\n : [\n ...(prompt.length > 0 ? [{ type: 'text' as const, text: prompt }] : []),\n ...attachments.map<PromptPart>((att) => {\n if (att.mediaType.startsWith('image/')) {\n return {\n type: 'image',\n mediaType: att.mediaType,\n data: att.content.toString('base64'),\n name: att.name,\n }\n }\n // Treat text/* as inline text documents; everything else\n // gets base64-encoded as an opaque document blob.\n if (att.mediaType.startsWith('text/')) {\n return {\n type: 'document',\n mediaType: att.mediaType,\n data: att.content.toString('utf-8'),\n encoding: 'text',\n name: att.name,\n }\n }\n return {\n type: 'document',\n mediaType: att.mediaType,\n data: att.content.toString('base64'),\n encoding: 'base64',\n name: att.name,\n }\n }),\n ]\n await agent.run({\n model: picked.model,\n prompt: runPrompt,\n ...(picked.effort ? { thinking: picked.effort } : {}),\n })\n await session.save().catch(err => debugLog('session.save failed', err))\n setCurrentSession(prev => prev\n ? {\n ...prev,\n title: deriveSessionTitle(session.turns, session.metadata),\n turnCount: session.turns.length,\n userMessageCount: session.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n runCount: session.runs.length,\n updatedAt: Date.now(),\n }\n : prev)\n // Post-run auto-compaction check. Fire-and-forget — the launcher\n // runs in the background, the prompt area stays interactive, and\n // the next `runSingleMessage` invocation awaits the resulting\n // promise via `autoCompactInFlightRef`. We don't auto-continue\n // the conversation; the user controls the next turn.\n triggerAutoCompactRef.current(session.id)\n }\n catch (err) {\n stream.appendImmediate({ kind: 'error', text: errorMessage(err) })\n }\n finally {\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n }\n }, [picked, stream])\n\n const onSubmitPrompt = useCallback((prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => {\n // Allow submit with empty text when attachments are present\n // (e.g. drag-an-image-in-and-press-enter); the typed-only path\n // still requires non-blank text.\n if (!prompt.trim() && attachments.length === 0)\n return\n const agent = agentRef.current\n const session = sessionRef.current\n if (!agent || !session || !picked)\n return\n\n // Already running — park the prompt in the queue box. The active\n // drain loop picks it up after the current message lands; the box\n // shows the user a stacked preview of \"what's waiting\".\n if (runningRef.current) {\n messageQueueRef.current = [...messageQueueRef.current, { prompt, references, attachments }]\n setMessageQueue(messageQueueRef.current.slice())\n return\n }\n\n // Idle — run this message immediately (skipping the queue box\n // entirely so the user never sees their freshly-typed prompt\n // appear \"queued\" for a frame before running), then drain anything\n // submitted while this run is in flight.\n runningRef.current = true\n void (async () => {\n setBusy(true)\n try {\n await runSingleMessage(prompt, references, attachments)\n while (messageQueueRef.current.length > 0) {\n const next = messageQueueRef.current[0]\n messageQueueRef.current = messageQueueRef.current.slice(1)\n setMessageQueue(messageQueueRef.current.slice())\n // Keep the user's queue cursor glued to the same MESSAGE while\n // the drain shrinks the list under their feet. The head we\n // just popped is now in the transcript, so:\n // - selection on the popped row (index 0) → exit selection\n // (the row no longer exists)\n // - selection further down → shift left by one so it still\n // points at the same entry now sitting one slot higher\n // - empty queue → exit selection (the box is about to\n // unmount anyway)\n setQueueSelectionIndex((prev) => {\n if (prev == null)\n return prev\n if (messageQueueRef.current.length === 0)\n return null\n if (prev <= 0)\n return null\n return prev - 1\n })\n await runSingleMessage(next.prompt, next.references, next.attachments)\n }\n }\n finally {\n setBusy(false)\n runningRef.current = false\n }\n })()\n }, [picked, runSingleMessage])\n\n // -------------------------------------------------------------------------\n // Top-level keyboard. Each screen handles its own primary action; this only\n // routes esc + the modal shortcuts.\n //\n // The callback is recreated on every render, but OpenTUI's `useKeyboard`\n // wraps it with `useEffectEvent` internally — registration only happens once\n // and the handler always sees the latest closure. No leak.\n // -------------------------------------------------------------------------\n\n // `pendingApproval` is derived at the top of `AppShell` (just below\n // `queue`) so every callback that gates on \"is the user mid-approval?\"\n // (model picker swap, ctrl+x details, the keyboard handler itself,\n // the footer hints) can depend on it without temporal-deadzone\n // contortions. The reference is the same value the keyboard handler\n // captures below.\n\n // Settings → \"Re-configure providers\" jumps back to the auth screen. We\n // capture this as a callback so the modal closes cleanly before the screen\n // transition (avoiding a brief flash of the modal over the new screen).\n //\n // Switching providers mid-stream would have the old agent keep firing\n // stream/tool hooks into the shared buffer after the new session mounts,\n // leaking deltas + approvals across sessions. We gate the wiring itself\n // (returning `undefined` while busy/pending) so the Settings modal omits\n // the row at build time — no silent no-op, no dead row in the picker.\n // The modal re-renders when `busy` flips and the row re-appears.\n const onReauth = useMemo(() => {\n if (busy || pendingApproval)\n return undefined\n return () => {\n modal.close()\n setScreen('auth')\n }\n }, [modal, busy, pendingApproval])\n\n // Per-server abort controllers for in-flight OAuth flows. Modal-driven\n // cancel (`esc` on an `authorizing` row) hits .abort() here; the\n // loginMcpServer flow rejects and the agent fires `mcp:auth:error`\n // which folds back into the auth state via the reducer.\n const mcpLoginAbortsRef = useRef(new Map<string, AbortController>())\n\n // Refs for the rebuild-on-login path. The rebuild closure runs much later\n // than its capture so reads must go through refs to see the latest values.\n // `pickedRef` is reused from upthread; only `buildAgentRef` is new here.\n const buildAgentRef = useRef(buildAgent)\n buildAgentRef.current = buildAgent\n\n /**\n * Rebuild the agent for the active session — used post-login so the\n * newly-authenticated MCP server actually connects. The current agent's\n * MCP connection is closed (which releases any other server connections\n * too); the fresh agent re-bootstraps lazily on its next run / warmup\n * with the now-populated credential store.\n *\n * Skipped silently when there's no active session / picked provider —\n * an early-startup login would race the agent's existence otherwise.\n * The bootstrap event handlers below pick up the new connection's\n * status without needing a manual refresh from this caller.\n */\n const rebuildActiveAgent = useCallback(async () => {\n const session = sessionRef.current\n const p = pickedRef.current\n if (!session || !p)\n return\n const fresh = buildAgentRef.current(session, p.provider.key)\n const stale = agentRef.current\n agentRef.current = fresh\n // Tear down the previous agent only after the new one is wired so\n // any in-flight UI state pointed at agentRef sees a valid agent at\n // every microtask. MCP close errors are debug-only — a stale\n // connection that won't close cleanly should not block the swap.\n if (stale)\n await stale.destroy().catch(err => debugLog('rebuildActiveAgent: destroy failed', err))\n }, [])\n\n const onLoginMcp = useCallback(async (name: string) => {\n const entry = mcpsCatalogRef.current.find(d => d.config.name === name)\n if (!entry)\n return\n\n // Replace any prior in-flight login for the same server (rare: the\n // modal disables `l` while authorizing, but a programmatic re-entry\n // shouldn't leak the older controller).\n mcpLoginAbortsRef.current.get(name)?.abort()\n const ac = new AbortController()\n mcpLoginAbortsRef.current.set(name, ac)\n\n dispatchAuth({ type: 'login:start', name })\n try {\n await loginMcpServer(entry.config, {\n store: mcpCredentialStore,\n signal: ac.signal,\n // Pipe through the live agent's hooks so `mcp:auth:url` /\n // `mcp:auth:success` / `mcp:auth:error` flow through the same\n // listeners the bootstrap path uses — single source of truth for\n // status state. Undefined-safe: when there's no agent yet, the\n // flow still completes via direct dispatch above + reducer below.\n hooks: agentRef.current?.hooks,\n // Auto-open the user's browser as soon as the SDK builds the\n // authorization URL. Best-effort: if no GUI is available\n // (`open`/`xdg-open` missing or restricted by the sandbox), the\n // user can still click / copy the URL from the modal's\n // authorizing panel — the loopback callback server is listening\n // either way.\n onAuthorizationUrl: url => tryOpenBrowser(url.toString()),\n })\n // Tokens persisted. Rebuild the agent so the next run picks them up.\n // Don't await — agent destroy can take a beat (MCP close) and the\n // user's already done with the modal at this point.\n void rebuildActiveAgent()\n }\n catch (err) {\n // `mcp:auth:error` hook already dispatched the status on the agent\n // bus. If no agent was wired, fall back to a direct dispatch.\n if (!agentRef.current)\n dispatchAuth({ type: 'auth-error', name, error: errorMessage(err) })\n }\n finally {\n mcpLoginAbortsRef.current.delete(name)\n }\n }, [dispatchAuth, mcpCredentialStore, rebuildActiveAgent])\n\n const onLogoutMcp = useCallback((name: string) => {\n mcpCredentialStore.delete(name)\n // For OAuth-tagged servers, drop back to `needs-auth` rather than `idle`.\n // `idle` is \"we know nothing about this server's auth needs\" — but we\n // DO know: the config says `auth: 'oauth'`, the store is empty,\n // therefore the next bootstrap will need login. Without this branch\n // the `l` (login) action would disappear until the user restarted the\n // CLI and the proactive seed re-ran on discovery.\n const entry = mcpsCatalogRef.current.find(d => d.config.name === name)\n dispatchAuth(\n entry?.config.auth === 'oauth'\n ? { type: 'auth-required', name, reason: 'no-tokens' }\n : { type: 'logout', name },\n )\n // Rebuild so the next agent bootstrap re-emits `mcp:auth:required`\n // (the reducer is now in sync, but the AGENT's MCP connection still\n // holds Linear authenticated until we rebuild).\n void rebuildActiveAgent()\n }, [dispatchAuth, mcpCredentialStore, rebuildActiveAgent])\n\n const onCancelLoginMcp = useCallback((name: string) => {\n mcpLoginAbortsRef.current.get(name)?.abort()\n }, [])\n\n // Settings → Open keybindings file. Creates the file with defaults\n // when missing (per `ensureKeybindingsFile`'s idempotent contract),\n // then launches `$EDITOR` if set, falling back to the platform's\n // default opener (`open` on macOS, `xdg-open` on Linux, `start` on\n // Windows). Closes the modal first so the editor's window doesn't\n // visually launch behind the settings overlay.\n //\n // Changes to the file take effect on the NEXT TUI launch — we read\n // keybindings during `resolveConfig`, before AppShell mounts. We\n // intentionally don't hot-reload: the global keyboard handler would\n // have to teardown + re-register, which complicates the in-flight\n // run gates. Restart is a small price and matches how the same\n // shortcuts elsewhere in the app (`Start from last session`, theme\n // changes are hot, this one isn't) are documented.\n const onOpenKeybindingsFile = useCallback(() => {\n modal.close()\n void (async () => {\n try {\n const path = ensureKeybindingsFile(config.paths.userDir)\n await launchEditor(path)\n }\n catch (err) {\n debugLog('open keybindings file failed', err)\n }\n })()\n }, [modal, config.paths.userDir])\n\n // `onRefreshSkills` / `onRefreshFiles` / `onRefreshMcps` come from\n // <DiscoveryShell> via `useDiscovery()` above. They're listed here\n // in passing so future maintainers don't try to redefine them in\n // AppShell — they're load-bearing because the modal layer reads\n // them through the same context that propagates fresh catalogs.\n\n // Drives both the `ctrl+a` shortcut wiring and the hint visibility — a\n // single-profile registry hides the affordance entirely so it doesn't\n // suggest a picker that has no choices to offer. Defined before\n // `useKeyboard` so the closure reads a stable, hoisted-friendly binding.\n const hasMultipleAgents = useMemo(\n () => Object.keys(agentRegistry).length > 1,\n [agentRegistry],\n )\n\n // -------------------------------------------------------------------------\n // Select-turn mode.\n //\n // Source of truth: every `StreamEvent` carries the `turnId` of its source\n // SessionTurn (see `eventsFromTurns` + the live hooks in `buildAgent`).\n // `selectableTurnIds` derives the navigation index — deduplicated, in\n // render order, parent-conversation only (subagent turns are nested\n // execution and would highlight nothing under the default\n // `hideSubagentOutput: true`).\n // -------------------------------------------------------------------------\n\n // `settings` is threaded in so the navigation index drops turns whose\n // every event is filtered out by user toggles (e.g. an assistant turn\n // that's pure `tool` events when `showToolCalls: false`). Without this\n // the cursor can land on a row that paints nothing and appears to\n // vanish. `selectableTurnIds` also coalesces result-only user turns\n // into their owning assistant turn — see `turnSelectionOwnership`.\n const turnIds = useMemo(() => selectableTurnIds(events, settings), [events, settings])\n\n /** Drop the selection if its turn disappeared (session swap, history reset). */\n useEffect(() => {\n if (selectedTurnId && !turnIds.includes(selectedTurnId))\n setSelectedTurnId(null)\n }, [selectedTurnId, turnIds])\n\n const inSelectMode = selectedTurnId !== null\n\n const enterSelectMode = useCallback(() => {\n if (turnIds.length === 0)\n return // no turns to select — silently no-op\n setSelectedTurnId(turnIds[turnIds.length - 1])\n }, [turnIds])\n\n const cycleSelectedTurn = useCallback((direction: -1 | 1) => {\n setSelectedTurnId((prev) => {\n if (!prev || turnIds.length === 0)\n return prev\n const idx = turnIds.indexOf(prev)\n if (idx === -1) // stale id — restart from the most recent turn\n return turnIds[turnIds.length - 1]\n // Wrap at the edges so ↑ on the oldest turn lands on the newest\n // (and ↓ on the newest comes back to the oldest). Matches the\n // wrap behavior of every list picker in the app.\n const len = turnIds.length\n const nextIdx = ((idx + direction) % len + len) % len\n return turnIds[nextIdx]\n })\n }, [turnIds])\n\n // Fork — create a new session whose turns are a protocol-clean prefix\n // ending at `turnId`, then switch to it. The current session is left\n // intact on disk so the user can navigate back to it from the sessions\n // screen. We thread through `activateSession` so the agent rebuild +\n // event replay + state persist all happen via the canonical path.\n const onForkTurn = useCallback(async (turnId: string) => {\n const source = sessionRef.current\n if (!source || !picked)\n return\n const slice = truncateTurnsAt(source.turns, turnId)\n if (!slice || slice.length === 0)\n return\n // Carry forward the parent's run records that are referenced by the\n // kept turns. Without this, the fork persists with `runs: []` even\n // though its turns still reference `run_1..run_N` — the modal's\n // run / token / cost stats would read as zero, and the next\n // `agent.run` on the fork would mint `run_1` again, colliding with\n // existing turn.runId references. Shallow-clone each entry so the\n // fork's mutations don't bleed back into the parent's in-memory\n // session.\n const referencedRunIds = new Set<string>()\n for (const t of slice) {\n if (t.runId)\n referencedRunIds.add(t.runId)\n }\n const inheritedRuns = source.runs\n .filter(r => referencedRunIds.has(r.id))\n .map(r => ({ ...r }))\n // Build the new session in-memory with the truncated turns + inherited\n // runs, persist via `save()`, then activate via id so the agent loop\n // sees the full SessionData on reload. The fork inherits the parent's\n // `projectRoot` rather than re-resolving from `cwd` — a fork belongs\n // to whatever project its parent did, even if the user is forking\n // from a different working directory than where the parent was made.\n const fork = await createSession({\n store,\n ...(source.projectRoot ? { projectRoot: source.projectRoot } : {}),\n })\n fork.setTurns(slice)\n fork.setRuns(inheritedRuns)\n try {\n await fork.save()\n }\n catch (err) {\n debugLog('fork: save failed', err)\n return\n }\n setSelectedTurnId(null) // exit select mode before swapping sessions\n await activateSession(fork.id, picked.provider.key)\n }, [picked, store, activateSession])\n\n // Delete — mutate the current session in place, stripping any orphan\n // tool blocks left by the removal. Persists via `session.save()` and\n // re-renders the transcript from the new turn list. Stays in the same\n // session (and in select mode, on a sensible neighbor if possible).\n const onDeleteTurn = useCallback(async (turnId: string) => {\n const session = sessionRef.current\n if (!session)\n return\n const nextTurns = deleteTurnSafely(session.turns, turnId)\n if (!nextTurns)\n return\n session.setTurns(nextTurns)\n try {\n await session.save()\n }\n catch (err) {\n debugLog('delete: save failed', err)\n return\n }\n // Re-derive events from the mutated turn list. `lastContextSize`\n // moves to whatever the new most-recent assistant turn reports.\n setEvents(eventsFromTurns(session.turns, session.runs))\n const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs)\n setLastInputTokens(replayedTokens)\n lastInputTokensRef.current = replayedTokens\n // Branching forks the conversation — any prior compaction baseline\n // belongs to the pre-branch history. Invalidate it.\n lastCompactedInputTokensRef.current = undefined\n setSessionCost(sumRunCosts(session.runs))\n setCurrentSession(prev => prev\n ? {\n ...prev,\n turnCount: session.turns.length,\n userMessageCount: session.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n updatedAt: Date.now(),\n }\n : prev)\n // If the active selection was the deleted turn (or one of the\n // collateral-stripped ones), drop it. The standalone effect on\n // `turnIds` would catch this too, but doing it eagerly avoids one\n // frame of \"selection points at nothing\".\n setSelectedTurnId((prev) => {\n if (!prev)\n return prev\n return nextTurns.some(t => t.id === prev) ? prev : null\n })\n }, [])\n\n // Edit — replace the text blocks of a turn with new content. Non-text\n // blocks (tool_call, tool_result, thinking, etc.) are preserved as-is.\n const onEditTurn = useCallback(async (turnId: string, newText: string) => {\n const session = sessionRef.current\n if (!session)\n return\n const turn = session.turns.find(t => t.id === turnId)\n if (!turn)\n return\n const hasTextBlock = turn.content.some(b => b.type === 'text')\n if (!hasTextBlock)\n return\n // Replace all text blocks with a single one containing the edited text.\n // Preserve non-text blocks (tool calls, tool results, thinking, etc.)\n // in their original positions.\n const nonTextBlocks = turn.content.filter(b => b.type !== 'text')\n const firstTextIdx = turn.content.findIndex(b => b.type === 'text')\n const updatedContent: SessionContentBlock[] = [...nonTextBlocks]\n if (newText.trim()) {\n updatedContent.splice(firstTextIdx >= 0 ? Math.min(firstTextIdx, updatedContent.length) : 0, 0, { type: 'text', text: newText })\n }\n turn.content = updatedContent\n session.setTurns([...session.turns])\n try {\n await session.save()\n }\n catch (err) {\n debugLog('edit: save failed', err)\n return\n }\n setEvents(eventsFromTurns(session.turns, session.runs))\n const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs)\n setLastInputTokens(replayedTokens)\n lastInputTokensRef.current = replayedTokens\n // Editing rewinds the turn list — an earlier compact-summary turn may\n // have been removed entirely. Drop the baseline so the next trigger\n // decision uses the absolute-threshold path.\n lastCompactedInputTokensRef.current = undefined\n setCurrentSession(prev => prev\n ? { ...prev, updatedAt: Date.now() }\n : prev)\n }, [])\n\n // -------------------------------------------------------------------------\n // Session details — opens a metadata + actions modal for a session. Reachable\n // via ctrl+x from either screen:\n // - chat: targets the active session (current run).\n // - sessions list: targets the focused row (state-driven below).\n // -------------------------------------------------------------------------\n\n /**\n * Identity of the session row the user has focused on the sessions\n * screen — single source of truth. `SessionsScreen` is rendered fully\n * controlled against it: the select's `selectedIndex` is derived from\n * this id, so when the underlying list reorders (e.g. after `generate\n * title` updates `updatedAt`, SQLite returns rows in a new order) the\n * cursor follows the IDENTITY of the row the user was on, not the\n * numerical slot it used to occupy. `ctrl+x` reads it directly.\n *\n * `null` means the cursor is on `+ new session` (or the list is\n * empty); the keyboard handler skips opening the details modal in\n * that case.\n */\n const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)\n\n const onDeleteSession = useCallback(async (id: string) => {\n try {\n await store.delete(id)\n }\n catch (err) {\n debugLog('delete session failed', err)\n return\n }\n // Cleanup the session's persisted-tool-result blobs (best-effort).\n // The helper swallows missing-dir errors so a session that never\n // persisted anything is a clean no-op. Runs AFTER the store delete\n // so a partial failure here doesn't leave the DB pointing at gone\n // blobs without the user knowing the session was nuked.\n void cleanupPersistedSession(resolvePersistDir({ userDir: dataDir, sessionId: id }))\n // Background-task log files live under `<userDir>/<sessionId>/tasks/`\n // — same cleanup pattern. Best-effort: a session with no background\n // tasks is a clean no-op.\n void cleanupPersistedSession(resolveTasksDir({ userDir: dataDir, sessionId: id }))\n const wasCurrent = id === currentSession?.id\n if (wasCurrent) {\n // Drop the active agent + bound session BEFORE we touch the\n // sessions list — the chat screen reads `currentSession` and\n // `sessionRef`, both of which must be cleared in lock-step or we\n // get a half-empty transcript pointing at a row that no longer\n // exists. Order: teardown (closes agent + drains queue) →\n // clear state (forces remount of `ChatScreen` / route to\n // `sessions` below).\n await teardown()\n setCurrentSession(null)\n setEvents([])\n setSelectedTurnId(null)\n stateStore.save({ ...stateStore.load(), lastSessionId: undefined })\n }\n await refreshSessions()\n if (wasCurrent) {\n // Switch the user to the sessions list so they pick the next\n // session intentionally. Falling back to chat would leave them\n // staring at the empty-transcript placeholder. When the list is\n // empty the sessions screen renders its own empty-state with the\n // `+ new` row already focused — no separate path needed.\n setScreen('sessions')\n }\n }, [store, currentSession, teardown, refreshSessions, stateStore, dataDir])\n\n // Generate a title for a session via the active provider + picked\n // model, persist it to `metadata.title`, and update any live UI\n // (chat title bar, sessions list) so the change is visible without a\n // session swap. Returns the generated title so the modal can flash\n // it in its header.\n //\n // The call uses the SAME provider/model the user is currently picked\n // on — title generation feels like a quick \"ask the model\" action,\n // and routing it elsewhere would surprise users billed per provider.\n const onGenerateTitle = useCallback(async (sessionId: string, signal: AbortSignal): Promise<string> => {\n if (!picked)\n throw new Error('No provider picked — open the chat screen first.')\n const descriptor = providerRegistry[picked.provider.key]\n if (!descriptor)\n throw new Error(`Provider \"${picked.provider.key}\" is not registered.`)\n // Prefer the live session when generating for the active one (saves\n // a round-trip to disk + we already have the freshest turns there).\n // Fall back to the store for inactive sessions. Reuse `data` for the\n // persist step so we don't hit the store twice for one operation.\n let turns: SessionTurn[] | undefined\n let metadataRecord: Record<string, unknown> | undefined\n let liveSession: Session | null = null\n let loadedData: Awaited<ReturnType<typeof store.load>> = null\n if (sessionId === sessionRef.current?.id) {\n liveSession = sessionRef.current\n turns = sessionRef.current.turns\n metadataRecord = sessionRef.current.metadata\n }\n else {\n loadedData = await store.load(sessionId)\n if (!loadedData)\n throw new Error('Session not found.')\n turns = loadedData.turns\n metadataRecord = loadedData.metadata\n }\n const title = await generateSessionTitle({\n provider: descriptor.factory(),\n model: picked.model,\n turns,\n signal,\n })\n // Persist via the live session when active (the wrapper hooks fire\n // `session:meta` for downstream observers); otherwise write through\n // the store directly with the loaded SessionData.\n if (liveSession) {\n liveSession.setMeta('title', title)\n await liveSession.save().catch(err => debugLog('generate-title: save failed', err))\n }\n else {\n // `loadedData` is populated above on this branch — no second load.\n if (!loadedData)\n throw new Error('Session disappeared mid-generation.')\n const nextMeta: Record<string, unknown> = { ...(metadataRecord ?? {}), title }\n await store.save({ ...loadedData, metadata: nextMeta, updatedAt: Date.now() })\n .catch(err => debugLog('generate-title: store.save failed', err))\n }\n // Reflect the new title in the chat header when it's the active\n // session.\n setCurrentSession(prev => prev && prev.id === sessionId\n ? { ...prev, title, updatedAt: Date.now() }\n : prev)\n // Refresh the sessions list so the new title shows immediately when\n // the modal was opened from there (the list is rendered live behind\n // the modal). Skipping this would leave the user looking at a\n // stale row right after committing the rename — the UX equivalent\n // of \"did anything happen?\". `refreshSessions` is cheap (one store\n // list + per-row metadata derive) so we don't gate it.\n await refreshSessions().catch(err => debugLog('generate-title: refreshSessions failed', err))\n return title\n }, [picked, providerRegistry, store, refreshSessions])\n\n // Session compaction — drives `compactConversation` against the active\n // provider + picked model, then appends a synthetic `compact-summary`\n // marker turn so the wire-level loop elides everything before it. The\n // model only sees the summary from this point forward; the user can\n // still scroll back to see the original turns (transcript renders\n // them unchanged, and select-turn mode still walks them).\n //\n // Prefers the live session for the active one (same rationale as\n // `onGenerateTitle` — live turns are freshest); falls back to the\n // store for inactive sessions. After compaction completes we refresh\n // the chat-side derived state so the transcript repaints with the\n // boundary card immediately.\n const onCompactSession = useCallback(async (\n sessionId: string,\n signal: AbortSignal,\n ): Promise<SessionCompactResult> => {\n if (!picked)\n throw new Error('No provider picked — open the chat screen first.')\n const descriptor = providerRegistry[picked.provider.key]\n if (!descriptor)\n throw new Error(`Provider \"${picked.provider.key}\" is not registered.`)\n const isLive = sessionId === sessionRef.current?.id\n const liveSession: Session | null = isLive ? sessionRef.current : null\n const loaded = liveSession ? null : await store.load(sessionId)\n const turns = liveSession ? liveSession.turns : loaded?.turns\n if (!turns || turns.length === 0)\n throw new Error('Session has no turns to compact.')\n\n const result = await compactConversation({\n provider: descriptor.factory(),\n model: picked.model,\n turns,\n scope: 'tail',\n signal,\n })\n const summaryTurn = summaryToTurn({\n summary: result.summary,\n replacesTurnIds: result.summarizedTurnIds,\n model: result.model,\n usage: result.usage,\n })\n\n // Post-compact restoration — re-inject the recently-read files and\n // active skills as synthetic tool_call/tool_result pairs after the\n // marker so the model keeps direct access to its working set.\n //\n // Only runs for the active (live) session: we need the agent's\n // ExecutionContext + handle to fetch file contents, and we need\n // `agent.activeSkills` for the skill list. For an inactive session\n // opened from the picker, restoration is skipped — re-activating the\n // session will trigger the agent to re-Read whatever it needs on the\n // next turn. The \"restored 0\" count surfaces in the banner so the\n // user knows.\n const agent = liveSession ? agentRef.current : null\n // Match the shape `buildPostCompactAttachments` returns so the\n // assignment below carries every field forward; the `estimatedTokens`\n // piece feeds the post-compact effective-size estimate the chat layer\n // surfaces in the footer and the success banner.\n let restoration = {\n turns: [] as readonly SessionTurn[],\n restoredFiles: 0,\n restoredSkills: 0,\n estimatedTokens: 0,\n }\n if (agent && liveSession && agent.handle) {\n const recentFiles = selectFilesFromSession(liveSession, agent.handle.cwd)\n try {\n const built = await buildPostCompactAttachments({\n recentFiles,\n activeSkills: agent.activeSkills,\n execution: agent.execution,\n handle: agent.handle,\n ...(summaryTurn.runId ? { runId: summaryTurn.runId } : {}),\n })\n restoration = built\n }\n catch (err) {\n // Restoration is best-effort — never let a failure here block\n // the compaction itself. The summary still landed; the user\n // just doesn't get the post-compact attachments this round.\n debugLog('compact: post-compact restoration failed', err)\n }\n }\n\n // Estimate the post-compact wire-level context size — the model's\n // view of the conversation on its NEXT turn. Used both as the\n // success banner's \"→ N effective\" line and as the new value for\n // `lastInputTokens` so the chat footer's `ctx` indicator drops\n // immediately instead of carrying the pre-compact count until the\n // next real turn's `usage` lands.\n //\n // Composition:\n // - `result.usage.output` — the LLM-counted token size of the\n // summary itself (most accurate piece, comes from the provider).\n // - `restoration.estimatedTokens` — byte-based estimate for the\n // re-injected file/skill content. Less precise but cheap to\n // compute and within ±20% of provider counts for text content.\n //\n // Under-estimates by the size of the system prompt + preserved\n // tail turns; both are small relative to the rest, and the next\n // real `turn:after` corrects the value precisely. Good enough for\n // \"did my context drop?\" visual feedback.\n const effectiveTokens = (result.usage.output ?? 0) + restoration.estimatedTokens\n\n // Persist via the live session when active so wrapper hooks fire\n // (downstream observers see `session:turns` for the marker turn);\n // otherwise append directly through the store for inactive sessions.\n if (liveSession) {\n await liveSession.appendTurns([summaryTurn, ...restoration.turns])\n // Repaint the transcript so the boundary card appears now, not\n // after the next agent.run. `eventsFromTurns` emits the\n // `compact-summary` event the renderer already knows about.\n //\n // Gated on the session still being current — auto-compact's\n // background nature means the user can navigate away mid-flight;\n // painting the old session's events onto the new session's\n // transcript would be confusing. The persisted data above is\n // unaffected; when the user returns to this session it rehydrates\n // through `eventsFromTurns` normally.\n if (sessionRef.current?.id === sessionId) {\n setEvents(eventsFromTurns(liveSession.turns, liveSession.runs))\n // Drop the footer's `ctx` indicator to the new effective size.\n // Same session-id gate as the events repaint — we never want to\n // overwrite the active session's footer with another session's\n // post-compact estimate.\n setLastInputTokens(effectiveTokens)\n lastInputTokensRef.current = effectiveTokens\n // Latch the post-compact baseline for hysteresis. Subsequent\n // turns' `shouldAutoCompact` calls compare the new\n // `lastInputTokensRef` against this baseline + the configured\n // growth floor to suppress compact → compact thrashing.\n lastCompactedInputTokensRef.current = effectiveTokens\n }\n setCurrentSession(prev => prev && prev.id === sessionId\n ? {\n ...prev,\n turnCount: liveSession.turns.length,\n updatedAt: Date.now(),\n }\n : prev)\n }\n else {\n if (!loaded)\n throw new Error('Session disappeared mid-compaction.')\n // Inactive sessions get only the marker — restoration needs an\n // execution context we don't have without an active agent. Same\n // append shape so `summaryTurn` is always the immediate successor\n // of the elided turns.\n const nextData: SessionData = {\n ...loaded,\n turns: [...loaded.turns, summaryTurn],\n updatedAt: Date.now(),\n }\n await store.save(nextData)\n .catch(err => debugLog('compact: store.save failed', err))\n }\n\n // Refresh the sessions list so the updated turn count + timestamps\n // show immediately if the modal was opened from the sessions screen.\n await refreshSessions().catch(err => debugLog('compact: refreshSessions failed', err))\n return {\n replacedCount: result.summarizedTurnIds.length,\n droppedCount: result.droppedDueToPtl.length,\n restoredFiles: restoration.restoredFiles,\n restoredSkills: restoration.restoredSkills,\n model: result.model,\n inputTokens: (result.usage.input ?? 0) + (result.usage.cacheRead ?? 0) + (result.usage.cacheCreation ?? 0),\n outputTokens: result.usage.output ?? 0,\n effectiveTokens,\n }\n }, [picked, providerRegistry, store, refreshSessions])\n\n // ---------------------------------------------------------------------\n // Auto-compaction — fires after a normal `agent.run()` completes, when\n // the latest turn's input usage crosses the user-configured threshold.\n //\n // Mechanics:\n // - Runs in the background (no await on the caller's hot path) so the\n // prompt input area stays interactive — the user can compose their\n // next message while the summary call is in flight.\n // - Stores its in-flight promise on `autoCompactInFlightRef`. The\n // next `onSubmitPrompt` invocation awaits it before calling\n // `agent.run()`, so a quick user submit is queued behind the\n // pending compaction (no race against `appendTurns`).\n // - Never auto-continues the conversation — only the user's explicit\n // prompt submission triggers the next turn.\n // - Surfaces `info` events into the transcript so the user sees what\n // happened (\"🗜 Compacting…\" → \"✓ Compacted: …\" or \"✗ failed\").\n // ---------------------------------------------------------------------\n const triggerAutoCompactIfNeeded = useCallback((sessionId: string): void => {\n if (autoCompactInFlightRef.current)\n return // already in flight — predicate guards against double-fire too, but cheap.\n if (!picked)\n return\n const descriptor = providerRegistry[picked.provider.key]\n const rawWindow = descriptor ? getContextWindow(descriptor, picked.model) : null\n const decision = shouldAutoCompact({\n enabled: autoCompactRef.current,\n threshold: autoCompactThresholdRef.current,\n inputTokens: lastInputTokensRef.current,\n rawContextWindow: rawWindow,\n alreadyCompacting: !!autoCompactInFlightRef.current,\n ...(lastCompactedInputTokensRef.current !== undefined\n ? { lastCompactedInputTokens: lastCompactedInputTokensRef.current }\n : {}),\n minGrowthFraction: AUTO_COMPACT_MIN_GROWTH_FRACTION,\n })\n if (decision.kind !== 'fire')\n return\n\n // Announce in the transcript — same `info` kind we use for any\n // generic banner, so the renderer doesn't need a new event type.\n const pct = Math.round(decision.usedFraction * 100)\n stream.appendImmediate({\n kind: 'info',\n text: `🗜 Auto-compacting conversation (used ${pct}% of effective context)…`,\n })\n // Light up the title indicator so the user sees \"conversation is\n // doing something\" even when nothing is streaming visibly. Cleared\n // in the `.finally` below alongside the ref.\n setCompacting(true)\n\n // Real AbortController so `teardown()` can cancel the in-flight\n // LLM call when the user navigates away from the session — both\n // saves tokens and prevents the result handlers below from racing\n // a now-disposed session view.\n const abort = new AbortController()\n autoCompactAbortRef.current = abort\n\n // Capture `sessionId` here so the completion / error handlers can\n // tell whether the user is still on the session this compaction\n // was meant for. If they've navigated away by the time we resolve,\n // skip the transcript event silently — the persisted summary +\n // restoration are correct on the original session and will\n // rehydrate via `eventsFromTurns` when the user comes back.\n const compactionPromise = onCompactSession(sessionId, abort.signal)\n .then((result) => {\n if (sessionRef.current?.id !== sessionId)\n return\n const elidedSuffix = result.droppedCount > 0 ? `, ${result.droppedCount} dropped (PTL)` : ''\n const restoredSuffix = result.restoredFiles > 0 ? `, ${result.restoredFiles} file${result.restoredFiles === 1 ? '' : 's'} restored` : ''\n // Surface the post-compact effective size — the next turn will\n // start from this token count instead of the pre-compact one.\n // Pairs with the footer's `ctx` indicator, which `onCompactSession`\n // already updated; the banner just shows the same number inline\n // so the user doesn't have to glance away to see the drop.\n stream.appendImmediate({\n kind: 'info',\n text: `✓ Compacted: ${result.replacedCount} turn${result.replacedCount === 1 ? '' : 's'} elided${elidedSuffix}${restoredSuffix} → ~${result.effectiveTokens.toLocaleString()} effective tokens.`,\n })\n })\n .catch((err) => {\n if (sessionRef.current?.id !== sessionId)\n return\n // Aborts are an intentional teardown path — never an error from\n // the user's perspective. Stay silent rather than emitting a\n // \"failed\" banner for the very thing we just chose to cancel.\n if (abort.signal.aborted)\n return\n stream.appendImmediate({\n kind: 'error',\n text: `Auto-compaction failed: ${errorMessage(err)}`,\n })\n })\n .finally(() => {\n // Only clear when WE're still the in-flight reference. Defense\n // against a faulty test or future race that swapped the ref\n // mid-flight; we never want to null out somebody else's promise.\n if (autoCompactInFlightRef.current === compactionPromise) {\n autoCompactInFlightRef.current = null\n setCompacting(false)\n }\n if (autoCompactAbortRef.current === abort)\n autoCompactAbortRef.current = null\n })\n\n autoCompactInFlightRef.current = compactionPromise\n }, [picked, providerRegistry, stream, onCompactSession])\n\n // Populate the hoisted `triggerAutoCompactRef` (declared up with the other\n // refs) now that `triggerAutoCompactIfNeeded` exists. The ref bridges the\n // ordering between `continueResumedInteractions` / `onSubmitPrompt` (which\n // need to fire the trigger) and `onCompactSession` (which the trigger\n // depends on), without forcing a major re-arrangement of the file.\n useEffect(() => { triggerAutoCompactRef.current = triggerAutoCompactIfNeeded }, [triggerAutoCompactIfNeeded])\n\n // Session export — render `SessionData` as Markdown/JSON and write it\n // under the project's (or user's) `.{prefix}/sessions/` directory.\n // Prefers the live in-memory snapshot for the active session (freshest\n // turns, no extra store roundtrip); reads the persisted blob from the\n // store for inactive sessions. The renderer-agnostic logic + path\n // resolution live in `chat/session-export.ts`.\n const onExportSession = useCallback(async (\n sessionId: string,\n format: SessionExportFormat,\n ): Promise<{ filepath: string, format: SessionExportFormat }> => {\n let data: SessionData | null = null\n if (sessionId === sessionRef.current?.id)\n data = sessionRef.current.toJSON()\n else\n data = await store.load(sessionId)\n if (!data)\n throw new Error('Session not found.')\n const target = await writeSessionExport({\n session: data,\n format,\n cwd: projectDir,\n prefix: config.prefix,\n })\n return { filepath: target.filepath, format }\n }, [store, projectDir, config.prefix])\n\n const openSessionDetails = useCallback(async (sessionId: string) => {\n // Always reload from the store. The chat screen has the live\n // `sessionRef`, but it's mutated by streaming hooks — loading a\n // fresh snapshot means the modal sees the persisted truth, which\n // is what the user expects (e.g. \"what's on disk if I delete?\").\n const data = await store.load(sessionId)\n if (!data) {\n debugLog('openSessionDetails: session not found', sessionId)\n return\n }\n modal.open(\n <SessionDetailsModal\n session={data}\n title={sessionId === currentSession?.id ? currentSession.title : undefined}\n isCurrent={sessionId === currentSession?.id}\n keybindings={keybindings}\n actions={{\n onDelete: onDeleteSession,\n onExport: onExportSession,\n ...(picked ? { onGenerateTitle, onCompact: onCompactSession } : {}),\n }}\n />,\n )\n }, [modal, store, currentSession, onDeleteSession, picked, onGenerateTitle, onCompactSession, onExportSession, keybindings])\n\n // Open the details modal for the active selection. Reads the turn from\n // `sessionRef.current` lazily so we don't have to wire the full session\n // through React state — it's append-only between activations and the\n // ref is updated synchronously in `activateSession`.\n const openSelectedTurn = useCallback(() => {\n const id = selectedTurnId\n if (!id)\n return\n const session = sessionRef.current\n if (!session)\n return\n const turn = session.turns.find(t => t.id === id)\n if (!turn)\n return\n const index = turnIds.indexOf(id) + 1\n modal.open(\n <TurnDetailsModal\n turn={turn}\n index={index}\n total={turnIds.length}\n actions={{ onFork: onForkTurn, onDelete: onDeleteTurn, onEdit: onEditTurn }}\n keybindings={keybindings}\n />,\n )\n }, [modal, selectedTurnId, turnIds, onForkTurn, onDeleteTurn, onEditTurn, keybindings])\n\n useKeyboard((key) => {\n if (modal.isOpen)\n return\n // Select-turn mode takes precedence on the chat screen — the textarea\n // is unfocused in this mode (see `ChatScreen` → `PromptBlock`) so up /\n // down / return reach this handler instead of moving the textarea\n // cursor. Esc exits the mode; the parent's \"esc = back / abort\" rule\n // only applies in normal mode.\n if (inSelectMode && screen === 'chat') {\n if (key.name === 'up') {\n cycleSelectedTurn(-1)\n return\n }\n if (key.name === 'down') {\n cycleSelectedTurn(1)\n return\n }\n if (key.name === 'return') {\n openSelectedTurn()\n return\n }\n if (key.name === 'escape') {\n setSelectedTurnId(null)\n return\n }\n // Swallow other keys so a stray ctrl+o doesn't open Settings mid-\n // selection — the user can exit first if they want a global action.\n return\n }\n // Queue-selection mode — same precedence rationale as select-turn.\n // The textarea is unfocused while `queueSelectionIndex != null` (see\n // PromptBlock's `selectMode`-style branch), so plain `up` / `down` /\n // `escape` / `delete` reach this handler without being eaten by the\n // textarea's own bindings. `pushQueuedMessage` carries the configured\n // shortcut (ctrl+return by default) so the user can rebind it.\n if (queueSelectionIndex != null && screen === 'chat') {\n if (key.name === 'up') {\n moveQueueSelection(-1)\n return\n }\n if (key.name === 'down') {\n moveQueueSelection(1)\n return\n }\n if (matchesBinding(key, keybindings.pushQueuedMessage)) {\n pushSelectedQueuedMessage()\n return\n }\n if (matchesBinding(key, keybindings.dropQueuedMessage)) {\n dropSelectedQueuedMessage()\n return\n }\n if (key.name === 'escape') {\n exitQueueSelection()\n return\n }\n // Same \"swallow stray globals\" rule as select-turn mode — the\n // user has explicitly picked the queue as their focus surface.\n return\n }\n // `enterQueueSelection` — moves focus into the queue box. Only fires\n // when there's something to select; gated on chat screen + no\n // approval / interaction (those own the slot the prompt would\n // otherwise be in). Busy state is fine — the queue itself implies\n // a run is in flight.\n if (\n matchesBinding(key, keybindings.enterQueueSelection)\n && screen === 'chat'\n && !pendingApproval\n && !pendingInteraction\n && messageQueue.length > 0\n ) {\n enterQueueSelection()\n return\n }\n // Global action shortcuts — every binding resolves through\n // `config.keybindings` so the user can rebind any of them via\n // `<userDir>/keybindings.json`. The hardcoded `key.name === 'o'`-\n // style checks used to live here; they got replaced with\n // `matchesBinding(key, …)` against the merged user+defaults map.\n // The action's *gates* (screen / busy / pendingApproval / model\n // capability / registry size) stay attached to its trigger so a\n // rebind to a different shortcut still respects the same eligibility.\n if (matchesBinding(key, keybindings.openSettings) && screen !== 'auth') {\n modal.open(\n <SettingsModal\n actions={{\n onReauth,\n onOpenKeybindings: onOpenKeybindingsFile,\n onLoginMcp,\n onLogoutMcp,\n onCancelLoginMcp,\n onRefreshSkills,\n onRefreshMcps,\n }}\n />,\n )\n return\n }\n // `openSessionDetails` — targets the active session on the chat\n // screen, or the focused row in the sessions list. Gated on the\n // chat side by `!busy && !pendingApproval` so a live run isn't\n // interrupted mid-stream by the modal stealing focus + handlers.\n // On the sessions screen the modal is read-mostly (delete confirms\n // first) so no run-state gate is needed there.\n if (matchesBinding(key, keybindings.openSessionDetails)) {\n if (screen === 'chat' && currentSession && !busy && !pendingApproval) {\n void openSessionDetails(currentSession.id)\n return\n }\n if (screen === 'sessions' && isSessionRowId(focusedSessionId)) {\n void openSessionDetails(focusedSessionId)\n return\n }\n }\n if (matchesBinding(key, keybindings.openModelPicker) && screen === 'chat' && picked && !busy) {\n modal.open(\n <ModelPickerModal\n providers={availableProviders}\n modelsFor={modelsFor}\n current={{ providerKey: picked.provider.key, modelId: picked.model }}\n onPick={onPickModel}\n />,\n )\n return\n }\n // `openEffortPicker` — gated on reasoning support: a non-reasoning\n // model has no effort knob and the modal would be a dead-end. Same\n // idle gating as the model picker so the modal doesn't steal\n // focus mid-stream.\n if (matchesBinding(key, keybindings.openEffortPicker) && screen === 'chat' && picked && !busy && modelHasReasoning) {\n const descriptor = providerRegistry[picked.provider.key]\n modal.open(\n <EffortPickerModal\n current={picked.effort}\n supportsAdaptive={!!descriptor && piIdOf(descriptor) === 'anthropic'}\n onPick={onPickEffort}\n />,\n )\n return\n }\n // `openTodos` — chat-only viewer for the active run's `todowrite`\n // checkpoints. Read-only by design (the model owns the list), so\n // there's no run-state gate beyond \"we're in a session\": opening\n // the modal mid-stream is fine, the live data flows through\n // `useActiveTodos` on every parent re-render. Still gated on\n // `screen === 'chat'` so the binding stays inert on auth /\n // sessions screens where there's no session to read.\n if (matchesBinding(key, keybindings.openTodos) && screen === 'chat' && currentSession) {\n // Pass `agentRef.current` so the modal can subscribe to\n // `tool:after` for live updates — `<ModalRoot>` sits ABOVE\n // `<AppShell>` in the tree, so the streaming-buffer cascade\n // doesn't reach it. Without the subscription the modal would\n // freeze at its open-time snapshot.\n modal.open(<TodosModal session={sessionRef.current} agent={agentRef.current} />)\n return\n }\n // `openKeybindings` — quick reference panel listing every shortcut\n // grouped by surface, with a button to edit the JSON file. Read-only\n // (changes to `keybindings.json` apply on next launch, see\n // `onOpenKeybindingsFile`), so no run-state gating beyond\n // `screen !== 'auth'`: the catalog is meaningful on every screen\n // and opening it mid-stream is safe.\n if (matchesBinding(key, keybindings.openKeybindings) && screen !== 'auth') {\n modal.open(\n <KeybindingsModal\n bindings={keybindings}\n filePath={keybindingsPath(config.paths.userDir)}\n onEditFile={onOpenKeybindingsFile}\n onClose={() => modal.close()}\n />,\n )\n return\n }\n // `enterSelectTurnMode` — only on the chat screen, only when idle.\n // A streaming turn would have a moving `turnIds` tail under our\n // feet — disrupting the muscle memory of \"I just selected this\n // turn\". No-op when the session has no turns yet. Also gated on\n // `pendingInteraction` so up/down don't fight the InteractionBlock's\n // own picker.\n if (matchesBinding(key, keybindings.enterSelectTurnMode) && screen === 'chat' && !busy && !pendingApproval && !pendingInteraction) {\n enterSelectMode()\n return\n }\n // `cycleAgent` — quick-cycle through agent profiles without\n // opening the picker. Gated on chat screen, idle state, no pending\n // approval/interaction (the wizard reuses `shift+↹` for back), and\n // a multi-profile registry so the textarea never sees it as input\n // and single-agent setups don't get a no-op binding. (The\n // descriptive picker is intentionally not bound at the global\n // level — `ctrl+a` is the textarea's select-all.)\n if (matchesBinding(key, keybindings.cycleAgent) && screen === 'chat' && hasMultipleAgents && !busy && !pendingApproval && !pendingInteraction) {\n void onCycleAgent()\n return\n }\n // `cancelToolCall` — surfaces a small picker that lets the user\n // stop something the agent has running without aborting the whole\n // run. Two kinds of entries land in the picker:\n //\n // 1. Mid-dispatch tool calls — `inFlightTools`, populated by\n // `tool:before` / drained by `tool:after`. Cancelled via\n // `agent.cancelTool(callId)`.\n //\n // 2. Running background tasks — `backgroundTasks`, populated by\n // `background:start` / drained by `background:exit`. A\n // background task SURVIVES past its spawning `tool:after`\n // (the shell tool body returns the handle and ends, but the\n // child process keeps running), so it can't live in\n // `inFlightTools` alone. Cancelled via\n // `agent.killBackgroundTask(taskId)`.\n //\n // The picker is open whenever EITHER registry has entries — not\n // just `busy`. A background task running while the agent is idle\n // (waiting for the user's next prompt) is the central use case.\n //\n // `!pendingApproval` stays the same — the approval picker IS the\n // gate for un-dispatched calls; this picker is for already-running\n // things.\n if (matchesBinding(key, keybindings.cancelToolCall) && screen === 'chat' && !pendingApproval) {\n // Filter to PARENT-level rows only. Subagent entries (rows with\n // `childId`) bubble through the parent's hook bus and look\n // cancellable here, but the cancel primitives (`cancelTool` /\n // `killBackgroundTask`) walk the PARENT agent's maps — the\n // child's entries live in the CHILD agent's maps. Without the\n // filter, picking a child row would be a silent no-op. Cancelling\n // the parent's `spawn` call IS in the picker and DOES unwind the\n // whole subtree (spawn body's `ctx.signal` flips, child run\n // sees the abort, child's destroy SIGTERMs its tasks).\n const tools = inFlightToolsRef.current.filter(entry => entry.childId === undefined)\n const tasks = backgroundTasksRef.current.filter(entry => entry.childId === undefined)\n const snapshot = [...tools, ...tasks]\n if (snapshot.length === 0)\n return\n modal.open(\n <CancelToolModal\n inFlight={snapshot}\n onCancel={(entry, reason) => {\n const agent = agentRef.current\n if (!agent)\n return false\n // Route by discriminator. Tool calls go through the\n // per-call abort signal; background tasks go through the\n // execution context's process-group kill.\n if (entry.kind === 'task')\n return agent.killBackgroundTask(entry.callId)\n return agent.cancelTool(entry.callId, reason)\n }}\n onCancelAll={() => {\n const agent = agentRef.current\n if (!agent)\n return\n for (const entry of snapshot) {\n if (entry.kind === 'task')\n void agent.killBackgroundTask(entry.callId)\n else\n agent.cancelTool(entry.callId, 'user-cancelled-all')\n }\n }}\n onClose={() => modal.close()}\n />,\n )\n return\n }\n if (matchesBinding(key, keybindings.changeCwd) && screen !== 'auth') {\n modal.open(\n <CwdPickerModal\n currentCwd={cwdRef.current}\n onPick={(dir) => {\n setCwd(dir)\n modal.close()\n }}\n />,\n )\n return\n }\n if (key.name !== 'escape')\n return\n // Esc always aborts the in-flight run (whether or not a prompt is\n // pending) — `onAbort` denies every queued approval and flips the\n // agent's signal. Per-call accept/deny lives in the approval picker\n // itself; this keeps \"esc = stop everything\" as a single rule.\n if (busy || pendingApproval)\n return onAbort()\n // Defer to the prompt's completion popup if it's open — Esc there\n // means \"close the @file / /skill picker\", *not* \"leave the chat\n // for the session picker\". `onAbort` above already short-circuits\n // on the same ref; this is the matching guard for the idle path.\n if (popupOpenRef.current)\n return\n if (screen === 'chat')\n return onOpenSessions()\n if (screen === 'sessions') {\n if (currentSession)\n setScreen('chat')\n else\n renderer.destroy()\n return\n }\n // auth screen — if the user got here from settings (picked already\n // exists), bounce them back to wherever they were. Only exit on a true\n // first-launch (no provider picked yet).\n if (picked) {\n setScreen(currentSession ? 'chat' : 'sessions')\n return\n }\n renderer.destroy()\n })\n\n // -------------------------------------------------------------------------\n // Derived footer state.\n // -------------------------------------------------------------------------\n\n // Resumed-interaction state — `pendingInteraction` is set but no live\n // run is in flight. The footer + esc handler treat this as \"pause\"\n // rather than \"abort\": esc leaves the form without writing a\n // `tool_result`, so the session stays paused and re-enqueues the\n // same interaction on next activation. Live interactions (busy=true)\n // continue to follow the abort rules below.\n const pendingInteractionIsResumed = !!pendingInteraction && !busy\n\n // Auto-update — quietly poll the registry every 24 h, surface a passive\n // `↑ vX.Y.Z` chip in the footer when a newer release is available. The\n // hook is a no-op when the host didn't wire `autoUpdate` or the user\n // disabled `Settings.checkForUpdates`; env opt-outs (CI / NO_UPDATE_NOTIFIER\n // / ZIDANE_NO_UPDATE) fire inside `checkForUpdate`.\n const updateStatus = useUpdateCheck({\n packageName: autoUpdateConfig?.packageName ?? '',\n currentVersion: autoUpdateConfig?.currentVersion ?? '',\n enabled: !!autoUpdateConfig && settings.checkForUpdates,\n cacheDir: dataDir,\n registry: autoUpdateConfig?.registry,\n channel: autoUpdateConfig?.channel,\n cacheTtlMs: autoUpdateConfig?.cacheTtlMs,\n })\n const updateHint = useMemo(\n () => buildUpdateHint(updateStatus, { color: COLOR.accent }),\n [updateStatus, COLOR.accent],\n )\n\n const hints: Hint[] = useMemo(\n () => buildHints({\n screen,\n busy,\n pending: !!pendingApproval,\n pendingInteractionLive: !!pendingInteraction && busy,\n pendingInteractionResumed: pendingInteractionIsResumed,\n currentSession,\n hasMultipleAgents,\n uiMode: settings.uiMode,\n modelLabel: picked?.model ?? null,\n modelColor: COLOR.model,\n effortLabel: modelHasReasoning ? (picked?.effort ?? 'medium') : null,\n effortColor: COLOR.warn,\n // `/n` rides the same `warn` accent as `ctrl+m` — both are real\n // shortcuts, so the chord reads as one keyboard binding (\"ctrl+m\n // or ctrl+n\") instead of a primary key with a hidden tail.\n effortKeyColor: COLOR.warn,\n agentLabel: pickedAgent.label,\n agentColor: accentColor(pickedAgent.accent, COLOR),\n keybindings,\n // Count parent-level rows from BOTH registries — mid-dispatch\n // tool calls AND running background tasks. Subagent entries\n // (rows with `childId`) are filtered out of the picker itself\n // (different cancel map), so the hint mirrors that scope.\n inFlightToolCount: inFlightTools.reduce(\n (n, entry) => entry.childId === undefined ? n + 1 : n,\n 0,\n ) + backgroundTasks.reduce(\n (n, entry) => entry.childId === undefined ? n + 1 : n,\n 0,\n ),\n activeSkillCount: activeSkillNames.size,\n skillsChipColor: COLOR.brand,\n updateHint,\n }),\n [screen, busy, pendingApproval, pendingInteraction, pendingInteractionIsResumed, currentSession, hasMultipleAgents, settings.uiMode, picked, pickedAgent, COLOR, modelHasReasoning, keybindings, inFlightTools, backgroundTasks, activeSkillNames, updateHint],\n )\n\n // Trigger affordances rendered on the right of the prompt-box title\n // overlay (after the prompt's keyboard shortcuts). Each entry is\n // gated on its provider having content — an empty skills catalog\n // means `/ skills` would be a dead hint, so we drop it entirely\n // rather than advertise a no-op. Files: same. Threaded into\n // `ChatScreen` → `PromptBlock` → `PromptHints` which owns the\n // responsive drop logic (these vanish before the primary shortcuts\n // when the terminal is too narrow).\n // Flatten the {@link QueuedMessage} shape (prompt + raw refs) into the\n // lighter {@link QueuedPreview} the ChatScreen renders. The mapping is\n // memoised by `messageQueue` identity so the queue box only re-renders\n // when the underlying queue actually changes — and the same identity\n // contract is preserved for the consumer's `=== EMPTY_QUEUED_MESSAGES`\n // default-stability check (we pass the live `messageQueue` array\n // through; React's default-prop fallback only fires when undefined).\n const queuedMessagePreviews = useMemo(\n () => messageQueue.map(m => ({\n text: m.prompt,\n refs: m.references\n .filter(r => r.start >= 0 && r.end > r.start)\n .map(r => ({ start: r.start, end: r.end, providerId: r.providerId })),\n })),\n [messageQueue],\n )\n\n // Resolved shortcut specs threaded into the queue UI so the title\n // overlay (\"ctrl+↑ select\") and the per-row action hints (\"ctrl+↵\n // push\", \"⌫ drop\") track the user's customizations from\n // `keybindings.json` instead of hardcoding the defaults.\n const queueShortcuts = useMemo(() => ({\n enter: keybindings.enterQueueSelection,\n push: keybindings.pushQueuedMessage,\n drop: keybindings.dropQueuedMessage,\n }), [keybindings])\n\n const promptTriggerHints: Hint[] = useMemo(() => {\n // Always advertise both triggers — discovery for the underlying\n // catalogs is now lazy (kicks off when the popover first opens),\n // so gating on `catalog.length > 0` would hide the hints until\n // the user has already used them. Hints are pure discoverability\n // affordances; an empty popover is harmless.\n return [\n {\n key: '@',\n label: 'files',\n keyColor: resolveChipColor(SURFACE.chips, 'files').bg,\n },\n {\n key: '/',\n label: 'skills',\n keyColor: resolveChipColor(SURFACE.chips, 'skills').bg,\n },\n ]\n }, [SURFACE])\n\n const contextUsage: ContextUsage | null = useMemo(() => {\n if (screen !== 'chat' || !picked)\n return null\n const descriptor = providerRegistry[picked.provider.key]\n if (!descriptor)\n return null\n // `getContextWindow` already checks `descriptor.models` first and falls\n // back to pi-ai's registry — one call covers both cases.\n const max = getContextWindow(descriptor, picked.model)\n return max ? { used: lastInputTokens, max } : null\n }, [screen, picked, lastInputTokens, providerRegistry])\n\n const footerCost = screen === 'chat' ? sessionCost : null\n\n // Drop the agent + clear timers when the app unmounts.\n useEffect(() => () => { void teardown() }, [teardown])\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1, backgroundColor: SURFACE.background }}>\n <box style={{ flexDirection: 'column', flexGrow: 1, paddingLeft: 1, paddingRight: 1 }}>\n {screen === 'auth' && <AuthScreen onPick={onPickProvider} />}\n {screen === 'sessions' && (\n <SessionsScreen\n sessions={sessions}\n currentId={currentSession?.id ?? null}\n focusedSessionId={focusedSessionId}\n onPick={onSwitchSession}\n onCreate={onCreateSession}\n onFocusChange={setFocusedSessionId}\n showAllProjects={settings.showAllProjects}\n currentProjectRoot={projectDir}\n />\n )}\n {screen === 'chat' && (\n <ChatScreen\n cwd={cwd}\n events={events}\n busy={busy}\n compacting={compacting}\n queuedMessages={queuedMessagePreviews}\n queueSelectionIndex={queueSelectionIndex}\n queueShortcuts={queueShortcuts}\n onEnterQueueFromEmptyPrompt={enterQueueSelectionFromEmptyPrompt}\n settings={settings}\n onSubmit={onSubmitPrompt}\n session={currentSession}\n liveSession={sessionRef.current}\n pending={pendingApproval}\n onApproval={resolveHead}\n pendingInteraction={pendingInteraction}\n onInteraction={onInteractionResolve}\n completionProviders={completionProviders}\n onPopupOpenChange={onPopupOpenChange}\n selectedTurnId={selectedTurnId}\n promptTriggerHints={promptTriggerHints}\n />\n )}\n </box>\n <Footer\n hints={hints}\n context={contextUsage}\n cost={footerCost}\n status={\n pendingInteraction?.kind === 'question'\n ? 'asking'\n : busy\n ? 'busy'\n : compacting\n ? 'compacting'\n : null\n }\n />\n </box>\n )\n}\n\n/**\n * Resolve the reasoning effort to seed `Picked.effort` for the given model.\n * Returns `undefined` when the model has no reasoning knob; otherwise the\n * per-model remembered value, defaulting to `'medium'` (sensible middle\n * ground when the user has never picked an explicit effort for this model).\n */\nfunction effortForModel(\n descriptor: ProviderDescriptor,\n modelId: string,\n remembered: Record<string, ThinkingLevel> | undefined,\n): ThinkingLevel | undefined {\n if (!modelSupportsReasoning(descriptor, modelId))\n return undefined\n return remembered?.[modelId] ?? 'medium'\n}\n","import type { FiletypeParserOptions } from '@opentui/core'\nimport { addDefaultParsers, getTreeSitterClient } from '@opentui/core'\n\n/**\n * Register Tree-sitter parsers for the languages we'd like highlighted\n * inside fenced markdown code blocks.\n *\n * OpenTUI ships JS/TS/Markdown/Zig out of the box. Anything else needs a\n * Tree-sitter `.wasm` grammar + a `highlights.scm` capture query. We fetch\n * both from the upstream Tree-sitter grammar repos; OpenTUI's worker\n * caches them under its data path (`~/.local/share/opentui/...` by\n * default) so the download is a one-shot cost per language per machine.\n *\n * `aliases` lets a single grammar handle multiple fence info-strings — e.g.\n * `bash` also matches ` ```sh ` and ` ```shell `. The model picks fences\n * inconsistently across providers; aliases save us from missing highlights\n * on synonyms.\n *\n * Runtime caveats:\n * - **First use** of a language triggers an HTTPS download. Subsequent\n * uses (same machine, same data path) are instant.\n * - **Compiled binaries** (`bun --compile`) still work — the data path\n * is a writable OS dir, not the bunfs. Air-gapped deployments would\n * need to either pre-populate the cache or migrate to local-file\n * vendoring via `with { type: 'file' }` imports (see\n * https://opentui.com/docs/reference/tree-sitter/#use-local-files).\n * - If a download fails (offline / firewall), the language renders as\n * plain `markup.raw.block` — no crash, just no syntax color.\n * - **Self-hosted wasm.** Languages whose upstream grammars don't ship\n * `.wasm` releases (currently: SQL) are built ahead of time and\n * vendored under `tui/parsers/`; the TUI binary embeds the file via\n * `with { type: 'file' }` (see `tui/src/tree-sitter-bundle.ts`),\n * extracts it to a tmpdir at startup, and points the SDK at the\n * extracted path through a `ZIDANE_LOCAL_TREE_SITTER_<X>_WASM` env\n * var — keeps the repo's privacy boundary intact (no\n * `raw.githubusercontent.com` fetches) and works offline / inside\n * air-gapped runners. SDK consumers without these env vars set just\n * skip the affected grammars. See `tui/parsers/README.md` for the\n * rebuild recipe and `LOCAL_PARSERS` below for the env-var\n * contract.\n *\n * Versions are pinned in the WASM URLs so a grammar repo's `master`\n * landing a breaking change can't silently affect us.\n */\nconst EXTRA_PARSERS: FiletypeParserOptions[] = [\n {\n filetype: 'python',\n aliases: ['py'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-python/releases/download/v0.23.6/tree-sitter-python.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-python/v0.23.6/queries/highlights.scm'],\n },\n },\n {\n filetype: 'bash',\n aliases: ['sh', 'shell', 'zsh'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-bash/releases/download/v0.23.3/tree-sitter-bash.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-bash/v0.23.3/queries/highlights.scm'],\n },\n },\n {\n filetype: 'json',\n wasm: 'https://github.com/tree-sitter/tree-sitter-json/releases/download/v0.24.8/tree-sitter-json.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-json/v0.24.8/queries/highlights.scm'],\n },\n },\n {\n filetype: 'rust',\n aliases: ['rs'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-rust/releases/download/v0.23.2/tree-sitter-rust.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-rust/v0.23.2/queries/highlights.scm'],\n },\n },\n {\n filetype: 'go',\n aliases: ['golang'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-go/releases/download/v0.23.4/tree-sitter-go.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-go/v0.23.4/queries/highlights.scm'],\n },\n },\n {\n filetype: 'yaml',\n aliases: ['yml'],\n wasm: 'https://github.com/tree-sitter-grammars/tree-sitter-yaml/releases/download/v0.7.0/tree-sitter-yaml.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter-grammars/tree-sitter-yaml/v0.7.0/queries/highlights.scm'],\n },\n },\n {\n filetype: 'html',\n aliases: ['htm'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-html/releases/download/v0.23.2/tree-sitter-html.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-html/v0.23.2/queries/highlights.scm'],\n },\n },\n {\n filetype: 'css',\n wasm: 'https://github.com/tree-sitter/tree-sitter-css/releases/download/v0.23.2/tree-sitter-css.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-css/v0.23.2/queries/highlights.scm'],\n },\n },\n {\n filetype: 'haskell',\n aliases: ['hs'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-haskell/releases/download/v0.23.1/tree-sitter-haskell.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-haskell/v0.23.1/queries/highlights.scm'],\n },\n },\n {\n filetype: 'c',\n // `h` would clash with C++ header conventions — leave header fences\n // unmapped so they fall through to the standard fence renderer\n // rather than picking the wrong grammar.\n wasm: 'https://github.com/tree-sitter/tree-sitter-c/releases/download/v0.24.1/tree-sitter-c.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-c/v0.24.1/queries/highlights.scm'],\n },\n },\n {\n filetype: 'cpp',\n // CommonMark accepts arbitrary fence info-strings — `c++`, `cxx`, `cc`\n // are common in the wild and all unambiguously C++.\n aliases: ['c++', 'cxx', 'cc'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-cpp/releases/download/v0.23.4/tree-sitter-cpp.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-cpp/v0.23.4/queries/highlights.scm'],\n },\n },\n {\n filetype: 'csharp',\n // Upstream ships the wasm with an underscore in the filename\n // (`tree-sitter-c_sharp.wasm`) — pinned URL handles that quirk.\n aliases: ['cs', 'c#'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-c-sharp/releases/download/v0.23.1/tree-sitter-c_sharp.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-c-sharp/v0.23.1/queries/highlights.scm'],\n },\n },\n {\n filetype: 'elixir',\n aliases: ['ex', 'exs'],\n wasm: 'https://github.com/elixir-lang/tree-sitter-elixir/releases/download/v0.3.4/tree-sitter-elixir.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/elixir-lang/tree-sitter-elixir/v0.3.4/queries/highlights.scm'],\n },\n },\n]\n\n/**\n * Vendored-grammar registration table for languages whose upstream\n * maintainers don't ship a `.wasm`. Each entry is gated on an env var\n * that points at an on-disk wasm path; when the var is unset (SDK\n * consumer running zidane outside the TUI binary), the grammar is\n * simply skipped — fences fall through to plain text.\n *\n * The TUI binary populates these via `tui/src/tree-sitter-bundle.ts`,\n * which embeds the wasm as a `with { type: 'file' }` import and\n * extracts it to a tmpdir at startup. External consumers can wire\n * their own paths by exporting the matching env var before calling\n * {@link setupTreeSitter}.\n */\ninterface LocalParser {\n /** Skeleton entry — filled in with the resolved wasm path at registration time. */\n template: Omit<FiletypeParserOptions, 'wasm'>\n /** Env var holding the absolute path to the local `.wasm`. */\n pathEnvVar: string\n}\n\nconst LOCAL_PARSERS: readonly LocalParser[] = [\n {\n // SQL: upstream `DerekStride/tree-sitter-sql` ships grammar source only;\n // we build the wasm ahead of time and ship it inside the TUI binary\n // (see `tui/parsers/tree-sitter-sql.wasm` + `tui/parsers/README.md`).\n // The `highlights.scm` is still pinned to upstream's release tag —\n // their repo is public so no credentials needed.\n template: {\n filetype: 'sql',\n aliases: ['psql', 'mysql', 'sqlite', 'plsql'],\n queries: {\n highlights: ['https://raw.githubusercontent.com/DerekStride/tree-sitter-sql/v0.3.11/queries/highlights.scm'],\n },\n },\n pathEnvVar: 'ZIDANE_LOCAL_TREE_SITTER_SQL_WASM',\n },\n]\n\n/**\n * Walk {@link LOCAL_PARSERS} and return the parsers whose `pathEnvVar`\n * resolves to a non-empty string. Stripped entries log a one-shot warning\n * to stderr so a TUI host with broken bundle setup doesn't fail\n * silently.\n */\nfunction resolveLocalParsers(): FiletypeParserOptions[] {\n const resolved: FiletypeParserOptions[] = []\n for (const { template, pathEnvVar } of LOCAL_PARSERS) {\n const wasm = process.env[pathEnvVar]\n if (wasm && wasm.length > 0)\n resolved.push({ ...template, wasm })\n }\n return resolved\n}\n\nlet registered = false\nlet workerInitStarted = false\n\n/**\n * Synchronously append every URL-fetched + locally-vendored grammar to\n * OpenTUI's global parser registry. Cheap (just a couple of `Array.push`\n * calls into a module-level table) and idempotent — subsequent calls\n * are no-ops.\n *\n * Split out from {@link initTreeSitterWorker} so the boot path can land\n * the registry *synchronously* on its critical path (no awaiting\n * required) and defer the heavier worker spin-up below until after the\n * first frame is on screen. OpenTUI's `highlightOnce` self-initialises\n * the worker on first use anyway — registering parsers ahead of time is\n * the only piece that genuinely has to happen synchronously.\n */\nexport function registerTreeSitterParsers(): void {\n if (registered)\n return\n registered = true\n addDefaultParsers([...EXTRA_PARSERS, ...resolveLocalParsers()])\n}\n\n/**\n * Spin up OpenTUI's parser worker thread. Idempotent — concurrent\n * callers share a single in-flight promise; subsequent calls after\n * resolution are no-ops.\n *\n * Safe to fire-and-forget after the renderer mounts: the worker boot\n * is the heaviest step in tree-sitter setup (~40–100ms on most\n * machines) and nothing visible needs it until the first fenced code\n * block highlight, which is many frames away even on the fastest\n * sessions. If a `<markdown>` element happens to call\n * `highlightOnce()` before our explicit init completes, OpenTUI's\n * client guards that call with its own `if (!this.initialized) await\n * this.initialize()` — no race.\n */\nexport function initTreeSitterWorker(): Promise<void> {\n if (!workerInitStarted) {\n workerInitStarted = true\n // Make sure the parsers are registered before the worker comes up;\n // otherwise `registerDefaultParsers()` inside `initialize()` would\n // run against an incomplete set on first boot.\n registerTreeSitterParsers()\n }\n return getTreeSitterClient().initialize()\n}\n\n/**\n * Convenience: register parsers AND await worker boot.\n *\n * Kept exported for hosts that genuinely need tree-sitter ready before\n * the first paint (CI smoke tests, scripted runs). The TUI itself uses\n * the split form to keep boot fast — see `src/tui/index.tsx`.\n */\nexport async function setupTreeSitter(): Promise<void> {\n registerTreeSitterParsers()\n await initTreeSitterWorker()\n}\n","/** @jsxImportSource @opentui/react */\nimport type { McpAuthStatus } from '../chat/mcp-auth-state'\nimport type { DiscoveredMcp, DiscoveryError } from '../chat/mcps-discovery'\nimport { homedir } from 'node:os'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useState } from 'react'\nimport { useEnabledToggleSet } from '../chat/enabled-toggle-set'\nimport { useMcpAuthState } from '../chat/mcp-auth-context'\nimport { getMcpAuthStatus } from '../chat/mcp-auth-state'\nimport { useColors } from '../chat/theme-context'\nimport { Modal } from './modal'\nimport { McpAuthorizingPanel } from './oauth-auth-block'\n\n/**\n * MCP server picker. Shows discovered entries with three columns:\n *\n * [enabled?] <name> <transport · detail> <auth status>\n *\n * Keybindings:\n *\n * ↑ / ↓ / k / j navigate\n * enter / space toggle enabled/disabled\n * l login (needs-auth or error rows; opens browser)\n * o logout (authed rows; wipes stored tokens)\n * esc close — also cancels an in-flight `authorizing` row\n *\n * Status badge legend:\n *\n * ✓ authed tokens stored + bootstrap connected\n * ! needs login bootstrap needs OAuth, no tokens\n * … authorizing interactive login in flight\n * ✗ <error> login attempt failed\n *\n * The state behind the badges is read via `useMcpAuthState()` so updates\n * propagate live while the modal is open — a `mcp:auth:url` arriving from\n * an in-flight login flips the row to `authorizing` without re-opening.\n *\n * Errors (`DiscoveryError[]`) surface in a warn-colored preamble so a\n * broken `mcps.json` is loud rather than invisible.\n */\nexport function McpsSettingsModal({\n catalog,\n errors,\n onLogin,\n onLogout,\n onCancelLogin,\n onRefresh,\n}: {\n catalog: readonly DiscoveredMcp[]\n errors?: readonly DiscoveryError[]\n onLogin: (name: string) => Promise<void> | void\n onLogout: (name: string) => void\n onCancelLogin: (name: string) => void\n /**\n * Optional force-rescan hook bound to `r` on this standalone modal.\n * Hosts wire it to `discoverProjectMcps` (or the future async\n * registry-aware variant). Unlike `SettingsModal`, here we use\n * plain `r` because there's no search input to swallow the key —\n * matches the single-letter convention of `l` / `o`.\n */\n onRefresh?: () => Promise<void> | void\n}) {\n const COLOR = useColors()\n const home = homedir()\n const authState = useMcpAuthState()\n const { enabledSet, toggle } = useEnabledToggleSet<DiscoveredMcp>({\n catalog,\n keyOf: d => d.config.name,\n settingKey: 'enabledMcps',\n })\n const [cursor, setCursorRaw] = useState(0)\n\n // Wrap-around on the edges so ↑ at row 0 jumps to the last server (and\n // ↓ at the bottom comes back to the first). The clamp-on-read below\n // covers the rare case where `catalog` shrinks between writes.\n const moveCursor = useCallback(\n (delta: number) =>\n setCursorRaw((prev) => {\n if (catalog.length === 0)\n return prev\n return (((prev + delta) % catalog.length) + catalog.length) % catalog.length\n }),\n [catalog.length],\n )\n const safeCursor = Math.min(cursor, Math.max(0, catalog.length - 1))\n\n const focusedEntry = catalog[safeCursor]\n const focusedStatus = focusedEntry ? getMcpAuthStatus(authState, focusedEntry.config.name) : undefined\n // While the paste-redirect input on the focused row is live, swallow\n // every single-letter shortcut (`l` / `o` / `r` / `k` / `j` / space)\n // so they end up in the text field. We keep `escape` working so the\n // user can still cancel the in-flight login with one keypress.\n const inputActive = focusedStatus?.kind === 'authorizing' && !!focusedStatus.url\n\n useKeyboard((key) => {\n // Escape always reaches us — it's the cancel affordance.\n if (key.name === 'escape' && focusedEntry) {\n const name = focusedEntry.config.name\n const status = getMcpAuthStatus(authState, name)\n if (status.kind === 'authorizing') {\n onCancelLogin(name)\n return\n }\n }\n if (inputActive)\n return\n if (key.name === 'up' || key.name === 'k' || (key.ctrl && key.name === 'p')) {\n moveCursor(-1)\n return\n }\n if (key.name === 'down' || key.name === 'j' || (key.ctrl && key.name === 'n')) {\n moveCursor(1)\n return\n }\n if (catalog.length === 0)\n return\n const entry = catalog[safeCursor]\n if (!entry)\n return\n const name = entry.config.name\n const status = getMcpAuthStatus(authState, name)\n if (key.name === 'return' || key.name === 'space') {\n toggle(name)\n return\n }\n if (key.name === 'l' && canLogin(entry, status)) {\n void onLogin(name)\n return\n }\n if (key.name === 'o' && canLogout(status)) {\n onLogout(name)\n return\n }\n if (key.name === 'r' && onRefresh) {\n void onRefresh()\n }\n })\n\n if (catalog.length === 0) {\n return (\n <Modal title=\"mcp servers\">\n {renderErrors(errors, home, COLOR.warn)}\n <text fg={COLOR.dim}>No MCP servers discovered.</text>\n <text fg={COLOR.mute}>\n Drop a\n <span fg={COLOR.model}>{' mcps.json '}</span>\n into\n <span fg={COLOR.model}>{' .zidane/ '}</span>\n or\n <span fg={COLOR.model}>{' .agents/ '}</span>\n (project or\n <span fg={COLOR.model}>{' ~/'}</span>\n ).\n </text>\n <text fg={COLOR.mute}>\n Flat array\n <span fg={COLOR.model}>{' [ { \"name\": \"fs\", \"command\": \"...\" } ] '}</span>\n or\n <span fg={COLOR.model}>{' { \"name\": {...} } '}</span>\n map.\n </text>\n <text fg={COLOR.mute}>\n <span fg={COLOR.model}>{' { \"mcpServers\": { ... } } '}</span>\n wrapper from Claude Code / Cursor also works.\n </text>\n <text fg={COLOR.mute}>\n {onRefresh && (\n <span>\n <span fg={COLOR.warn}>r</span>\n {' refresh · '}\n </span>\n )}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n }\n\n return (\n <Modal title={` mcp servers · ${enabledSet.size} / ${catalog.length} enabled `}>\n {renderErrors(errors, home, COLOR.warn)}\n <box style={{ flexDirection: 'column' }}>\n {catalog.map((entry, i) => {\n const focused = i === safeCursor\n const name = entry.config.name\n const enabled = enabledSet.has(name)\n const status = getMcpAuthStatus(authState, name)\n return (\n <text key={name} fg={focused ? COLOR.brand : COLOR.dim}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{name}</span>\n <span fg={COLOR.mute}>\n {' '}\n {detailFor(entry)}\n </span>\n {renderInlineBadge(status, COLOR)}\n </text>\n )\n })}\n </box>\n {focusedEntry && focusedStatus && renderDetailPanel(focusedEntry, focusedStatus, COLOR)}\n {renderActionHints(focusedEntry, focusedStatus, !!onRefresh, COLOR)}\n </Modal>\n )\n}\n\nfunction detailFor(entry: DiscoveredMcp): string {\n const transport = entry.config.transport\n const detail = transport === 'stdio'\n ? entry.config.command ?? ''\n : entry.config.url ?? ''\n return `${transport} · ${detail}`\n}\n\nfunction canLogin(entry: DiscoveredMcp, status: McpAuthStatus): boolean {\n // OAuth login only applies to http transports. `stdio` MCP servers\n // don't speak OAuth — login keypress on them is a no-op.\n if (entry.config.transport === 'stdio')\n return false\n // `idle` is excluded — we'd be guessing at OAuth on a server we haven't\n // tried yet, and the SDK's discovery would 404 on servers that don't\n // advertise it. Let the bootstrap run first; it'll flip the row to\n // `needs-auth` (which IS loginable) or just connect cleanly.\n return status.kind === 'needs-auth' || status.kind === 'error'\n}\n\nfunction canLogout(status: McpAuthStatus): boolean {\n return status.kind === 'authed' || status.kind === 'error' || status.kind === 'authorizing'\n}\n\n/**\n * One-liner badge next to the row name. Stays short so the row never wraps —\n * the verbose info (full URL, full error message) lives in the focused-row\n * detail panel below the list, where it can wrap naturally.\n */\nfunction renderInlineBadge(status: McpAuthStatus, COLOR: ReturnType<typeof useColors>) {\n switch (status.kind) {\n case 'idle':\n return null\n case 'authed':\n return (\n <span fg={COLOR.accent}>\n {' '}\n ✓ authed\n </span>\n )\n case 'needs-auth':\n return (\n <span fg={COLOR.warn}>\n {' '}\n ! needs login\n </span>\n )\n case 'authorizing':\n return (\n <span fg={COLOR.warn}>\n {' '}\n … authorizing\n </span>\n )\n case 'error':\n return (\n <span fg={COLOR.error}>\n {' '}\n ✗ login failed\n </span>\n )\n }\n}\n\n/**\n * Verbose detail panel rendered below the list for the focused row. Only\n * draws something when the status carries information that doesn't fit on\n * the row itself (authorizing URL, error description). Idle / authed /\n * needs-auth rows render nothing here — the inline badge and the action\n * hints already say everything needed.\n *\n * Wraps via stacked `<text>` lines rather than a single multi-line string\n * so long URLs / errors break at safe points (and the modal panel sizes\n * itself to the content automatically).\n */\nfunction renderDetailPanel(\n entry: DiscoveredMcp,\n status: McpAuthStatus,\n COLOR: ReturnType<typeof useColors>,\n) {\n if (status.kind === 'authorizing') {\n if (!status.url) {\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.brand}>{`Authorizing ${entry.config.name}`}</text>\n <text fg={COLOR.dim}>Starting login flow…</text>\n </box>\n )\n }\n // `McpAuthorizingPanel` owns the paste-back input + handler. Default\n // Modal maxWidth=92, padding=2 each side, border=1 each side → content\n // area ≤ 86 cols.\n return (\n <McpAuthorizingPanel\n serverName={entry.config.name}\n authUrl={status.url}\n maxLineWidth={86}\n inputFocused\n />\n )\n }\n if (status.kind === 'error') {\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.error}>\n {`Login failed: ${entry.config.name}`}\n </text>\n <text fg={COLOR.dim}>{status.error}</text>\n <text fg={COLOR.mute}>\n Press\n {' '}\n <span fg={COLOR.warn}>l</span>\n {' '}\n to retry.\n </text>\n </box>\n )\n }\n return null\n}\n\nfunction renderActionHints(\n entry: DiscoveredMcp | undefined,\n status: McpAuthStatus | undefined,\n showRefresh: boolean,\n COLOR: ReturnType<typeof useColors>,\n) {\n const effectiveStatus = status ?? { kind: 'idle' as const }\n const canL = entry ? canLogin(entry, effectiveStatus) : false\n const canO = canLogout(effectiveStatus)\n const canCancel = effectiveStatus.kind === 'authorizing'\n return (\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' toggle'}\n {canL && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>l</span>\n {' login'}\n </span>\n )}\n {canO && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>o</span>\n {' logout'}\n </span>\n )}\n {showRefresh && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>r</span>\n {' refresh'}\n </span>\n )}\n {canCancel && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </span>\n )}\n {!canCancel && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </span>\n )}\n </text>\n )\n}\n\nfunction renderErrors(\n errors: readonly DiscoveryError[] | undefined,\n home: string,\n warnColor: string,\n) {\n if (!errors || errors.length === 0)\n return null\n return (\n <box style={{ flexDirection: 'column' }}>\n {errors.map(err => (\n <text key={err.path} fg={warnColor}>\n {`! ${displayPath(err.path, home)}: ${err.message}`}\n </text>\n ))}\n </box>\n )\n}\n\nfunction displayPath(path: string, home: string): string {\n if (home && path.startsWith(`${home}/`))\n return `~/${path.slice(home.length + 1)}`\n return path\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ReactNode } from 'react'\nimport type { EnabledAllowlistKey } from '../chat/enabled-toggle-set'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useState } from 'react'\nimport { useEnabledToggleSet } from '../chat/enabled-toggle-set'\nimport { useColors } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/**\n * Generic list-with-checkboxes modal. Powers both the Skills and MCP\n * server pickers — same state machine, same keyboard map, same row\n * geometry. Per-feature variance flows in through props:\n *\n * - `keyOf` — extract the persisted identity (skill name, server name).\n * - `settingKey` — `'enabledSkills'` | `'enabledMcps'`.\n * - `renderDetail` — appended to each row in mute color (descriptions,\n * transports, …). Optional.\n * - `emptyState` — replacement content when `catalog` is empty.\n *\n * Renderer-agnostic state machine lives in `useEnabledToggleSet`\n * (chat layer) — a GUI shell can build its own toggle list against the\n * same hook without pulling OpenTUI.\n */\nexport function ToggleListModal<T>({\n catalog,\n keyOf,\n settingKey,\n title,\n renderDetail,\n emptyState,\n preamble,\n onRefresh,\n}: {\n catalog: readonly T[]\n keyOf: (entry: T) => string\n settingKey: EnabledAllowlistKey\n /** Modal title stem — rendered as `${title} · N / M enabled` in non-empty mode. */\n title: string\n /** Mute-colored trailing copy for each row (description, transport, …). */\n renderDetail?: (entry: T) => ReactNode\n /** What to render when `catalog` is empty (hint about where to drop config). */\n emptyState: ReactNode\n /**\n * Optional content rendered ABOVE the list (or the empty state). Used to\n * surface non-fatal warnings — e.g. discovered files that failed to parse,\n * so the user can fix them without quitting the TUI. Distinct from\n * `emptyState`, which only renders when there's nothing to show; this\n * renders even when the catalog has entries.\n */\n preamble?: ReactNode\n /**\n * Optional force-rescan hook bound to `ctrl+R`. Hosts wire it to a\n * discovery rerun (`discoverProjectSkills`, `discoverProjectMcps`,\n * …); the modal just exposes the keybind and footer hint.\n */\n onRefresh?: () => Promise<void> | void\n}) {\n const COLOR = useColors()\n const { enabledSet, toggle } = useEnabledToggleSet<T>({ catalog, keyOf, settingKey })\n const [cursor, setCursorRaw] = useState(0)\n\n // Wrap-around navigation — ↑ on the first row jumps to the last entry,\n // ↓ on the last row comes back to the first. Same pattern the model\n // picker / settings modal use; keeps long lists navigable without a\n // separate \"home / end\" shortcut.\n const moveCursor = useCallback(\n (delta: number) =>\n setCursorRaw((prev) => {\n if (catalog.length === 0)\n return prev\n return (((prev + delta) % catalog.length) + catalog.length) % catalog.length\n }),\n [catalog.length],\n )\n\n const safeCursor = Math.min(cursor, Math.max(0, catalog.length - 1))\n\n useKeyboard((key) => {\n if (key.name === 'up' || (key.ctrl && key.name === 'p')) {\n moveCursor(-1)\n }\n else if (key.name === 'down' || (key.ctrl && key.name === 'n')) {\n moveCursor(1)\n }\n else if (key.name === 'return' || key.name === 'space') {\n const entry = catalog[safeCursor]\n if (entry)\n toggle(keyOf(entry))\n }\n else if (key.ctrl && key.name === 'r' && onRefresh) {\n void onRefresh()\n }\n })\n\n if (catalog.length === 0) {\n return (\n <Modal title={title}>\n {preamble}\n {emptyState}\n <text fg={COLOR.mute}>\n {onRefresh && (\n <span>\n <span fg={COLOR.warn}>ctrl+R</span>\n {' refresh · '}\n </span>\n )}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n }\n\n return (\n <Modal title={` ${title} · ${enabledSet.size} / ${catalog.length} enabled `}>\n {preamble}\n <box style={{ flexDirection: 'column' }}>\n {catalog.map((entry, i) => {\n const focused = i === safeCursor\n const name = keyOf(entry)\n const enabled = enabledSet.has(name)\n return (\n <text key={name} fg={focused ? COLOR.brand : COLOR.dim}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{name}</span>\n {renderDetail && (\n <span fg={COLOR.mute}>\n {' '}\n {renderDetail(entry)}\n </span>\n )}\n </text>\n )\n })}\n </box>\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' toggle · '}\n {onRefresh && (\n <span>\n <span fg={COLOR.warn}>ctrl+R</span>\n {' refresh · '}\n </span>\n )}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { SkillConfig } from '../skills'\nimport { useColors } from '../chat/theme-context'\nimport { ToggleListModal } from './toggle-list-modal'\n\n/**\n * List + toggle modal for discovered skills. State machine + keyboard +\n * row geometry live in `<ToggleListModal>`; this file just supplies the\n * skill-specific column (description) and the empty-state hint.\n *\n * Pass `onRefresh` to expose `ctrl+R` (force re-scan) on the list. The\n * standalone modal is for embedders — the in-app flow goes through\n * `<SettingsModal>` which exposes the same affordance via its own\n * `SettingsActions.onRefreshSkills` plumbing.\n */\nexport function SkillsSettingsModal({\n catalog,\n onRefresh,\n}: {\n catalog: readonly SkillConfig[]\n onRefresh?: () => Promise<void> | void\n}) {\n const COLOR = useColors()\n return (\n <ToggleListModal<SkillConfig>\n catalog={catalog}\n keyOf={s => s.name}\n settingKey=\"enabledSkills\"\n title=\"skills\"\n renderDetail={skill => skill.description}\n onRefresh={onRefresh}\n emptyState={(\n <>\n <text fg={COLOR.dim}>No skills discovered.</text>\n <text fg={COLOR.mute}>\n Drop a\n <span fg={COLOR.model}>{' SKILL.md '}</span>\n into\n <span fg={COLOR.model}>{' .zidane/skills/<name>/ '}</span>\n or\n <span fg={COLOR.model}>{' .agents/skills/<name>/ '}</span>\n (project or\n <span fg={COLOR.model}>{' ~/'}</span>\n ).\n </text>\n </>\n )}\n />\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ChatOptions } from '../chat/config'\nimport { createCliRenderer } from '@opentui/core'\nimport { createRoot } from '@opentui/react'\nimport { bootTick } from '../chat/boot-profiler'\nimport { resolveConfig } from '../chat/config'\nimport { clampFps, DEFAULT_SETTINGS } from '../chat/settings-context'\nimport { errorMessage } from '../errors'\nimport { createTuiStore } from '../session/sqlite'\nimport { App } from './app'\nimport { initTreeSitterWorker, registerTreeSitterParsers } from './tree-sitter'\n\n// ---------------------------------------------------------------------------\n// Public API\n//\n// `runTui(options)` is the one-shot launcher. For consumers who want to embed\n// the App inside a renderer they already control, the OpenTUI-specific\n// building blocks below are also exported individually.\n//\n// The renderer-agnostic engine (auth, credentials, providers, safe-mode,\n// store, streaming, settings, theme palette, formatters, markdown healer,\n// `resolveConfig`, …) lives in `zidane/chat`. Import from there directly if\n// you're building a non-TUI shell.\n// ---------------------------------------------------------------------------\n\n/**\n * Tracks whether `runTui` has been invoked in this process. `createCliRenderer`\n * installs signal handlers + hijacks stdin; calling it a second time produces\n * undefined behavior. We bail loudly instead.\n */\nlet runTuiInvoked = false\n\n/**\n * Boot a full chat TUI with sensible defaults. **Does not return** under\n * normal use — it terminates the host process via `process.exit(0)` once\n * the user dismisses the renderer (Ctrl+C / Esc on the auth screen / a\n * non-zero exit code is mapped via the `catch` path below).\n *\n * **One-shot:** this function may only be invoked once per process. The\n * underlying OpenTUI renderer wires up global terminal state on init.\n *\n * **Why it exits the process:** after the renderer's `destroy()` restores\n * the terminal, React's reconciler and OpenTUI's internal listeners can\n * keep the Node/Bun event loop open indefinitely — the script appears to\n * hang in `bun run`, and under `bun --watch run` the watcher waits forever\n * for the child to exit. Forcing a clean exit here is the contract that\n * makes `runTui` a true one-shot launcher.\n *\n * Hosts that need post-renderer cleanup should mount `<App config={...} />`\n * against their own `createCliRenderer()` instead of calling `runTui()`.\n *\n * Env-var overrides (handy when launching from CI or restricted shells):\n * - `ZIDANE_STORAGE_DIR` — sets `storageDir`\n * - `ZIDANE_PREFIX` — sets `prefix`\n * - `ZIDANE_DEBUG` — enables `gatherStats` on the renderer and dumps the\n * final fps / frametime distribution to stderr on exit. Useful when\n * tuning the renderer fps setting or comparing terminal emulators.\n *\n * Renderer fps lives in Settings (`targetFps`, cycle `30 / 60 / 120`);\n * the on-disk value seeds the renderer at boot and `AppShell` mirrors\n * subsequent flips onto the live renderer.\n *\n * Hosts building on top of `zidane/tui` typically want their own env vars\n * (e.g. `MYAPP_STORAGE_DIR`); read them in your launch script and forward\n * to `runTui({ storageDir, prefix })`.\n *\n * ```ts\n * import { BUILTIN_AGENTS, BUILTIN_PROVIDERS } from 'zidane/chat'\n * import { runTui } from 'zidane/tui'\n * import { createRemoteStore } from 'zidane/session' // for the `store` option\n *\n * await runTui() // ~/.zidane/sessions.db + state.json\n * await runTui({ prefix: '.myapp' }) // ~/.myapp/...\n * await runTui({ storageDir: '/data', prefix: 'myapp' })\n * await runTui({ providers: { ...BUILTIN_PROVIDERS, mine: myDescriptor } })\n * await runTui({ store: createRemoteStore({ url: '…' }) })\n * await runTui({ agents: { ...BUILTIN_AGENTS, debug: myDebugProfile } })\n * ```\n */\nexport async function runTui(options: Partial<ChatOptions> = {}): Promise<never> {\n if (runTuiInvoked) {\n throw new Error(\n 'runTui() can only be invoked once per process. '\n + 'Compose `<App config={resolveConfig(...)} />` against your own renderer '\n + 'if you need to run multiple TUIs in the same lifetime.',\n )\n }\n runTuiInvoked = true\n bootTick('runTui:enter')\n\n // Register extra Tree-sitter parsers (python, bash, json, rust, go,\n // yaml, html, css, haskell, c, cpp, csharp, elixir, sql) so fenced\n // code blocks in those languages get syntax highlighting. The grammar\n // `.wasm` files are downloaded lazily on first use and cached under\n // OpenTUI's data path. Parser *registration* is sync + cheap and runs\n // on the critical boot path; the heavier *worker* spin-up below is\n // fired off after the first paint — see post-mount block.\n registerTreeSitterParsers()\n bootTick('runTui:after-registerTreeSitterParsers')\n\n // `resolveConfig` wires `ZIDANE_CREDENTIALS_PATH` and applies stored API\n // keys into `process.env` internally — both happen before any provider\n // factory is constructed, so the wizard can render even on fresh installs\n // where the harness providers would otherwise throw \"No API key found\".\n //\n // `store` is injected as a factory so `zidane/chat` stays free of\n // `bun:sqlite`. Callers can override by passing their own `store` in\n // `options`; the explicit fallback keeps `runTui()` usable with zero args\n // while letting hosts swap in their own adapter.\n const config = resolveConfig({\n ...options,\n store: options.store ?? (paths => createTuiStore(paths.db)),\n })\n bootTick('runTui:after-resolveConfig')\n\n // Promise + explicit resolver: avoid the `let done!: ...` non-null trick.\n let done: () => void = () => {}\n const exited = new Promise<void>((resolve) => { done = resolve })\n\n // Seed the renderer's fps from the persisted setting (or its default\n // when state.json predates the field). `AppShell` keeps the live\n // renderer in sync with subsequent flips via the `settings.targetFps`\n // effect — boot value just avoids one flash of 30fps on the way in.\n const bootFps = clampFps(config.initialSettings.targetFps ?? DEFAULT_SETTINGS.targetFps)\n const gatherStats = !!process.env.ZIDANE_DEBUG\n\n const renderer = await createCliRenderer({\n exitOnCtrlC: true,\n onDestroy: () => done(),\n // OpenTUI debounces resize events by 100ms by default, which freezes the\n // current frame while the user drags the terminal corner and produces the\n // visible \"snap\" once the drag stops. Setting this to 0 re-lays out on\n // every SIGWINCH — the OS already caps the rate, so we just track it.\n debounceDelay: 0,\n // OpenTUI's stock target is 30fps (`_targetFps = 30` in its `CliRenderer`\n // ctor). We pin both knobs to the same value — uncapping `maxFps` past\n // the target only burns CPU on terminals that paint at the target rate.\n targetFps: bootFps,\n maxFps: bootFps,\n gatherStats,\n })\n bootTick('runTui:after-createCliRenderer')\n\n createRoot(renderer).render(<App config={config} />)\n bootTick('runTui:after-mount')\n\n // Spin up the Tree-sitter worker in the background. Highlighting on\n // the very first fence is gated by OpenTUI's `highlightOnce()`, which\n // self-initialises the worker if it isn't up yet — moving the boot\n // off the critical path costs ~zero correctness and saves the longest\n // single step (~40–100ms) in `runTui()`.\n void initTreeSitterWorker().then(\n () => bootTick('runTui:after-treeSitterWorker'),\n (err) => {\n process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${errorMessage(err)}\\n`)\n },\n )\n\n await exited\n\n // Dump fps stats AFTER teardown so the numbers cover the full session.\n // The JS-side counters survive `destroy()` — only the native bridge +\n // terminal hijack are gone by this point. Wrapped defensively so a\n // future shape change in `getStats()` can't keep the process pinned.\n if (gatherStats) {\n try {\n const s = renderer.getStats()\n process.stderr.write(\n `[zidane/tui] render stats: fps=${s.fps.toFixed(1)} `\n + `avgFrame=${s.averageFrameTime.toFixed(2)}ms `\n + `min=${s.minFrameTime.toFixed(2)}ms max=${s.maxFrameTime.toFixed(2)}ms `\n + `frames=${s.frameCount}\\n`,\n )\n }\n catch (err) {\n process.stderr.write(`[zidane/tui] render stats unavailable: ${errorMessage(err)}\\n`)\n }\n }\n\n // Force a clean exit. See JSDoc above for rationale — without this the\n // process hangs after the renderer's `destroy()` returns, which manifests\n // as a stuck `bun --watch` parent and a terminal that needs a second Ctrl+C\n // to actually surrender control.\n process.exit(0)\n}\n\n// ---------------------------------------------------------------------------\n// TUI-specific composition exports.\n//\n// Renderer-agnostic engine (auth, store, safe-mode, settings, providers,\n// streaming hooks, theme palette, …) is in `zidane/chat`. Don't re-export it\n// from here — keep `zidane/tui` focused on OpenTUI presentation primitives so\n// non-TUI consumers (a GUI, an SDK, a CI script) never pull OpenTUI in.\n// ---------------------------------------------------------------------------\n\n// Back-compat re-exports from `zidane/chat` so existing `zidane/tui`\n// consumers keep working after the renderer-agnostic helpers (chip\n// splitter, accent resolver, transcript visibility/spacing, tool\n// formatters, hint primitives) moved into the chat layer. New code\n// should import these directly from `zidane/chat` to keep a GUI shell\n// off OpenTUI.\n//\n// @deprecated These re-exports will be removed in the next minor — switch\n// the imports below over to `zidane/chat` (or `zidane/chat/<module>` for\n// the tree-shaken entry points).\n\n/** @deprecated Import from `zidane/chat`. */\nexport { accentColor } from '../chat/agents'\n/** @deprecated Import `Hint` / `hintsLength` from `zidane/chat`. */\nexport { clipHintsToWidth, type Hint, hintsLength, truncateTrailing } from '../chat/hints'\n/** @deprecated Import from `zidane/chat`. */\nexport { type MarkdownSegment, splitMarkdownCodeBlocks } from '../chat/markdown-segments'\n/** @deprecated Import from `zidane/chat`. */\nexport { type PromptSegment, type PromptSegmentRef, splitPromptSegments } from '../chat/prompt-segments'\n/** @deprecated Import from `zidane/chat`. */\nexport { isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, selectableTurnIds, turnSelectionOwnership } from '../chat/store'\n/** @deprecated Import from `zidane/chat`. */\nexport {\n displayNameFor,\n formatToolCall,\n TOOL_DISPLAY,\n type ToolDisplayMeta,\n type ToolFormatLine,\n} from '../chat/tool-formatters'\n/** @deprecated Import from `zidane/chat`. */\nexport { computeTurnAnchors, type TranscriptItem } from '../chat/transcript-anchors'\nexport { AgentPickerModal } from './agent-picker'\nexport { App } from './app'\nexport { CompletionPopup } from './completion-popup'\nexport {\n type ContextUsage,\n Footer,\n type MetaSegment,\n onInputSubmit,\n renderHintSpans,\n Spinner,\n StatusSpinner,\n TitleOverlay,\n Transcript,\n} from './components'\nexport { EffortPickerModal } from './effort-picker'\nexport { InteractionBlock } from './interaction-block'\nexport { McpsSettingsModal } from './mcps-settings'\nexport { Modal, type ModalProps, ModalRoot, useModal, useModalAwareFocus } from './modal'\nexport { ModelPickerModal, type PickedModel } from './model-picker'\nexport { AuthScreen, ChatScreen, type QueuedPreview, type QueueShortcuts, SessionsScreen } from './screens'\nexport {\n type SessionCompactResult,\n SessionDetailsModal,\n type SessionDetailsModalActions,\n type SessionExportResult,\n} from './session-details-modal'\nexport { type SettingsActions, SettingsModal } from './settings-modal'\nexport { SkillsSettingsModal } from './skills-settings'\nexport { buildMdStyle, useMdStyle } from './theme'\nexport { ToggleListModal } from './toggle-list-modal'\nexport { TurnDetailsModal, type TurnDetailsModalActions } from './turn-details-modal'\n"],"mappings":"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"}
1
+ {"version":3,"file":"tui.js","names":["EmptyState","VISIBLE_ROWS","debugLog","CountsBadge","anchorIdFor","ActionRow","canLogin","canLogout","ActionRow","displayPath","MAX_MODAL_HEIGHT"],"sources":["../src/tui/modal.tsx","../src/tui/agent-picker.tsx","../src/tui/cancel-tool-modal.tsx","../src/tui/clipboard.ts","../src/tui/crush-throbber.tsx","../src/tui/theme.ts","../src/tui/components.tsx","../src/tui/cwd-picker.tsx","../src/tui/discovery-shell.tsx","../src/tui/effort-picker.tsx","../src/tui/keybindings-modal.tsx","../src/tui/model-picker.tsx","../src/tui/completion-popup.tsx","../src/tui/file-edit-approval-modal.tsx","../src/tui/interaction-block.tsx","../src/tui/oauth-url-block.tsx","../src/tui/oauth-auth-block.tsx","../src/tui/todo-indicator.tsx","../src/tui/screens.tsx","../src/tui/session-details-modal.tsx","../src/tui/settings-modal.tsx","../src/tui/todos-modal.tsx","../src/tui/turn-details-modal.tsx","../src/tui/app.tsx","../src/tui/tree-sitter.ts","../src/tui/mcps-settings.tsx","../src/tui/toggle-list-modal.tsx","../src/tui/skills-settings.tsx","../src/tui/index.tsx"],"sourcesContent":["/** @jsxImportSource @opentui/react */\nimport type { ReactNode } from 'react'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { createContext, useContext, useMemo, useState } from 'react'\nimport { useColors, useSurfaces } from '../chat/theme-context'\n\n// ---------------------------------------------------------------------------\n// Modal layer\n//\n// A single, app-wide overlay slot. `ModalRoot` renders the active node on top\n// of its children via OpenTUI's `position: 'absolute'` so the host UI stays\n// mounted underneath (state preserved, no remount on close).\n//\n// Components anywhere in the tree call `useModal().open(...)` to push a node\n// and `.close()` to dismiss. Escape is captured by `Modal` itself when it's\n// open, so the App's top-level keyboard handler doesn't fight it.\n// ---------------------------------------------------------------------------\n\ninterface ModalApi {\n open: (node: ReactNode) => void\n close: () => void\n /** Signal \"modal-like UI is on screen\" without rendering an overlay. */\n lock: () => void\n unlock: () => void\n isOpen: boolean\n}\n\nconst ModalContext = createContext<ModalApi | null>(null)\n\nexport function ModalRoot({ children }: { children: ReactNode }) {\n const [active, setActive] = useState<ReactNode | null>(null)\n const [lockCount, setLockCount] = useState(0)\n\n const api = useMemo<ModalApi>(() => ({\n open: node => setActive(node),\n close: () => setActive(null),\n lock: () => setLockCount(c => c + 1),\n unlock: () => setLockCount(c => Math.max(0, c - 1)),\n get isOpen() { return active !== null || lockCount > 0 },\n }), [active, lockCount])\n\n return (\n <ModalContext.Provider value={api}>\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n {children}\n </box>\n {active && (\n // Transparent backdrop — host UI remains visible behind. The modal is\n // opaque (see <Modal/>) and sits centered; the dimming the eye perceives\n // comes from the modal's solid panel against the surrounding content,\n // not from a backdrop fill (OpenTUI cells have no alpha to blend with).\n <box\n style={{\n position: 'absolute',\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n alignItems: 'center',\n justifyContent: 'center',\n zIndex: 100,\n }}\n >\n {active}\n </box>\n )}\n </ModalContext.Provider>\n )\n}\n\nexport function useModal(): ModalApi {\n const ctx = useContext(ModalContext)\n if (!ctx)\n throw new Error('useModal must be used inside <ModalRoot>')\n return ctx\n}\n\n/**\n * Focus computed against the modal layer.\n *\n * Pass a component's preferred focus state and this returns `false` whenever a\n * modal is open — so focused inputs (textarea, selects) release their focus and\n * stop intercepting keys behind the overlay. Pair with `focusable={false}` on\n * \"passive\" focusables (scrollbox) so the renderer doesn't cycle focus into\n * them when the primary input blurs.\n */\nexport function useModalAwareFocus(preferred: boolean = true): boolean {\n const { isOpen } = useModal()\n return preferred && !isOpen\n}\n\n// ---------------------------------------------------------------------------\n// Modal — bordered content panel with built-in esc-to-close keyboard handling.\n//\n// Consumers pass their own content (forms, selects, lists). The modal owns:\n// - border + title + minimal padding\n// - sizing constraints (width caps at a comfortable column width)\n// - escape key → onClose\n// ---------------------------------------------------------------------------\n\nexport interface ModalProps {\n title?: string\n /**\n * Secondary label rendered on the bottom border, right-aligned. Use it\n * for status / counter info that complements the top title (e.g.\n * \"3 before · 6 after\" on the turn-details modal). Kept short — long\n * strings collide with the bottom-right corner glyph.\n */\n bottomTitle?: string\n /**\n * Right-aligned overlay rendered on the **top** border, next to the\n * title slot. Use it for live counters / status badges (e.g.\n * `\"3 in progress · 2 completed\"` on the todos modal,\n * `\"src/foo.ts · child-2\"` on the file-edit modal). Same scissor-rect\n * trick as `TitleOverlay` / `CompletionPopup` — painted as an\n * absolutely-positioned sibling so OpenTUI's scissor doesn't clip\n * the border row.\n */\n rightTitle?: ReactNode\n /** Called when the user presses esc. Defaults to `useModal().close()` if available. */\n onClose?: () => void\n /**\n * When true, Modal's built-in Esc-to-close handler is suppressed for\n * this render. The child stays free to register its own `useKeyboard`\n * and handle Esc however it wants (cancel a pending confirmation,\n * block dismissal mid-async, …). `@opentui/react`'s `useKeyboard`\n * registers independent listeners with no propagation, so a child's\n * `return` inside its own handler can't stop this Modal's default\n * handler from firing on the same keystroke — `disableEscape` is the\n * declarative escape hatch that closes that race.\n */\n disableEscape?: boolean\n children: ReactNode\n /** Preferred width in columns. Modal grows to this when the terminal allows. */\n maxWidth?: number\n /** Floor on the width, so content stays minimally legible. */\n minWidth?: number\n /**\n * Hard cap on the modal's height in rows. When set, the modal grows\n * to this size when the terminal allows and shrinks down to fit on\n * smaller screens. Without it, the modal grows to fit its children\n * (uncapped) — fine for picker-style modals, problematic for\n * unbounded content (long turn previews, etc.) where the modal could\n * exceed the viewport and bury its esc-hint footer.\n */\n maxHeight?: number\n /** Columns of breathing room kept on each side of the modal. */\n horizontalMargin?: number\n /** Rows of breathing room kept above + below the modal when `maxHeight` is set. */\n verticalMargin?: number\n}\n\n/**\n * Responsive modal — picks a width (and optionally a height) based on the\n * live terminal size.\n *\n * - On a wide terminal, the modal grows to `maxWidth` so descriptions sit on\n * one line and don't wrap.\n * - On a narrow terminal, the modal shrinks down to `minWidth`, keeping a\n * small horizontal margin from the screen edges. Text inside wraps naturally.\n * - When `maxHeight` is set, the same tier logic applies on the vertical\n * axis — anything beyond is the consumer's job (typically a `scrollbox`\n * child for long content).\n *\n * Uses `useTerminalDimensions()` so it reflows on `SIGWINCH` without remount.\n */\nexport function Modal({\n title,\n bottomTitle,\n rightTitle,\n onClose,\n disableEscape = false,\n children,\n maxWidth = 92,\n minWidth = 44,\n maxHeight,\n horizontalMargin = 4,\n verticalMargin = 2,\n}: ModalProps) {\n const ctx = useContext(ModalContext)\n const dismiss = onClose ?? ctx?.close\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n\n useKeyboard((key) => {\n // `disableEscape` is the declarative gate the child uses to take over\n // Esc handling for a transient state (pending confirmation, in-flight\n // async). Without it the Modal's listener fires alongside the child's\n // own `useKeyboard` — they're sibling listeners, not a propagation\n // chain — and the user's Esc keystroke would clear the pending state\n // AND close the modal in the same tick.\n if (key.name === 'escape' && !disableEscape)\n dismiss?.()\n })\n\n const { width: termWidth, height: termHeight } = useTerminalDimensions()\n const width = Math.max(minWidth, Math.min(maxWidth, termWidth - horizontalMargin * 2))\n const height = maxHeight === undefined\n ? undefined\n : Math.min(maxHeight, Math.max(0, termHeight - verticalMargin * 2))\n\n const panel = (\n <box\n title={title ? ` ${title} ` : undefined}\n bottomTitle={bottomTitle ? ` ${bottomTitle} ` : undefined}\n bottomTitleAlignment=\"right\"\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n backgroundColor: SURFACE.modal,\n paddingTop: 1,\n paddingBottom: 1,\n paddingLeft: 2,\n paddingRight: 2,\n width,\n ...(height !== undefined ? { height } : {}),\n flexDirection: 'column',\n gap: 1,\n }}\n >\n {children}\n </box>\n )\n\n // No `rightTitle` → emit the bordered panel as-is so existing\n // consumers see no tree change. With `rightTitle` → wrap the panel\n // so the overlay can paint as an absolutely-positioned sibling\n // (a child of the bordered box can't ride the top border —\n // OpenTUI's scissor rect excludes it; a sibling outside the panel\n // can). Same trick as TitleOverlay / CompletionPopup.\n if (rightTitle == null)\n return panel\n return (\n <box style={{ width, flexDirection: 'column' }}>\n {panel}\n <box style={{ position: 'absolute', top: 0, right: 1 }}>\n {rightTitle}\n </box>\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { AgentProfile, AgentRegistry } from '../chat/agents'\nimport { useMemo } from 'react'\nimport { useColors, useSelectStyle } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/** Cap the scroll window — a long custom registry shouldn't push the modal off-screen. */\nconst VISIBLE_ROW_CAP = 10\n\n/**\n * Modal that lists the registered {@link AgentProfile}s and lets the user\n * pick one. Rows show: `● selected · label description`.\n *\n * The accent column is intentionally compact (single-char marker) — the\n * profile's `accent` color is read from the active theme so Build and Plan\n * stand apart at a glance without taking horizontal space.\n *\n * Used by `App` (Ctrl+A binding) and exported for hosts that want to drive\n * agent switching from elsewhere in their own composition.\n */\nexport function AgentPickerModal({\n agents,\n currentAgentId,\n onPick,\n}: {\n agents: AgentRegistry\n currentAgentId: string\n onPick: (id: string) => void\n}) {\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n\n const profiles = useMemo(() => Object.values(agents), [agents])\n\n const initialIndex = useMemo(\n () => profiles.findIndex(p => p.id === currentAgentId),\n [profiles, currentAgentId],\n )\n\n const options = useMemo(\n () => profiles.map(p => ({\n name: `${p.id === currentAgentId ? '● ' : ' '}${p.label}`,\n description: p.description,\n value: p.id,\n })),\n [profiles, currentAgentId],\n )\n\n if (profiles.length === 0)\n return <EmptyState />\n\n const visibleRows = Math.min(options.length, VISIBLE_ROW_CAP)\n const currentMissing = initialIndex < 0\n // `<select>` requires a non-negative index; clamp here and surface the\n // mismatch via a banner so the user knows their persisted profile is gone.\n const safeIndex = currentMissing ? 0 : initialIndex\n\n return (\n <Modal title=\"select agent\">\n {currentMissing && (\n <text fg={COLOR.warn}>\n {`Current agent \"${currentAgentId}\" is not in this registry — pick one below.`}\n </text>\n )}\n <select\n {...SELECT_THEME}\n focused\n options={options}\n wrapSelection\n selectedIndex={safeIndex}\n showScrollIndicator={options.length > visibleRows}\n style={{ height: visibleRows }}\n onSelect={(_idx, option) => {\n if (option)\n onPick(option.value as string)\n }}\n />\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n\nfunction EmptyState() {\n const COLOR = useColors()\n return (\n <Modal title=\"select agent\">\n <text fg={COLOR.dim}>No agents registered.</text>\n <text fg={COLOR.mute}>\n Pass an\n <span fg={COLOR.model}>{' agents '}</span>\n registry to\n <span fg={COLOR.model}>{' runTui({ agents }) '}</span>\n to populate this list.\n </text>\n </Modal>\n )\n}\n\n// `accentColor` now lives in `src/chat/agents.ts` (renderer-agnostic).\n// Re-exported below for back-compat with TUI consumers that imported it\n// from `zidane/tui`; new code should import from `zidane/chat` directly.\nexport { accentColor } from '../chat/agents'\n\n/** Re-export so consumers don't need to import the type from `zidane/chat` separately. */\nexport type { AgentProfile }\n","/** @jsxImportSource @opentui/react */\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useEffect, useState } from 'react'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/**\n * Modal that lists currently-dispatching tool calls and lets the user\n * cancel one (or all) without aborting the entire run. Mirrors the\n * `EffortPickerModal` / `AgentPicker` shape — vertical row list, arrow\n * keys navigate, `↵` commits the focused row, `esc` dismisses.\n *\n * Why a dedicated modal rather than re-using the existing turn selection?\n *\n * - Tools fire in parallel inside a single assistant turn. The user\n * needs to *target one specific call* by `callId`, not \"the most\n * recent tool row\". A list picker maps to the underlying data\n * (Map<callId, …>) directly.\n * - The set is highly transient — calls can finish between keystrokes.\n * Building the picker around a fresh snapshot on each mount means\n * each open is consistent with the live state at that moment.\n *\n * `inFlight` is consumed by reference; the modal does NOT subscribe to\n * further updates while open. That's intentional — re-rendering rows\n * out from under the keyboard cursor would surprise the user. The\n * caller closes + reopens the modal if it wants a fresh snapshot\n * (or — more typically — fires the cancel and lets the next open\n * reflect the post-cancel state).\n */\n\n/** One cancellable entry, as the picker sees it. */\nexport interface InFlightToolCall {\n /**\n * `'tool'` — a mid-dispatch tool call addressable via\n * `agent.cancelTool(callId)`. `id` is the `callId`.\n *\n * `'task'` — a running background task (spawned via\n * `shell({ run_in_background: true })`) addressable via\n * `agent.killBackgroundTask(taskId)`. `id` is the `taskId`.\n *\n * The two cancel paths are different primitives — per-call abort\n * signal flip vs SIGTERM-the-process-group — so the picker carries\n * the discriminator and the dispatch callback routes based on it.\n * Optional + defaulting to `'tool'` so older callers stay\n * source-compatible.\n */\n kind?: 'tool' | 'task'\n /** `callId` for tool calls, `taskId` for background tasks. */\n callId: string\n /** Display label — tool name for tool calls, command preview for tasks. */\n tool: string\n /** `Date.now()` when the entry started. */\n startedAt: number\n /** Subagent label (`child-N`) when the call belongs to a subagent, else undefined. */\n childId?: string\n}\n\ninterface Props {\n /** Snapshot of the live in-flight map at modal open time. */\n inFlight: readonly InFlightToolCall[]\n /**\n * Cancel one entry. Returns `true` when the cancel was dispatched\n * (matches `agent.cancelTool`'s contract; for tasks the boolean is\n * synthesized after the async kill settles). The picker passes the\n * full entry so the host can route to the right primitive without\n * a second lookup.\n */\n onCancel: (entry: InFlightToolCall, reason?: string) => boolean | Promise<boolean>\n /** Cancel every entry in the snapshot. */\n onCancelAll: () => void\n /** Dismiss the modal without cancelling anything. */\n onClose: () => void\n}\n\nexport function CancelToolModal({ inFlight, onCancel, onCancelAll, onClose }: Props) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const { width: termWidth } = useTerminalDimensions()\n const [selectedIdx, setSelectedIdx] = useState(0)\n\n // Snapshot the list at mount so a tool finishing mid-pick doesn't\n // shift indices under the keyboard cursor. The `key` deps below tie\n // re-snapshotting to caller-driven changes (close + reopen), which\n // is the intended path.\n const rows = inFlight\n const empty = rows.length === 0\n\n // Auto-close once the last row clears. Without this the user is left\n // staring at an empty list with no obvious next step. Trigger it via\n // an effect so the modal renders the \"no calls\" state for at least\n // one frame — keeps the UX legible if a call finishes precisely when\n // the modal mounts.\n useEffect(() => {\n if (!empty)\n return\n const t = setTimeout(onClose, 500)\n return () => clearTimeout(t)\n }, [empty, onClose])\n\n const safeIndex = empty ? 0 : Math.min(selectedIdx, rows.length - 1)\n\n useKeyboard((key) => {\n if (empty)\n return\n if (key.name === 'up') {\n setSelectedIdx(i => ((i - 1) % rows.length + rows.length) % rows.length)\n return\n }\n if (key.name === 'down') {\n setSelectedIdx(i => (i + 1) % rows.length)\n return\n }\n if (key.name === 'return') {\n const row = rows[safeIndex]\n if (row) {\n // `onCancel` may be async for background tasks (the kill awaits\n // SIGTERM + stream flush). Fire-and-forget — the modal closes\n // immediately so the user isn't held up by the round-trip;\n // the `background:exit` listener in `app.tsx` paints the\n // banner once the kill lands.\n const result = onCancel(row, 'user-clicked-cancel')\n if (result instanceof Promise)\n result.catch(() => { /* errors surface via the next background:exit */ })\n }\n onClose()\n return\n }\n // `a` cancels every call in the snapshot. Cheap escape hatch when\n // multiple tools are wedged at once; the modal closes immediately\n // after so the user can verify the cancellations took.\n if (key.name === 'a') {\n onCancelAll()\n onClose()\n }\n })\n\n // Width budget for the elapsed column — fixed so rows align even when\n // tool names vary wildly.\n const elapsedColWidth = 8\n const callIdColWidth = 14\n const childColWidth = 10\n\n return (\n <Modal\n title=\"cancel tool call\"\n bottomTitle={empty ? 'no calls in flight' : `${rows.length} in flight`}\n maxWidth={Math.min(96, Math.max(64, termWidth - 8))}\n minWidth={56}\n onClose={onClose}\n >\n {empty\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>no tool calls are currently in flight — </span>\n <span fg={COLOR.dim}>nothing to cancel.</span>\n </text>\n )\n : (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {rows.map((row, i) => (\n <CancelToolRow\n key={row.callId}\n row={row}\n isFocused={i === safeIndex}\n highlightBg={SURFACE.selection}\n elapsedColWidth={elapsedColWidth}\n callIdColWidth={callIdColWidth}\n childColWidth={childColWidth}\n />\n ))}\n </box>\n )}\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' cancel selected · '}\n <span fg={COLOR.warn}>a</span>\n {' cancel all · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n\nfunction CancelToolRow({\n row,\n isFocused,\n highlightBg,\n elapsedColWidth,\n callIdColWidth,\n childColWidth,\n}: {\n row: InFlightToolCall\n isFocused: boolean\n highlightBg: string\n elapsedColWidth: number\n callIdColWidth: number\n childColWidth: number\n}) {\n const COLOR = useColors()\n const elapsed = formatElapsed(Date.now() - row.startedAt).padStart(elapsedColWidth, ' ')\n const idLabel = truncate(row.callId, callIdColWidth).padEnd(callIdColWidth, ' ')\n const childLabel = (row.childId ? `· ${row.childId}` : '').padEnd(childColWidth, ' ')\n\n // Glyph differentiates a mid-dispatch tool call from a backgrounded\n // task: `⌁` matches the task-notification banner so the user reads\n // \"this row is the same kind of thing as that banner\".\n const kindGlyph = row.kind === 'task' ? '⌁' : '·'\n\n return (\n <box\n style={{\n height: 1,\n paddingLeft: 1,\n paddingRight: 1,\n flexShrink: 0,\n backgroundColor: isFocused ? highlightBg : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isFocused ? COLOR.brand : COLOR.mute}>{isFocused ? '›' : ' '}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isFocused ? COLOR.warn : COLOR.mute}>{kindGlyph}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isFocused ? COLOR.brand : COLOR.dim}>{row.tool}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.mute}>{idLabel}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.mute}>{childLabel}</span>\n <span fg={COLOR.warn}>{elapsed}</span>\n </text>\n </box>\n )\n}\n\n/**\n * Format a sub-minute duration as `0.3s` / `7.4s`; minute-plus as `2m12s`.\n * Tight + monospace-friendly so the column stays right-aligned.\n */\nfunction formatElapsed(ms: number): string {\n if (ms < 0)\n return '0.0s'\n if (ms < 60_000) {\n const seconds = ms / 1000\n return `${seconds.toFixed(1)}s`\n }\n const totalSeconds = Math.floor(ms / 1000)\n const minutes = Math.floor(totalSeconds / 60)\n const seconds = totalSeconds % 60\n return `${minutes}m${String(seconds).padStart(2, '0')}s`\n}\n\nfunction truncate(s: string, max: number): string {\n if (s.length <= max)\n return s\n if (max <= 1)\n return s.slice(0, max)\n return `${s.slice(0, max - 1)}…`\n}\n","/**\n * Two-pronged clipboard write.\n *\n * 1. **OSC 52** — `\\x1b]52;c;<base64>\\x07`. Zero deps, works over SSH\n * (lands on the user's LOCAL machine clipboard, which is the whole\n * point on a remote box). Honored by recent macOS Terminal, iTerm2,\n * kitty, alacritty, wezterm, Ghostty (when `clipboard-write = allow`),\n * tmux / screen with passthrough enabled. Older / stricter setups\n * silently drop it — we have no way to detect that in-band.\n * 2. **Native helper** — `pbcopy` (macOS), `wl-copy` (Wayland Linux),\n * `xclip` (X11 Linux), `clip.exe` (Windows / WSL). Reliable on a\n * LOCAL session regardless of the terminal emulator's clipboard\n * policy, but useless under SSH — they target the remote box's\n * clipboard, not the user's.\n *\n * We try both. OSC 52 is the only thing that matters over SSH; the\n * native helper is the safety net for local sessions where the user's\n * terminal denies OSC 52 access (Ghostty's default `clipboard-write =\n * false` is the common case on macOS). Either landing the text in the\n * OS clipboard means `cmd+v` Just Works™ in the next app, without the\n * user ever having to think about `cmd+c`.\n *\n * Returns `true` if any path appeared to succeed, `false` if every\n * available path failed. Callers can reflect the result in UX (toast /\n * inline confirmation) but should not treat `false` as catastrophic —\n * the user's terminal may still have honored OSC 52 silently.\n */\n\nimport { Buffer } from 'node:buffer'\nimport { spawn } from 'node:child_process'\n\n/**\n * Pick a platform-appropriate native clipboard helper. Returns `null`\n * when no helper is plausibly available (e.g. headless Linux without\n * X11 or Wayland) — the caller still gets the OSC 52 path.\n *\n * We pick the FIRST plausible helper rather than probing each binary\n * with `which` so the hot path stays sync. `spawn` itself emits an\n * `error` event when the binary is missing; we listen for it and\n * swallow, so a wrong guess is harmless.\n */\nfunction pickHelper(): { cmd: string, args: readonly string[] } | null {\n if (process.platform === 'darwin')\n return { cmd: 'pbcopy', args: [] }\n if (process.platform === 'win32')\n return { cmd: 'clip', args: [] }\n // Linux / *BSD — prefer Wayland when present, fall back to X11.\n if (process.env.WAYLAND_DISPLAY)\n return { cmd: 'wl-copy', args: [] }\n if (process.env.DISPLAY)\n return { cmd: 'xclip', args: ['-selection', 'clipboard'] }\n return null\n}\n\n/**\n * Spawn the helper and pipe `text` over stdin. Fire-and-forget: we\n * don't await the child's exit because the OS clipboard is populated\n * synchronously by `pbcopy` / `clip.exe` / `wl-copy` / `xclip` once\n * stdin closes, and a typical drag-select is human-scale latency we\n * don't need to gate the next frame on.\n *\n * Returns `true` if the spawn looked plausibly successful, `false`\n * if we couldn't even attach to the child's stdin (binary missing,\n * permission denied, etc.). An `error` event after spawn flips the\n * returned value to `false` retroactively — but by then the caller\n * has already seen `true` and moved on. We rely on the boolean only\n * for the synchronous \"should we report progress to the user?\" hint,\n * not as a hard contract.\n */\nfunction spawnHelper(text: string): boolean {\n const helper = pickHelper()\n if (!helper)\n return false\n try {\n const child = spawn(helper.cmd, [...helper.args], {\n stdio: ['pipe', 'ignore', 'ignore'],\n })\n // Eat `ENOENT` etc. so an absent helper (xclip not installed) is\n // just a silent fallback to OSC 52, not a process-wide crash.\n child.on('error', () => {})\n child.stdin?.on('error', () => {})\n child.stdin?.end(text, 'utf8')\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * Emit the OSC 52 sequence on stdout. Skips silently when stdout\n * isn't a TTY so `bun run app | tee log` doesn't dump literal escape\n * bytes into a pipe.\n */\nfunction writeOsc52(text: string): boolean {\n if (typeof process === 'undefined' || !process.stdout?.write)\n return false\n if (!process.stdout.isTTY)\n return false\n try {\n // OSC 52 caps payloads at the terminal's discretion. Most honor at\n // least 8 KB; some legacy emulators cut at ~1 KB. We don't enforce\n // a limit ourselves — if a terminal truncates, the user pastes a\n // truncated string and notices, which is preferable to silently\n // refusing to copy large outputs.\n const encoded = Buffer.from(text, 'utf8').toString('base64')\n process.stdout.write(`\\x1B]52;c;${encoded}\\x07`)\n return true\n }\n catch {\n return false\n }\n}\n\nexport function writeToClipboard(text: string): boolean {\n // Always try OSC 52 first — it's free, doesn't fork a process, and\n // it's the only thing that lands on the user's clipboard over SSH.\n const osc = writeOsc52(text)\n // Then also poke the native helper — belt-and-suspenders for local\n // sessions where the user's terminal has OSC 52 disabled. On SSH\n // this writes to the remote box's clipboard (harmless, just unused).\n const helper = spawnHelper(text)\n return osc || helper\n}\n","/** @jsxImportSource @opentui/react */\n// ---------------------------------------------------------------------------\n// CrushThrobber — port of Charm's Crush `internal/ui/anim/Anim`.\n//\n// A row of `size` cells that cycle a random hex/symbol rune per cell per\n// frame, each painted from a left-drifting HSL gradient ramp blended\n// A→B→A→B. Cells \"wake\" individually after a staggered birth offset\n// (0–1 s); until then they show `.` so the throbber appears to materialize\n// rather than pop in. Once every cell is alive, an animated ellipsis\n// (`\"\"` → `\".\"` → `\"..\"` → `\"...\"`, advancing every 8 frames) cycles after\n// the label.\n//\n// Differences from the Go original:\n// - We blend in HSL, not HCL (no `go-colorful` equivalent in our deps).\n// Saturated palette pairs (e.g. Crush's Charple→Dolly) stay vivid;\n// more disparate pairs may pass through slightly different hues.\n// - Rune randomization happens in `render` instead of being prebaked\n// into frame arrays. Cheap enough at 20 fps for ~15 cells and avoids\n// a fixed loop period.\n// ---------------------------------------------------------------------------\n\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { blendHsl } from '../chat/color-gradient'\nimport { useColors } from '../chat/theme-context'\n\n// Character set the throbber cycles through. Verbatim from\n// `charmbracelet/crush/internal/ui/anim/anim.go`'s `availableRunes`.\nconst CRUSH_RUNES = '0123456789abcdefABCDEF~!@#$£€%^&*()+=_'\n// 20 fps — matches Crush's `fps` constant.\nconst TICK_MS = 50\n// Ellipsis cycle order. Note the empty string at the end: Crush walks\n// \".\" → \"..\" → \"...\" → \"\" so there's a beat of \"no dots\" between cycles.\nconst ELLIPSIS_FRAMES = ['.', '..', '...', '']\n// How many ticks per ellipsis frame. 8 * 50 ms = 400 ms per step,\n// matching Crush's `ellipsisAnimSpeed`.\nconst ELLIPSIS_TICKS = 8\n// Upper bound on staggered birth — every cell wakes within this window.\nconst BIRTH_MAX_MS = 1000\n\ninterface CrushThrobberProps {\n /** Trailing label text, painted in `labelColor`. Omit for glyphs only. */\n label?: string\n /**\n * Cycling-character width. Default 15 — Crush's `AssistantMessageItem`\n * size. Sub-1 values are clamped to 1.\n */\n size?: number\n /** Gradient start color (hex). */\n from: string\n /** Gradient end color (hex). */\n to: string\n /**\n * Label / ellipsis tint. Defaults to the theme's body-dim color so the\n * label reads as secondary against the rainbow cycling chars.\n */\n labelColor?: string\n}\n\n/**\n * Build a gradient ramp of length `n` traversing the keys `from → to →\n * from → to` (matching Crush's `CycleColors=true` mode, which prerenders\n * `width*3` stops across four anchor points so the drift can loop without\n * a visible seam).\n */\nfunction buildCycleRamp(from: string, to: string, n: number): string[] {\n // Crush passes four stops `[A, B, A, B]` to `makeGradientRamp`, which\n // splits the requested length across three equal segments. We mirror\n // that arithmetic so the visual cadence lines up.\n const segLen = Math.floor(n / 3)\n const ramp: string[] = []\n for (let i = 0; i < segLen; i++)\n ramp.push(blendHsl(from, to, i / segLen))\n for (let i = 0; i < segLen; i++)\n ramp.push(blendHsl(to, from, i / segLen))\n const tail = n - 2 * segLen\n for (let i = 0; i < tail; i++)\n ramp.push(blendHsl(from, to, i / Math.max(1, tail)))\n return ramp\n}\n\nexport function CrushThrobber({\n label,\n size = 15,\n from,\n to,\n labelColor,\n}: CrushThrobberProps) {\n const COLOR = useColors()\n const cells = Math.max(1, size)\n\n // Frame counter ticks at 20 fps. We rerender every tick so the random\n // runes refresh and the gradient offset shifts left by one slot.\n const [frame, setFrame] = useState(0)\n useEffect(() => {\n const id = setInterval(() => setFrame(f => f + 1), TICK_MS)\n return () => clearInterval(id)\n }, [])\n\n // Birth offsets per cell, randomized on mount and on size change.\n // Each cell stays as `.` until `elapsed` passes its offset, giving the\n // staggered \"fade-in\" Crush opens with.\n const birthOffsets = useMemo(\n () => Array.from({ length: cells }, () => Math.random() * BIRTH_MAX_MS),\n [cells],\n )\n\n // Wall-clock anchor for the birth animation. Kept in a ref so it\n // survives renders without retriggering the gradient memo.\n const startRef = useRef(0)\n if (startRef.current === 0)\n startRef.current = Date.now()\n\n // Precompute the gradient ramp. `cells * 3` stops match Crush's\n // `width*3` ramp size for cycle mode.\n const ramp = useMemo(() => buildCycleRamp(from, to, cells * 3), [from, to, cells])\n\n const elapsed = Date.now() - startRef.current\n const initialized = elapsed >= BIRTH_MAX_MS\n\n // Sliding window into the ramp — `frame % ramp.length` is the left\n // edge; each cell reads `ramp[(frame + i) % ramp.length]`. This is the\n // same \"shift left by one each frame\" effect Crush gets from its\n // `offset++` loop when prebaking cycle frames.\n const rampLen = ramp.length\n const offset = ((frame % rampLen) + rampLen) % rampLen\n\n const labelTint = labelColor ?? COLOR.dim\n\n const ellipsis = initialized && label\n ? ELLIPSIS_FRAMES[Math.floor(frame / ELLIPSIS_TICKS) % ELLIPSIS_FRAMES.length]\n : ''\n\n return (\n <text>\n {Array.from({ length: cells }, (_, i) => {\n const alive = initialized || elapsed >= birthOffsets[i]\n const ch = alive\n ? CRUSH_RUNES[Math.floor(Math.random() * CRUSH_RUNES.length)]\n : '.'\n const fg = ramp[(offset + i) % rampLen]\n return <span key={i} fg={fg}>{ch}</span>\n })}\n {label !== undefined && (\n <>\n <span fg={labelTint}>{` ${label}`}</span>\n <span fg={labelTint}>{ellipsis}</span>\n </>\n )}\n </text>\n )\n}\n","import type { TextareaRenderable } from '@opentui/core'\nimport type { ReactNode, RefObject } from 'react'\nimport type { Theme } from '../chat/theme'\nimport { RGBA, SyntaxStyle } from '@opentui/core'\nimport { createContext, createElement, useContext, useEffect, useMemo } from 'react'\nimport { useTheme } from '../chat/theme-context'\n\ninterface StyleEntry {\n fg?: RGBA\n bg?: RGBA\n bold?: boolean\n italic?: boolean\n underline?: boolean\n dim?: boolean\n}\n\n/**\n * Convert the renderer-agnostic `Theme.syntax` map (hex strings + plain\n * booleans) into an OpenTUI `SyntaxStyle`. Used both for the markdown\n * structural captures (`markup.heading`, `markup.bold`, …) and the\n * embedded Tree-sitter language tokens (`keyword`, `string`, `function`,\n * …) — OpenTUI's `<markdown>` re-uses the same `SyntaxStyle` for the\n * fenced-code renderable, so one table drives both surfaces.\n *\n * `overrides`, when set, replaces individual entries by token name —\n * each override is merged INTO the resolved entry (shallow). Used by\n * the \"on-selection\" variant to swap `markup.raw.bg` for the row's\n * selection surface so inline code chips blend into the highlight\n * rather than reading as \"punched out\" rectangles. Top-level keys\n * absent from the base theme become brand-new entries.\n */\nexport function buildMdStyle(theme: Theme, overrides?: Record<string, Partial<StyleEntry>>): SyntaxStyle {\n const styles: Record<string, StyleEntry> = {}\n for (const [token, style] of Object.entries(theme.syntax)) {\n const out: StyleEntry = {}\n if (style.fg)\n out.fg = RGBA.fromHex(style.fg)\n if (style.bg)\n out.bg = RGBA.fromHex(style.bg)\n if (style.bold)\n out.bold = true\n if (style.italic)\n out.italic = true\n if (style.underline)\n out.underline = true\n if (style.dim)\n out.dim = true\n styles[token] = out\n }\n if (overrides) {\n for (const [token, patch] of Object.entries(overrides))\n styles[token] = { ...(styles[token] ?? {}), ...patch }\n }\n return SyntaxStyle.fromStyles(styles)\n}\n\n// ---------------------------------------------------------------------------\n// MdStyleProvider\n//\n// `SyntaxStyle.fromStyles(...)` allocates a sizable, pure-function-of-theme\n// object that every `<markdown>` instance can share. Building it inside a\n// per-instance `useMemo` (the previous approach) re-ran the conversion N\n// times on a theme switch — N being the number of markdown blocks in the\n// active transcript, easily 50+ on a long session.\n//\n// This context computes it once per theme and broadcasts the result to\n// every `useMdStyle()` consumer. Mounted by `ThemedShell` in app.tsx,\n// directly underneath `ThemeProvider` so the upstream theme is in scope.\n// ---------------------------------------------------------------------------\n\ninterface MdStyleBundle {\n /** Style used by every non-selected markdown row. */\n regular: SyntaxStyle\n /**\n * Variant painted when the enclosing row carries the selection\n * background. Inline-code chips (`markup.raw`) and fenced-code\n * blocks (`markup.raw.block`) get their `bg` rewritten to the row's\n * selection surface so they blend into the highlight instead of\n * reading as \"punched out\" rectangles against the selection.\n */\n selected: SyntaxStyle\n}\n\nconst MdStyleContext = createContext<MdStyleBundle | null>(null)\n\nexport function MdStyleProvider({ children }: { children: ReactNode }) {\n const theme = useTheme()\n const bundle = useMemo<MdStyleBundle>(() => {\n const selectionBg = RGBA.fromHex(theme.surfaces.selection)\n return {\n regular: buildMdStyle(theme),\n selected: buildMdStyle(theme, {\n 'markup.raw': { bg: selectionBg },\n 'markup.raw.block': { bg: selectionBg },\n }),\n }\n }, [theme])\n // Authored with `createElement` rather than JSX so this module stays a\n // plain `.ts` file — keeps the OpenTUI `jsxImportSource` pragma noise\n // out of a one-element provider definition.\n return createElement(MdStyleContext.Provider, { value: bundle }, children)\n}\n\n/**\n * Active markdown / syntax-highlighting style. Returns a single shared\n * `SyntaxStyle` instance for the active theme — built once at provider\n * mount, re-built on theme switch. A `Settings.theme` flip re-paints every\n * `<markdown>` that reads this hook.\n *\n * Pass `{ selected: true }` to get the on-selection variant where inline\n * code chips' background matches the row's selection surface (so the\n * chips blend into the highlight rather than reading as punched-out\n * rectangles).\n *\n * Throws if used outside `<MdStyleProvider>` so a missing wiring shows up\n * loudly in development rather than silently rendering plain text.\n */\nexport function useMdStyle(opts: { selected?: boolean } = {}): SyntaxStyle {\n const bundle = useContext(MdStyleContext)\n if (!bundle)\n throw new Error('useMdStyle must be used inside <MdStyleProvider>')\n return opts.selected ? bundle.selected : bundle.regular\n}\n\n// ---------------------------------------------------------------------------\n// Chip style — bg/fg for in-textarea completion references.\n//\n// The submitted echo paints chip backgrounds via flex-row `<text bg=…>`\n// segments (see `UserPromptBlock`), but the live `<textarea>` is one\n// `EditBufferRenderable` that can't host per-span React children. OpenTUI\n// exposes per-range styling for edit buffers via `SyntaxStyle` +\n// `addHighlightByCharRange`, so we register one styleId per chip kind in\n// the theme's `surfaces.chips` map and resolve the right one per ref at\n// highlight time. `PromptBlock` pulls the styleId via `resolveChipStyleId`\n// on every `completion.references` change.\n// ---------------------------------------------------------------------------\n\nconst CHIP_TOKEN_PREFIX = 'completion.reference'\n\n/** Per-kind token name in the chip `SyntaxStyle` — e.g. `completion.reference.skills`. */\nexport function chipTokenFor(providerId: string): string {\n return `${CHIP_TOKEN_PREFIX}.${providerId}`\n}\n\n/** Fallback token registered for every theme — always resolves to a styleId. */\nexport const CHIP_TOKEN_DEFAULT = chipTokenFor('default')\n\nexport function buildChipStyle(theme: Theme): SyntaxStyle {\n const styles: Record<string, { fg: RGBA, bg: RGBA }> = {}\n // `theme.surfaces.chips` is `{ default: ChipColor } & Partial<…>`; the\n // partial half means `Object.entries` can theoretically surface `undefined`\n // values (an `enabledKey: undefined` slot). Skip those defensively so the\n // RGBA conversion never trips on a missing pair.\n for (const [providerId, chip] of Object.entries(theme.surfaces.chips)) {\n if (!chip)\n continue\n styles[chipTokenFor(providerId)] = {\n fg: RGBA.fromHex(chip.fg),\n bg: RGBA.fromHex(chip.bg),\n }\n }\n return SyntaxStyle.fromStyles(styles)\n}\n\n/**\n * Resolve the styleId for a chip of the given provider id, falling back\n * to {@link CHIP_TOKEN_DEFAULT} when the theme has no kind-specific\n * entry. Returns `null` only when the style was built from a malformed\n * theme (missing `default`) — every built-in theme satisfies the\n * contract, so callers can treat `null` as \"skip highlight\".\n */\nexport function resolveChipStyleId(style: SyntaxStyle, providerId: string): number | null {\n return style.getStyleId(chipTokenFor(providerId)) ?? style.getStyleId(CHIP_TOKEN_DEFAULT)\n}\n\n/**\n * Convert a JS string offset in `text` to the column offset expected by\n * OpenTUI's `addHighlightByCharRange`. The native API takes display-\n * column offsets that EXCLUDE newlines (each `\\n` consumes zero\n * columns) — mirroring the convention documented in `@opentui/core`'s\n * extmark wrapper. Skipping the conversion paints chips at the wrong\n * column once the prompt spans multiple lines, drifting one column\n * further left per preceding newline.\n *\n * Single-cell text covers every chip kind the built-in providers emit\n * (slash-commands + `@`-prefixed file paths). Wide-cell graphemes\n * (emoji, CJK) would need `stringWidth` accounting; left as a TODO\n * until a chip kind actually carries them.\n */\nexport function offsetToHighlightColumn(text: string, offset: number): number {\n const clamped = Math.max(0, Math.min(offset, text.length))\n let newlines = 0\n for (let i = 0; i < clamped; i++) {\n // `charCodeAt` over indexOf-in-loop: avoids substring allocations\n // when refs sit deep in a long buffer.\n if (text.charCodeAt(i) === 10)\n newlines++\n }\n return clamped - newlines\n}\n\nconst ChipStyleContext = createContext<SyntaxStyle | null>(null)\n\nexport function ChipStyleProvider({ children }: { children: ReactNode }) {\n const theme = useTheme()\n const style = useMemo(() => buildChipStyle(theme), [theme])\n return createElement(ChipStyleContext.Provider, { value: style }, children)\n}\n\n/**\n * Active chip-highlight style for the prompt textarea. Single shared\n * instance per theme so the underlying buffer style table is re-allocated\n * only on a theme switch.\n */\nexport function useChipStyle(): SyntaxStyle {\n const style = useContext(ChipStyleContext)\n if (!style)\n throw new Error('useChipStyle must be used inside <ChipStyleProvider>')\n return style\n}\n\n/**\n * Minimal ref shape consumed by {@link useChipHighlights} — `start`/`end`\n * are JS string offsets into the textarea's `plainText`, `providerId`\n * picks the chip color via {@link resolveChipStyleId}. Structurally\n * assignable from `CompletionReference<unknown>` so the prompt block can\n * pass `completion.references` verbatim.\n */\nexport interface ChipHighlightRef {\n start: number\n end: number\n providerId: string\n}\n\n/**\n * Sync per-range chip highlights onto a textarea on every `references`\n * change. Encapsulates the OpenTUI plumbing — `clearAllHighlights` +\n * one `addHighlightByCharRange` per ref, with JS→column-offset\n * translation — so the prompt block stays focused on UX state.\n *\n * The hook owns the contract documented in\n * {@link offsetToHighlightColumn}: refs carry JS string offsets that\n * include newlines; the edit buffer's highlight API takes display\n * columns that exclude them. Skipping this conversion is the difference\n * between stable multi-line chips and the one-column-per-newline drift\n * we shipped in the first cut.\n */\nexport function useChipHighlights(\n textareaRef: RefObject<TextareaRenderable | null>,\n references: readonly ChipHighlightRef[],\n chipStyle: SyntaxStyle,\n): void {\n useEffect(() => {\n const ta = textareaRef.current\n if (!ta)\n return\n ta.clearAllHighlights()\n const text = ta.plainText\n for (const ref of references) {\n const styleId = resolveChipStyleId(chipStyle, ref.providerId)\n if (styleId == null)\n continue\n ta.addHighlightByCharRange({\n start: offsetToHighlightColumn(text, ref.start),\n end: offsetToHighlightColumn(text, ref.end),\n styleId,\n })\n }\n }, [textareaRef, references, chipStyle])\n}\n","/** @jsxImportSource @opentui/react */\nimport type { CliRenderer, RenderNodeContext, ScrollBoxRenderable } from '@opentui/core'\nimport type { Token } from 'marked'\nimport type { MutableRefObject, ReactNode } from 'react'\nimport type { Hint } from '../chat/hints'\nimport type { ThemeColors, ThemeSurfaces } from '../chat/theme'\nimport type { TranscriptItem } from '../chat/transcript-anchors'\nimport type { EditOutcome, EditPayload, Settings, StreamEvent } from '../chat/types'\nimport { BoxRenderable, CodeRenderable, getTreeSitterClient, stripAnsiSequences, TextRenderable } from '@opentui/core'\nimport { useRenderer, useTerminalDimensions } from '@opentui/react'\nimport { memo, useEffect, useMemo, useRef, useState } from 'react'\nimport { buildLinearRamp } from '../chat/color-gradient'\nimport { summarizeOutcomes } from '../chat/edit-approval'\nimport { buildContextualDiff, buildUnifiedDiff, filetypeFromPath, summarizeEditPayload } from '../chat/edit-diff'\nimport { compactPath, fmtTokens, formatDuration, formatTaskStatus } from '../chat/format'\nimport { clipHintsToWidth, hintsLength, truncateTrailing } from '../chat/hints'\nimport { splitPromptSegments } from '../chat/prompt-segments'\nimport { useSettings } from '../chat/settings-context'\nimport { isTurnHighlighted, isVisible, marginTopFor, turnSelectionOwnership } from '../chat/store'\nimport { resolveChipColor } from '../chat/theme'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { TODO_STATUS_GLYPHS, TODOWRITE_TOOL } from '../chat/todos'\nimport { displayNameFor, formatToolCall } from '../chat/tool-formatters'\nimport { computeTurnAnchors } from '../chat/transcript-anchors'\nimport { writeToClipboard } from './clipboard'\nimport { CrushThrobber } from './crush-throbber'\nimport { useMdStyle } from './theme'\n\n/**\n * Memoized so a flush that mutates only the trailing event doesn't force the\n * entire transcript to re-render. Each event holds a stable reference until\n * its content changes (we only ever recreate the streaming-markdown tail).\n *\n * The outer wrapper handles top-margin per kind (and per neighbor) so spacing\n * is the single source of truth for inter-event breathing room. Selected\n * rows fill with `surfaces.selection` and absorb their `marginTop` as\n * `paddingTop` — that keeps the gap above colored too so consecutive\n * same-turn events read as one continuous highlighted block instead of a\n * striped list.\n */\nconst EventLine = memo(\n ({ event, previous, depthOffset = 0, selected = false, anchorId, hideChildLabel = false }: {\n event: StreamEvent\n previous?: StreamEvent\n /**\n * Subtract from the event's depth before computing left indent. The\n * subagent box already provides one level of visual offset via its\n * border + padding, so events rendered inside it pass `depthOffset: 1`\n * to avoid double-indenting.\n */\n depthOffset?: number\n /** Fills the row with the theme's selection background. */\n selected?: boolean\n /**\n * Stable render id targeted by `ScrollBoxRenderable.scrollChildIntoView`.\n * Only set on the first event of each turn so the scroll target is\n * unambiguous — see `computeTurnAnchors`.\n */\n anchorId?: string\n /**\n * Suppress the `child-N` label on subagent `spawn-start` / `spawn-end`\n * rows. The enclosing `SubagentBlock` already shows the same label in\n * its border title; repeating it on every inner row is noise.\n */\n hideChildLabel?: boolean\n }) => {\n const SURFACE = useSurfaces()\n const gap = marginTopFor(event, previous)\n return (\n <box\n id={anchorId}\n style={{\n // Selection trick: when highlighted, convert the inter-event gap\n // from outer margin to inner padding so the bg fill extends\n // through it. With marginTop the gap is OUTSIDE the box and\n // stays uncolored, producing visible stripes between consecutive\n // same-turn events.\n marginTop: selected ? 0 : gap,\n paddingTop: selected ? gap : 0,\n backgroundColor: selected ? SURFACE.selection : undefined,\n // Pin width to the scrollbox content area + lock the row against\n // shrinking. Without these, a streamed markdown growing taller can\n // make Yoga renegotiate widths for *every* sibling row, which is the\n // visible \"the text jiggles a column to the left, then back\" effect.\n alignSelf: 'stretch',\n flexShrink: 0,\n flexDirection: 'column',\n }}\n >\n <EventLineImpl event={event} depthOffset={depthOffset} hideChildLabel={hideChildLabel} selected={selected} />\n </box>\n )\n },\n)\n\n/**\n * `@opentui/react` extends `React.JSX.IntrinsicElements`, so `onSubmit` on `<input>`\n * gets intersected with the DOM `SubmitEvent` shape and demands an unhelpful overload.\n * The OpenTUI input runtime fires `(value: string) => void`; this helper isolates\n * the cast so each call site stays readable.\n */\nexport function onInputSubmit(handler: (value: string) => void): never {\n return handler as unknown as never\n}\n\n// ---------------------------------------------------------------------------\n// Footer — single status bar: shortcut hints on the left, context-window\n// indicator on the right. Hint labels carry their own color so the model\n// id (next to `ctrl+m`) and agent label (next to `shift+tab`) read as\n// distinct values rather than generic hint text — no separate badges\n// needed.\n// ---------------------------------------------------------------------------\n\nexport interface ContextUsage {\n used: number\n max: number\n}\n\n/**\n * Footer status bar. Renders as a single row when the terminal is wide\n * enough, otherwise stacks the context indicator beneath the hint row.\n * Width tiering is driven by plain-text length estimates — close enough\n * since the segments are ASCII-heavy.\n */\nexport function Footer({\n hints,\n context,\n cost = null,\n status = null,\n}: {\n hints: Hint[]\n context: ContextUsage | null\n /**\n * Cumulative USD cost for the current session, summed across runs.\n * Hidden when `null` or `0` — most providers don't populate cost today\n * (only OpenRouter does), and \"$0.00\" would read as a lie on the rest.\n */\n cost?: number | null\n /**\n * Active run status, surfaced as a small glyph at the right edge of\n * the hints (left) section. Replaces the spinner that used to live in\n * the title overlay. `null` hides it entirely. Priority order matches\n * the previous title-overlay logic: asking > compacting > busy.\n */\n status?: 'busy' | 'compacting' | 'asking' | null\n}) {\n const { width } = useTerminalDimensions()\n\n const showCost = typeof cost === 'number' && cost > 0\n\n // 1ch padding on each side. Plus 1ch breathing room between hints and\n // context in single-row layout (the spacer's minimum width).\n const inner = Math.max(0, width - 2)\n const hW = hintsLength(hints)\n const ctxW = context ? contextIndicatorLength(context) : 0\n const costW = showCost ? costIndicatorLength(cost) : 0\n // \"$<cost> · ctx …\" — 3 cells for the · separator when both render.\n const rightW = costW + ctxW + (costW > 0 && ctxW > 0 ? 3 : 0)\n // Status icon reserves 1 leading-gap cell + 1 glyph cell (2 for the\n // ❓ asking-question emoji, which is double-width on most terminals).\n const sW = status === 'asking' ? 3 : status ? 2 : 0\n\n const oneRowFits = hW + sW + (rightW > 0 ? rightW + 1 : 0) <= inner\n\n if (oneRowFits) {\n return (\n <box style={{ flexDirection: 'row', height: 1, paddingLeft: 1, paddingRight: 1 }}>\n <HintsText hints={hints} />\n <FooterStatusIcon status={status} />\n <box style={{ flexGrow: 1 }} />\n <RightStatus cost={showCost ? cost : null} context={context} />\n </box>\n )\n }\n\n // Stacked layout — give the hints their own row, then the context\n // indicator below. Clip the hint list to the row's renderable width\n // so a still-too-long primary set doesn't wrap into a second row and\n // collide with the context indicator below it.\n const stackedHints = clipHintsToWidth(hints, inner)\n return (\n <box style={{ flexDirection: 'column', paddingLeft: 1, paddingRight: 1 }}>\n {(stackedHints.length > 0 || status) && (\n <box style={{ flexDirection: 'row', height: 1 }}>\n <HintsText hints={stackedHints} />\n <FooterStatusIcon status={status} />\n </box>\n )}\n {rightW > 0 && (\n <box style={{ flexDirection: 'row', height: 1 }}>\n <box style={{ flexGrow: 1 }} />\n <RightStatus cost={showCost ? cost : null} context={context} />\n </box>\n )}\n </box>\n )\n}\n\nfunction RightStatus({ cost, context }: { cost: number | null, context: ContextUsage | null }) {\n const COLOR = useColors()\n if (cost == null && !context)\n return null\n return (\n <text>\n {cost != null && <CostIndicator cost={cost} />}\n {cost != null && context && <span fg={COLOR.mute}>{' · '}</span>}\n {context && <ContextIndicator context={context} />}\n </text>\n )\n}\n\n/**\n * Right-edge status glyph in the Footer's left section. Renders a single\n * cell preceded by a 1ch gap when active; nothing at all when `status` is\n * null. Color / glyph mapping mirrors the old title-overlay logic so the\n * cue reads the same just relocated to the bottom bar.\n */\nfunction FooterStatusIcon({ status }: { status: 'busy' | 'compacting' | 'asking' | null }) {\n const COLOR = useColors()\n if (!status)\n return null\n const glyph = status === 'asking'\n ? <span>❓</span>\n : status === 'busy'\n ? <StatusSpinner color={COLOR.warn} />\n : <StatusSpinner color={COLOR.accent} />\n return (\n <text>\n <span>{' '}</span>\n {glyph}\n </text>\n )\n}\n\nfunction HintsText({ hints }: { hints: readonly Hint[] }) {\n const COLOR = useColors()\n return <text fg={COLOR.dim}>{renderHintSpans(hints, COLOR)}</text>\n}\n\n/**\n * Pure renderer for a list of {@link Hint}s as colored spans —\n * `<key1> <label1> · <key2> <label2> · …` with warn/dim/mute colors.\n *\n * Returns spans only (no enclosing `<text>`, no leading / trailing\n * whitespace) so the caller can wrap it in whichever container fits\n * their surface: the bottom-bar footer uses a single-row `<text>`, the\n * prompt-box overlay wraps it with leading + trailing spaces so the\n * outermost cells punch through the border like a native title would.\n *\n * Pattern matches `renderRefSpans` below — pure function over an opaque\n * `ThemeColors`, no internal hook calls so it composes inside any\n * `<text>` regardless of where the parent grabbed its palette.\n */\nexport function renderHintSpans(hints: readonly Hint[], COLOR: ThemeColors): ReactNode {\n return hints.map((h, i) => (\n <span key={i}>\n {i > 0 && <span fg={COLOR.mute}> · </span>}\n <span fg={h.keyColor ?? COLOR.warn}>{h.key}</span>\n {h.extra && <span fg={h.extra.keyColor ?? COLOR.mute}>{h.extra.key}</span>}\n <span fg={h.labelColor ?? COLOR.dim}>{` ${h.label}`}</span>\n {h.extra && <span fg={h.extra.labelColor ?? COLOR.dim}>{` ${h.extra.label}`}</span>}\n </span>\n ))\n}\n\nfunction ContextIndicator({ context }: { context: ContextUsage }) {\n const COLOR = useColors()\n const ratio = context.max > 0 ? context.used / context.max : 0\n const pct = Math.round(ratio * 100)\n const color = ratio >= 0.85 ? COLOR.error : ratio >= 0.6 ? COLOR.warn : COLOR.dim\n return (\n <span>\n <span fg={COLOR.mute}>ctx </span>\n <span fg={color}>{fmtTokens(context.used)}</span>\n <span fg={COLOR.mute}>{` / ${fmtTokens(context.max)} `}</span>\n <span fg={color}>{`(${pct}%)`}</span>\n </span>\n )\n}\n\nfunction CostIndicator({ cost }: { cost: number }) {\n const COLOR = useColors()\n const fg = COLOR.money ?? COLOR.warn\n return (\n <span>\n <span fg={COLOR.mute}>$</span>\n <span fg={fg}>{formatCost(cost)}</span>\n </span>\n )\n}\n\n// ---------------------------------------------------------------------------\n// TitleOverlay — colored title (left) + optional meta (right) painted over\n// a bordered box's top border. Replaces the native `title=` prop where\n// per-segment color is needed (the native title is single fg).\n//\n// Render contract: must be a SIBLING of the bordered box, declared AFTER\n// it inside a shared parent so the renderer paints the overlay on top of\n// the border row. Bordered boxes clip their own absolute children via a\n// scissor rect that excludes the border, so the overlay can't live\n// inside the bordered box itself.\n// ---------------------------------------------------------------------------\n\n/**\n * Width budget reservations applied to the responsive math in\n * {@link TitleOverlay}. Each title / meta segment owns a leading +\n * trailing mute space (the cells that punch through the underlying\n * border `─`); a 2-cell `GAP` keeps title and meta visually distinct\n * when both render in the same row.\n */\nconst TITLE_OVERLAY_WRAP = 2\nconst TITLE_OVERLAY_META_WRAP = 2\nconst TITLE_OVERLAY_GAP = 2\n\n/**\n * One run of styled text in a {@link TitleOverlay} meta segment array.\n * `color` defaults to `COLOR.dim` (the standard secondary tone for\n * meta info); supply an explicit color to accent a value — typical use\n * is a `warn` tint on a primary stat (turn count, session count) while\n * surrounding separators stay `mute`.\n */\nexport interface MetaSegment {\n text: string\n /** Foreground color. Defaults to `COLOR.dim`. */\n color?: string\n}\n\n/**\n * Render text as a per-character HSL gradient between two hex colors.\n * Used by the title overlay so the chat header sweeps the theme's\n * throbber pair (Charple→Dolly for Crush) instead of sitting flat on\n * the brand color.\n */\nfunction renderGradientText(text: string, from: string, to: string) {\n // The ramp matches the text length so every character gets a stop;\n // single-character strings still produce a stable midpoint color.\n const ramp = buildLinearRamp(from, to, text.length)\n return text.split('').map((ch, i) => (\n <span key={i} fg={ramp[i]}>{ch}</span>\n ))\n}\n\n/**\n * Colored title for a full-screen bordered surface. The `title` slot\n * rides `titleColor` (defaults to a per-character gradient across the\n * theme's throbber pair, fallback brand→accent) on the LEFT of the top\n * border; the optional `meta` slot rides on the RIGHT.\n *\n * `meta` accepts either:\n * - a plain `string` → rendered entirely in `COLOR.dim` (the simple\n * \"single dim label\" case, e.g. `\"5 sessions\"`).\n * - a {@link MetaSegment} array → rendered concatenated with each\n * segment's own color (lets a stat like turn count stand out from\n * the surrounding separators).\n *\n * Responsive behavior (driven by `useTerminalDimensions`):\n * - Both fit → render both with a 2-cell visual gap between them.\n * - Meta doesn't fit → drop meta; title takes the full budget.\n * - Title doesn't fit → truncate with a trailing `…`.\n * - Terminal is degenerately narrow → render nothing.\n *\n * The width math assumes the immediate parent fills the screen's\n * content area (the standard `flexGrow: 1` flex column). Hosts running\n * the bordered box inside a narrower container should pass `parentWidth`\n * to override the terminal-width assumption.\n *\n * @example\n * ```tsx\n * <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n * <box style={{ border: true, flexGrow: 1 }}>...</box>\n * <TitleOverlay\n * title=\"my session\"\n * meta={[\n * { text: '#abcd' },\n * { text: ' · ', color: COLOR.mute },\n * { text: '5 turns', color: COLOR.warn },\n * ]}\n * />\n * </box>\n * ```\n */\nexport function TitleOverlay({\n title,\n meta = null,\n titleColor,\n parentWidth,\n statusIcon = null,\n statusIconCells = 1,\n}: {\n title: string\n /** Optional right-aligned secondary segment. Dropped first when space runs out. */\n meta?: string | readonly MetaSegment[] | null\n /** Defaults to `COLOR.brand`. */\n titleColor?: string\n /**\n * Override for the parent container's width in columns. Defaults to\n * `terminalWidth - 2` to match the standard screen layout that has\n * 1-cell padding on each side.\n */\n parentWidth?: number\n /**\n * Optional single-cell glyph rendered immediately AFTER the title\n * text. Used by the chat screen to surface a streaming / compacting\n * indicator next to the session name — keeps the affordance close to\n * what it qualifies (the conversation) without claiming a full row\n * above the prompt input.\n *\n * Sized as `statusIconCells` cells + one leading space when present,\n * so the title budget reserves the right number of extra columns.\n * Pass a {@link StatusSpinner} or any other 1-cell `<span>`; `null`\n * (the default) takes no space.\n *\n * Render order is `title <space> icon`. The leading title slot reads\n * naturally — eye lands on the name first, then catches the activity\n * indicator — and avoids the visual jitter that came with the icon\n * pulsing on the leftmost position.\n */\n statusIcon?: ReactNode\n /**\n * Cell width of `statusIcon`. Defaults to `1` for the standard\n * single-cell spinners; pass `2` when the icon is a double-width\n * emoji glyph (e.g. `❓`) so the title-budget math keeps the meta\n * segment from being clipped a column early.\n */\n statusIconCells?: 1 | 2\n}) {\n const COLOR = useColors()\n const { width: termWidth } = useTerminalDimensions()\n // When `titleColor` is omitted, paint the title as a per-character\n // gradient using the theme's throbber pair (Charple→Dolly for Crush;\n // brand→accent for everything else by fallback). When the caller\n // explicitly hands us a color (e.g. the wizard's solid-accent title)\n // we honor it and skip the gradient.\n const useGradient = titleColor === undefined\n // Title sweeps `throbber.to → throbber.from` so the gradient flows\n // *toward* the brand anchor on the right side of the title. For\n // Crush this reads Dolly (pink) → Charple (purple) left-to-right.\n const gradientFrom = COLOR.throbber?.to ?? COLOR.accent\n const gradientTo = COLOR.throbber?.from ?? COLOR.brand\n const fg = titleColor ?? COLOR.brand\n\n // Available decorable cells along the top border = parent width\n // minus the two corner glyphs. Corners are off-limits for overlays.\n const W = Math.max(0, parentWidth ?? termWidth - 2)\n const inner = Math.max(0, W - 2)\n\n // The status icon, when set, eats `statusIconCells` glyph cells + 1\n // leading space in the title slot. Reserve those out of every budget\n // calculation so the glyph never pushes the title (or the meta)\n // off-screen — double-width emoji icons need `statusIconCells: 2`.\n const iconReserve = statusIcon ? 1 + statusIconCells : 0\n const metaLen = metaSegmentsLength(meta)\n const showMeta = meta != null && metaLen > 0\n && title.length + iconReserve + TITLE_OVERLAY_WRAP + TITLE_OVERLAY_GAP + metaLen + TITLE_OVERLAY_META_WRAP <= inner\n const titleBudget = (showMeta\n ? inner - (metaLen + TITLE_OVERLAY_META_WRAP) - TITLE_OVERLAY_GAP - TITLE_OVERLAY_WRAP\n : inner - TITLE_OVERLAY_WRAP) - iconReserve\n const visibleTitle = titleBudget <= 0 ? '' : truncateTrailing(title, titleBudget)\n\n return (\n <>\n {(visibleTitle || statusIcon) && (\n <text style={{ position: 'absolute', top: 0, left: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n {useGradient && visibleTitle.length > 0\n ? renderGradientText(visibleTitle, gradientFrom, gradientTo)\n : <span fg={fg}>{visibleTitle}</span>}\n {statusIcon && <span fg={COLOR.mute}>{' '}</span>}\n {statusIcon}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n )}\n {showMeta && meta && (\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n {typeof meta === 'string'\n ? <span fg={COLOR.dim}>{meta}</span>\n : meta.map((seg, i) => (\n <span key={i} fg={seg.color ?? COLOR.dim}>{seg.text}</span>\n ))}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n )}\n </>\n )\n}\n\n/** Total printed-character length of a {@link TitleOverlay} `meta` value. */\nfunction metaSegmentsLength(meta: string | readonly MetaSegment[] | null | undefined): number {\n if (meta == null)\n return 0\n if (typeof meta === 'string')\n return meta.length\n return meta.reduce((sum, seg) => sum + seg.text.length, 0)\n}\n\nfunction contextIndicatorLength(context: ContextUsage): number {\n const ratio = context.max > 0 ? context.used / context.max : 0\n const pct = Math.round(ratio * 100)\n // \"ctx <used> / <max> (<pct>%)\"\n return 4 + fmtTokens(context.used).length + 3 + fmtTokens(context.max).length\n + 2 + String(pct).length + 2\n}\n\n// Mirrors `session-details-modal.tsx:469` — 4 digits of precision under a\n// cent, 2 above. Single source of truth would be nicer; one shared helper\n// can wait until a third surface needs it.\nfunction formatCost(cost: number): string {\n return cost.toFixed(cost < 0.01 ? 4 : 2)\n}\n\nfunction costIndicatorLength(cost: number): number {\n return 1 + formatCost(cost).length // \"$\" + digits\n}\n\n// ---------------------------------------------------------------------------\n// Spinner — animated braille used while a run is streaming.\n// ---------------------------------------------------------------------------\n\nconst SPINNER_FRAMES = ['▰▱▱▱▱', '▰▰▱▱▱', '▰▰▰▱▱', '▰▰▰▰▱', '▰▰▰▰▰', '▱▰▰▰▰', '▱▱▰▰▰', '▱▱▱▰▰', '▱▱▱▱▰', '▱▱▱▱▱']\nconst SPINNER_INTERVAL_MS = 80\n\n/**\n * Tick a frame index every {@link SPINNER_INTERVAL_MS}. Pulled out of the\n * default `Spinner` body so the title-overlay status icon and any other\n * tiny single-glyph spinner can share the same animation cadence without\n * each instance running its own `setInterval`.\n */\nfunction useSpinnerFrame(): string {\n const [frame, setFrame] = useState(0)\n useEffect(() => {\n const id = setInterval(() => setFrame(f => (f + 1) % SPINNER_FRAMES.length), SPINNER_INTERVAL_MS)\n return () => clearInterval(id)\n }, [])\n return SPINNER_FRAMES[frame]!\n}\n\nexport function Spinner({ label }: { label?: string }) {\n const COLOR = useColors()\n const glyph = useSpinnerFrame()\n return (\n <text fg={COLOR.warn}>\n {glyph}\n {label !== undefined && <span fg={COLOR.dim}>{` ${label}`}</span>}\n </text>\n )\n}\n\n/**\n * Glyph-only spinner painted in a caller-chosen color. Used as the\n * status icon prefixed to the chat screen's session title — a single\n * cell wide so it slots in front of the title text without disturbing\n * the existing `TITLE_OVERLAY_WRAP` budget math.\n */\nexport function StatusSpinner({ color }: { color: string }) {\n const glyph = useSpinnerFrame()\n return <span fg={color}>{glyph}</span>\n}\n\n// ---------------------------------------------------------------------------\n// Transcript — scrollbox with sticky-bottom and structured event rendering.\n// ---------------------------------------------------------------------------\n\nexport function Transcript({\n events,\n settings,\n selectedTurnId = null,\n busy = false,\n}: {\n events: StreamEvent[]\n settings: Settings\n /**\n * Turn id whose events should render with the selection accent bar. `null`\n * leaves every event unhighlighted (normal mode). Events without a turnId\n * (synthetic markers) are never highlighted.\n */\n selectedTurnId?: string | null\n /**\n * True while a run is streaming. Drives the inline working throbber that\n * appears at the tail of the transcript while the assistant is preparing\n * a reply (thinking, or otherwise busy before content/tool calls land).\n * Matches Crush's `AssistantMessageItem.isSpinning()` semantics: the\n * throbber hides as soon as the active turn produces visible output.\n */\n busy?: boolean\n}) {\n const COLOR = useColors()\n const items = useMemo(() => partitionTranscript(events, settings), [events, settings])\n\n // `busy` is true for the entire `agent.run` lifecycle (set on submit /\n // interaction-resume, cleared in the run's `finally`), so this directly\n // mirrors \"the assistant is still working on this turn\". The throbber\n // stays put under the streaming content as a persistent \"still active\"\n // cue — disappears the moment the run resolves. Gated on\n // `Settings.showThrobber` (off by default) so users who find the\n // animated glyphs noisy can drop them without losing the run's\n // markdown / status-row signals.\n const showThrobber = busy && settings.showThrobber\n // Label only when the run is currently emitting thinking tokens —\n // matches Crush, where `AssistantMessageItem.renderSpinning()` sets\n // the label to \"Thinking\" / \"Summarizing\" only for those states and\n // otherwise renders the bare gradient.\n const throbberLabel = events.length > 0 && events[events.length - 1].kind === 'thinking'\n ? 'Thinking'\n : undefined\n // Ownership map for the select-turn coalesce rule: result-only user\n // turns map to the assistant turn whose `tool_call`s they answer. The\n // highlight gate ({@link isTurnHighlighted}) and the snap-to-bottom\n // branch below both consult it so the selection accent extends from\n // an assistant turn onto its tool-result rows as ONE unit.\n const ownership = useMemo(() => turnSelectionOwnership(events), [events])\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Anchor ids the scrollbox can target for auto-scroll. We tag ONLY the\n // first rendered event of each turn so `scrollChildIntoView` has an\n // unambiguous target; later events of the same turn render without an\n // id. Walk in the same order as the render below to keep the index\n // mapping in lock-step with the actual JSX tree.\n const anchors = useMemo(() => computeTurnAnchors(items), [items])\n\n // Auto-scroll the selected turn into view. Fires on every selection\n // change (and on items mutation while a selection is held — e.g. a\n // streamed turn growing taller pushes prior turns up).\n //\n // Behavior:\n // - Most turns: `scrollChildIntoView` brings the FIRST event of the\n // selected turn into view. It's a no-op when the row already fits,\n // so we don't fight the user's manual scroll position when they're\n // already on the right turn.\n // - Last turn: snap to the absolute bottom of the scrollbox instead.\n // Anchoring on the first event would scroll UPWARD if the turn is\n // taller than the viewport, leaving the assistant's tail off-screen\n // — which felt wrong when navigating back into the most recent\n // message. Snapping to bottom also re-engages sticky-bottom for any\n // follow-up streaming once select mode exits.\n //\n // Deferred to the next frame via `requestAnimationFrame` because the\n // effect fires synchronously after React commit but BEFORE OpenTUI's\n // layout pass updates the scrollbar's `scrollSize`. Reading\n // `scrollHeight` (or letting `scrollChildIntoView` read the child's\n // measured `y` / `height`) immediately can use stale values; deferring\n // one frame gives Yoga + the scrollbar time to settle.\n useEffect(() => {\n if (!selectedTurnId)\n return\n const scrollbox = scrollboxRef.current\n if (!scrollbox)\n return\n const handle = requestAnimationFrame(() => {\n // Snap to absolute bottom when the selected turn is the last\n // rendered turn — OR when it OWNS the last rendered turn (i.e.\n // the trailing turn is a result-only one coalesced into our\n // selection). Without the ownership branch, navigating to the\n // most recent assistant turn would scroll its anchor into view\n // but leave the matching tool-result row peeking off the bottom\n // edge of the viewport.\n const ownsLast = anchors.lastTurnId !== undefined\n && ownership.get(anchors.lastTurnId) === selectedTurnId\n if (selectedTurnId === anchors.lastTurnId || ownsLast) {\n // `scrollSize - viewportSize` is the max scroll position. Pass a\n // value at least that large; the scrollbar clamps internally so we\n // don't have to subtract the viewport height ourselves.\n scrollbox.scrollTop = scrollbox.scrollHeight\n return\n }\n const id = anchors.idByTurn.get(selectedTurnId)\n if (id)\n scrollbox.scrollChildIntoView(id)\n })\n return () => cancelAnimationFrame(handle)\n }, [selectedTurnId, anchors, ownership])\n\n // Empty + idle → show the welcome card. Empty + busy (rare; the\n // submission usually appends a `user-prompt` event synchronously)\n // falls through so the throbber still has somewhere to land.\n if (items.length === 0 && !showThrobber)\n return <EmptyState />\n\n return (\n <scrollbox\n ref={scrollboxRef}\n // Never claim keyboard focus: the textarea is the chat's primary input,\n // so up/down/page-up/page-down should stay with it (or with the modal\n // when one is open). Mouse-wheel scrolling still works.\n focusable={false}\n style={{ flexGrow: 1, paddingLeft: 1, paddingRight: 1 }}\n stickyScroll\n stickyStart=\"bottom\"\n // Slim, theme-tinted scrollbar. Defaults are loud — a 2-cell-wide\n // bar with a dark #252527 track painted top-to-bottom (visible\n // as a stripe down the right edge even when the conversation\n // barely overflows) and a foreign #9a9ea3 thumb that doesn't\n // match any theme palette. We keep just the thumb and paint it\n // in `mute`, which already rides the active theme.\n verticalScrollbarOptions={{\n width: 1,\n trackOptions: {\n backgroundColor: 'transparent',\n foregroundColor: COLOR.mute,\n },\n }}\n >\n {/*\n Index-based keying is intentional. The visible items list is\n derived from an append-only event stream filtered through\n `partitionTranscript`; the same `event` reference always occupies\n the same slot during a live stream. Filter toggles (e.g. \"show\n thinking\") re-derive the list, but `EventLine` is keyed by index\n and memoized on its props — `event` identity drives the actual\n re-render decision, so a shifted slot reusing a memo entry is\n functionally correct (every `EventLineImpl` is stateless).\n Switching to a derived stable key would require threading a\n per-event id through the stream buffer + persistence layer,\n which isn't worth the churn for the same observable behavior.\n */}\n {items.map((item, i) => (\n item.kind === 'event'\n ? (\n <EventLine\n key={i}\n event={item.event}\n previous={item.previous}\n selected={isTurnHighlighted(item.event, selectedTurnId, ownership)}\n anchorId={anchors.ids[i][0]}\n />\n )\n : (\n <SubagentBlock\n key={i}\n events={item.events}\n previous={item.previous}\n selectedTurnId={selectedTurnId}\n anchorIds={anchors.ids[i]}\n />\n )\n ))}\n {showThrobber && (\n <box style={{ marginTop: items.length > 0 ? 1 : 0 }}>\n <CrushThrobber\n label={throbberLabel}\n from={COLOR.throbber?.from ?? COLOR.brand}\n to={COLOR.throbber?.to ?? COLOR.accent}\n />\n </box>\n )}\n </scrollbox>\n )\n}\n\n// `isVisible` / `isEditErrorResult` / `marginTopFor` now live in\n// `src/chat/store.ts`; `computeTurnAnchors` + `TranscriptItem` now live\n// in `src/chat/transcript-anchors.ts`. See the re-exports at the bottom\n// of this file for back-compat with TUI consumers; new code should\n// import directly from `zidane/chat`.\n\n/**\n * Walk the visible-event list once and group consecutive child events\n * (`depth > 0`) into runs so we can wrap each run in a single bordered\n * subagent box.\n *\n * When `hideSubagentOutput` is on, `isVisible` already filters most child\n * events out; the surviving `spawn-start` / `spawn-end` markers render as\n * plain entries (no boxing) — there's nothing meaningful to box.\n */\nfunction partitionTranscript(events: StreamEvent[], settings: Settings): TranscriptItem[] {\n const visible = events.filter(e => isVisible(e, settings))\n if (visible.length === 0)\n return []\n\n // Hide-mode: spawn-start/end are the only child events left. Don't box\n // them — they're already standalone \"subagent N is working/done\" lines.\n if (settings.hideSubagentOutput) {\n return visible.map((event, i) => ({ kind: 'event', event, previous: visible[i - 1] }))\n }\n\n const items: TranscriptItem[] = []\n let run: StreamEvent[] = []\n let runPrevious: StreamEvent | undefined\n\n const flush = () => {\n if (run.length > 0) {\n items.push({ kind: 'child-run', events: run, previous: runPrevious })\n run = []\n runPrevious = undefined\n }\n }\n\n for (let i = 0; i < visible.length; i++) {\n const event = visible[i]\n if (isChild(event)) {\n if (run.length === 0)\n runPrevious = visible[i - 1]\n run.push(event)\n }\n else {\n flush()\n items.push({ kind: 'event', event, previous: visible[i - 1] })\n }\n }\n flush()\n return items\n}\n\n/**\n * Bordered container for one run of subagent events. The box's border +\n * left padding give the visual \"this is a subagent\" affordance, so events\n * inside render with `depthOffset: 1` — a direct child of the parent\n * (depth 1) sits flush against the box's inner padding rather than being\n * indented twice. Grandchildren (depth ≥ 2) still indent further, so\n * nested subagents remain visually distinct.\n */\nfunction SubagentBlock({\n events,\n previous,\n selectedTurnId = null,\n anchorIds,\n}: {\n events: StreamEvent[]\n previous?: StreamEvent\n selectedTurnId?: string | null\n /**\n * Per-inner-event scroll anchor ids, parallel to `events`. Entries are\n * `undefined` for events that aren't a turn's first occurrence — see\n * `computeTurnAnchors`.\n */\n anchorIds?: readonly (string | undefined)[]\n}) {\n const COLOR = useColors()\n const childIds = useMemo(() => {\n const set = new Set<string>()\n for (const e of events) {\n if (e.childId)\n set.add(e.childId)\n }\n return Array.from(set)\n }, [events])\n\n const title = childIds.length === 0\n ? ' subagent '\n : childIds.length === 1\n ? ` ${childIds[0]} `\n : ` subagents · ${childIds.join(', ')} `\n\n // Keep the same vertical breathing room a markdown/spawn-start event would\n // get standalone — without this the box looks glued to the parent's tool\n // call above it.\n const marginTop = previous ? 1 : 0\n\n // Single-subagent runs hoist the `child-N` label to the box title and drop\n // it from every inner line — the label would otherwise repeat verbatim on\n // every spawn-start / spawn-end row. Multi-subagent runs keep the per-line\n // tag so the reader can still disambiguate interleaved events.\n const hideChildLabel = childIds.length === 1\n\n return (\n <box\n title={title}\n style={{\n border: true,\n // Brand-tinted frame is the cheapest way to colorize the title — the\n // box's title shares its color with `borderColor`. Subagents now read\n // visually closer to `task-notification` banners (brand-anchored\n // identity) while still looking calmer than a focused interactive\n // surface (which uses `borderActive`).\n borderColor: COLOR.brand,\n paddingLeft: 1,\n paddingRight: 1,\n paddingTop: 0,\n paddingBottom: 0,\n marginTop,\n flexDirection: 'column',\n flexShrink: 0,\n alignSelf: 'stretch',\n }}\n >\n {events.map((evt, i) => (\n <EventLine\n key={i}\n event={evt}\n previous={events[i - 1]}\n depthOffset={1}\n selected={selectedTurnId !== null && evt.turnId === selectedTurnId}\n anchorId={anchorIds?.[i]}\n hideChildLabel={hideChildLabel}\n />\n ))}\n </box>\n )\n}\n\nfunction EmptyState() {\n const COLOR = useColors()\n return (\n <box style={{ flexGrow: 1, alignItems: 'center', justifyContent: 'center' }}>\n <text fg={COLOR.mute}>no messages yet — type below to start</text>\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EventLine — dispatches per kind. Subagent events render via indented dim\n// blocks; the `depth` field drives the left indent.\n// ---------------------------------------------------------------------------\n\n/** Left-pad applied per depth level (in columns). */\nconst INDENT_PER_DEPTH = 2\n\nfunction indentFor(depth: number | undefined): number {\n return depth && depth > 0 ? depth * INDENT_PER_DEPTH : 0\n}\n\nfunction isChild(event: StreamEvent): boolean {\n return (event.depth ?? 0) > 0\n}\n\n/**\n * Shared row geometry for every transcript event.\n *\n * `alignSelf: 'stretch'` + `flexShrink: 0` together pin each row to the\n * scrollbox's content width and prevent flex re-negotiation when neighboring\n * rows grow or shrink (streaming markdown, late-arriving tool results, etc.).\n * Without this, Yoga is free to re-compute widths every render and the\n * visible text appears to \"wiggle\" between columns as the stream advances.\n */\nfunction rowStyle(paddingLeft: number) {\n return {\n paddingLeft,\n flexDirection: 'column' as const,\n flexShrink: 0,\n alignSelf: 'stretch' as const,\n }\n}\n\n// `marginTopFor` now lives in `src/chat/store.ts`; see re-export below.\n\nfunction EventLineImpl({ event, depthOffset = 0, hideChildLabel = false, selected = false }: {\n event: StreamEvent\n depthOffset?: number\n hideChildLabel?: boolean\n /**\n * Whether the enclosing row carries the selection background. Inner\n * renderers that paint their own background (currently\n * {@link MarkdownBlock} for inline-code chips + the markdown body\n * itself) read this and swap to a selection-aware variant so the\n * highlight band stays visually continuous across the row.\n */\n selected?: boolean\n}) {\n const COLOR = useColors()\n const { settings } = useSettings()\n const safeText = event.text === '' ? ' ' : event.text\n const effectiveDepth = Math.max(0, (event.depth ?? 0) - depthOffset)\n const row = rowStyle(indentFor(effectiveDepth))\n\n // Subagent text is dimmed across the board so the visual hierarchy is\n // obvious even before reading the indent.\n const child = isChild(event)\n\n switch (event.kind) {\n case 'separator':\n return <text> </text>\n case 'user-prompt':\n return <UserPromptBlock text={safeText} refs={event.refs} attachments={event.attachments} />\n case 'info':\n return (\n <box style={row}>\n <text fg={COLOR.dim}>{safeText}</text>\n </box>\n )\n case 'thinking':\n return (\n <box style={row}>\n <text fg={COLOR.dim}>{safeText}</text>\n </box>\n )\n case 'tool':\n if (event.edit && settings.showEditDiffs) {\n return (\n <box style={row}>\n <EditDiffBlock payload={event.edit} dim={child} />\n </box>\n )\n }\n // `'hidden'` was already filtered out by isVisible upstream, so\n // by the time we render the dispatch reduces to `'formatted' | 'full'`.\n return (\n <box style={row}>\n <ToolCallBlock\n event={event}\n display={settings.toolCallDisplay === 'full' ? 'full' : 'formatted'}\n dim={child}\n />\n {/* Inline \"what's in progress\" affordance for `todowrite`. The\n tool-result line summarizes the tally; this sub-list spells\n out the LIVE work — the items the model has flagged as\n `in_progress` right now — so the user can read the\n checkpoint at a glance without opening the modal. Hidden\n for any `todowrite` whose payload has no in-progress\n items (e.g. all-completed close-out → list cleared). */}\n {event.tool === TODOWRITE_TOOL && <TodoInProgressList input={event.input} dim={child} />}\n </box>\n )\n case 'tool-result':\n return <ToolResultBlock text={event.text} indent={row.paddingLeft} />\n case 'error':\n return (\n <box style={row}>\n <text fg={COLOR.error}>\n <span fg={COLOR.error}>✗ </span>\n {safeText}\n </text>\n </box>\n )\n case 'markdown':\n return (\n <box style={row}>\n {/* eslint-disable-next-line ts/no-use-before-define -- forward-referenced memo component, evaluated at render time */}\n <MarkdownBlock text={event.text} dim={child} selected={selected} />\n </box>\n )\n case 'spawn-start':\n // `hideChildLabel` is set when the surrounding `SubagentBlock`\n // already shows `child-N` in its border title — repeating it on\n // every inner row reads as visual noise. We still keep the glyph\n // + body text so the row is meaningful in isolation (e.g. when\n // the user collapses or scrolls past the box title).\n return (\n <box style={row}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>🌱 </span>\n {!hideChildLabel && (\n <>\n <span fg={COLOR.brand}>{event.childId ?? 'child'}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n </>\n )}\n <span fg={COLOR.dim}>{safeText}</span>\n </text>\n </box>\n )\n case 'spawn-end':\n // Same shape as `spawn-start` — 2-cell emoji keeps the column\n // grid stable across a subagent's lifecycle, and the label is\n // hoisted to the box title in single-child runs.\n return (\n <box style={row}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>🌳 </span>\n {!hideChildLabel && (\n <>\n <span fg={COLOR.brand}>{event.childId ?? 'child'}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n </>\n )}\n <span fg={COLOR.mute}>{safeText}</span>\n </text>\n </box>\n )\n case 'compact-summary':\n // Boundary card — distinguishes itself visually from the regular\n // transcript so the user can tell at a glance \"the model only sees\n // history from this row down\". The summary body is rendered as\n // dimmed markdown so links / file refs still highlight; the meta\n // line above carries `n turns compacted · model · tokens`.\n return <CompactSummaryBlock event={event} indent={row.paddingLeft} />\n case 'task-notification':\n // One-line banner for background-task completion. Status-accented\n // glyph + command preview + status label + duration + clickable\n // (OSC 8) output path. Dedicated component so the structured\n // fields read cleanly — the underlying text block was already\n // suppressed from the generic user-prompt path in\n // `eventsFromTurns`'s replay synthesis.\n return <TaskNotificationBlock event={event} indent={row.paddingLeft} />\n default:\n return <text>{safeText}</text>\n }\n}\n\n/**\n * One-line completion banner for a background task that exited or was\n * killed. Visual contract:\n *\n * - Glyph color reflects exit cleanliness:\n * `'exited'` exit code 0 → success (subtle accent — quiet).\n * `'exited'` non-zero → warn (model / user should notice).\n * `'killed'` → warn (we issued SIGTERM).\n * - Task id reads in the agent's brand accent so the banner stands\n * apart from surrounding markdown without screaming.\n * - Status label takes the same accent as the glyph — color-coupling\n * reinforces the \"this is what happened\" mental model at a glance.\n * - Output path is `compactPath()`-formatted (`~/.zidane/...` when\n * under `$HOME`) so the column doesn't blow past terminal width\n * just to show the user's home prefix they already know about.\n *\n * Vertical spacing comes from `marginTopFor` — banners get a `tool`-like\n * gap before (and the next non-task event provides the gap after), but\n * consecutive banners stack tightly so a burst of completions doesn't\n * scatter the transcript.\n */\nfunction TaskNotificationBlock({\n event,\n indent,\n}: {\n event: StreamEvent\n indent: number | undefined\n}) {\n const COLOR = useColors()\n const task = event.task\n const isError = task ? (task.status === 'killed' || task.exitCode !== 0) : false\n // Glyph + status share an accent so the eye reads them as one unit.\n const statusAccent = isError ? COLOR.warn : COLOR.brand\n const statusLabel = task ? formatTaskStatus(task) : 'done'\n // Show the path as `~/...` whenever it lives under `$HOME` — the\n // task dir is always under there for TUI sessions\n // (`<userDir>/<sessionId>/tasks/`), so the full prefix is noise.\n const displayPath = task?.outputPath ? compactPath(task.outputPath) : ''\n\n return (\n <box style={{ paddingLeft: indent }}>\n <text wrapMode=\"none\">\n <span fg={statusAccent}>{'⌁ '}</span>\n <span fg={COLOR.brand}>{task?.taskId ?? '?'}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={statusAccent}>{statusLabel}</span>\n {task && task.durationMs > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.dim}>{formatDuration(task.durationMs)}</span>\n </>\n )}\n {displayPath && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.dim}>{displayPath}</span>\n </>\n )}\n </text>\n </box>\n )\n}\n\n/**\n * Boundary card for `compact-summary` events. Two visual rows:\n *\n * 1. Meta line — emoji + replaced-turn count + model + token usage,\n * dimmed so it reads as metadata rather than dialogue.\n * 2. Summary body — the LLM-generated summary text, indented under\n * the meta line. Rendered as plain text (not markdown) because the\n * summary follows the 9-section template and benefits more from\n * crisp wrap than from inline syntax highlighting.\n *\n * The card never collapses by default — the user explicitly asked for\n * this compaction, so the immediate post-compact view should show what\n * the model now sees.\n */\nfunction CompactSummaryBlock({\n event,\n indent,\n}: {\n event: StreamEvent\n indent: number | undefined\n}) {\n const COLOR = useColors()\n const meta = event.compact\n return (\n <box style={{ flexDirection: 'column', paddingLeft: indent }}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>⎯⎯ </span>\n <span fg={COLOR.warn}>{`${meta?.replacedCount ?? 0} turn${meta?.replacedCount === 1 ? '' : 's'} compacted`}</span>\n {meta && (\n <>\n <span fg={COLOR.mute}> · model </span>\n <span fg={COLOR.model}>{meta.model}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.dim}>{fmtTokens(meta.inputTokens + meta.cacheReadTokens + meta.cacheCreationTokens)}</span>\n <span fg={COLOR.mute}> in / </span>\n <span fg={COLOR.dim}>{fmtTokens(meta.outputTokens)}</span>\n <span fg={COLOR.mute}> out</span>\n </>\n )}\n </text>\n <text fg={COLOR.dim}>{event.text}</text>\n </box>\n )\n}\n\n/**\n * User prompt — bordered to rhyme with the prompt input box below.\n *\n * No refs → plain text, rendered as a single `<text>` node. Mixed text +\n * refs → flex-row of word-sized atomic segments; each chip is its own\n * `<text>` painted with the theme's per-provider `chips[providerId]`\n * pair (or `chips.default` when the provider id isn't themed).\n *\n * Why flex-row instead of `<span>` siblings inside `<text>`: the OpenTUI\n * text buffer's \"word\" wrap breaks at every punctuation boundary, so a\n * file path like `@src/index.ts` would split between line 1 (`@src/index.`)\n * and line 2 (`ts`) on a narrow terminal — and the chip's background\n * would visually fragment across the wrap. With flex-row + flexWrap, each\n * chip is one atomic flex item; the wrap engine never breaks inside it.\n */\n/** Prompt chevron rendered ahead of every user-prompt block. */\nconst USER_PROMPT_PREFIX = '❯ '\n\nfunction UserPromptBlock({\n text,\n refs,\n attachments,\n}: {\n text: string\n refs?: readonly { start: number, end: number, providerId: string }[]\n attachments?: readonly { name: string, mediaType: string, size: number }[]\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n\n const boxStyle = {\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n }\n\n const attachmentChips = attachments && attachments.length > 0\n ? (\n <box style={{ flexDirection: 'row', flexWrap: 'wrap', paddingTop: text.length > 0 ? 0 : 0 }}>\n {attachments.map((att, idx) => {\n const sz = att.size\n const label = sz < 1024\n ? `${sz}B`\n : sz < 1024 * 1024\n ? `${(sz / 1024).toFixed(1)}KB`\n : `${(sz / (1024 * 1024)).toFixed(1)}MB`\n const icon = att.mediaType.startsWith('image/') ? '🖼' : '📎'\n const chipColor = resolveChipColor(SURFACE.chips, 'file')\n return (\n <text key={`att-${idx}`}>\n {idx > 0 ? ' ' : ''}\n {icon}\n <span fg={chipColor.fg} bg={chipColor.bg}>\n {' '}\n {att.name}\n {' '}\n (\n {label}\n )\n {' '}\n </span>\n </text>\n )\n })}\n </box>\n )\n : null\n\n if (!refs || refs.length === 0) {\n return (\n <box style={boxStyle}>\n {text.length > 0 && (\n <text>\n <span fg={COLOR.brand}>{USER_PROMPT_PREFIX}</span>\n {text}\n </text>\n )}\n {!text.length && attachmentChips && (\n <text fg={COLOR.brand}>{USER_PROMPT_PREFIX}</text>\n )}\n {attachmentChips}\n </box>\n )\n }\n\n // Refs offsets are raw (no prefix shift). The chevron rides as a\n // separate leading flex item so wrap behavior stays clean: on a\n // narrow terminal the prompt text wraps under the chevron, chips\n // stay atomic, and ref offsets don't have to know about the prefix.\n const segments = splitPromptSegments(text, refs)\n return (\n <box style={boxStyle}>\n {/*\n Two boxes — outer = border + padding, inner = wrap container.\n Splitting lets the outer keep its bordered geometry while the\n inner does the row layout + flex-wrap work that keeps each chip\n atomic.\n */}\n <box style={{ flexDirection: 'row', flexWrap: 'wrap' }}>\n <text fg={COLOR.brand}>{USER_PROMPT_PREFIX}</text>\n {segments.map((seg, i) => {\n if (seg.kind === 'plain')\n return <text key={i}>{seg.text}</text>\n const chip = resolveChipColor(SURFACE.chips, seg.providerId)\n return <text key={i} fg={chip.fg} bg={chip.bg}>{seg.text}</text>\n })}\n </box>\n {attachmentChips}\n </box>\n )\n}\n\n/**\n * Markdown block. Renders both live-streaming markdown (while deltas are\n * still appending) and finalized markdown (after `turn:after`, or every\n * entry on a reloaded transcript) through a SINGLE stable `<markdown>`\n * element.\n *\n * Three knobs are intentionally pinned to constants:\n *\n * - `streaming={true}` — always. The OpenTUI setter for `streaming`\n * calls `updateBlocks(true)` (force table refresh) on every flip, so\n * a `true → false` transition at stream completion would rebuild\n * every block renderable in the markdown tree. Pinning the prop to\n * `true` leaves the trailing 2 blocks technically \"unstable\" forever,\n * but that costs nothing once deltas stop arriving — the parser\n * just never re-lexes anything because `content` doesn't change.\n *\n * - `internalBlockMode=\"coalesced\"` — always. The naming suggests this\n * mode merges adjacent prose into one big block (which would force a\n * full re-flow on every delta), but the merge step is gated on\n * `!this._renderNode` inside the markdown core, so the `renderNode`\n * callback below keeps the per-token block structure of top-level\n * mode WHILE getting coalesced's `updateBlockRenderable` update\n * path. That path mutates the existing renderable's `content` in\n * place instead of destroying it on every delta, which is what\n * unlocks the body's \"preserve last-styled buffer while async\n * tree-sitter resolves\" behaviour and kills the streaming blink on\n * the live block. There's no setter for this prop at runtime — it's\n * set-once at construction.\n *\n * - `renderNode` — wraps `code` tokens with a {@link CodeBlockWrapper}\n * to host the `[lang] … [copy]` header and reimplements\n * inter-block spacing (which coalesced + `renderNode` strips along\n * with the parser's space tokens). The resulting visual shape is\n * bit-identical to what top-level mode would produce in opencode-\n * style well-formatted markdown; see the block comment above\n * `makeMarkdownRenderNode` for details.\n *\n * `splitMarkdownCodeBlocks` (now in `zidane/chat`) exposes the\n * segmented prose / code shape for any non-transcript surface that\n * still wants to split fences out for custom rendering; the streaming\n * Transcript no longer uses it.\n */\n// ---------------------------------------------------------------------------\n// Fenced code blocks — lifted out of `<markdown>` so we can pin a copy\n// affordance to the header row. Selection-driven OSC 52 (mounted in\n// AppShell) already covers \"drag to copy\", but a fenced block is the\n// archetypal \"yank me whole\" thing — the header skips the precision-\n// dragging step entirely.\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Custom block renderers for OpenTUI's `<markdown renderNode>` hook.\n//\n// Two transcript-specific concerns are solved by intercepting block tokens\n// at the renderable layer:\n//\n// 1. **Streaming \"blink\" on the live block.** With `streaming: true`\n// OpenTUI's markdown parser marks the last two tokens as \"unstable\"\n// and re-lexes them on every content delta. In\n// `internalBlockMode: \"top-level\"`, the markdown handles those new\n// tokens by destroying the existing renderable and creating a\n// fresh one — and a freshly-constructed CodeRenderable\n// (`drawUnstyledText: false`, `streaming: true`, `filetype:\n// \"markdown\"`) paints NOTHING until the async tree-sitter highlight\n// resolves. On chunks where the parse misses the 16ms frame budget\n// the user sees a blank, then-styled flash that reads as the live\n// block \"blinking\". Opencode runs the exact same configuration and\n// has the same vulnerability; we just want the live block to stay\n// visibly stable.\n//\n// `internalBlockMode: \"coalesced\"` chooses a different update path:\n// same-type adjacent tokens UPDATE the existing renderable's\n// `content` in place instead of destroying it. The CodeRenderable's\n// content setter then takes its early-return path\n// (`streaming && !drawUnstyledText && filetype` are all true), which\n// preserves the previously-styled buffer on screen while the async\n// highlight computes new chunks. The eye registers a smooth\n// styled → styled transition. No blink, regardless of how long\n// tree-sitter takes.\n//\n// One side effect: coalesced mode normally MERGES adjacent prose\n// tokens into one big block (which would force a full re-flow on\n// every delta). The merge step is gated on `!this._renderNode`\n// inside the markdown core, so having a `renderNode` callback set\n// (even one that returns `undefined` for prose) is enough to keep\n// the per-token block structure of top-level mode while landing on\n// coalesced's smoother update path.\n//\n// 2. **`[copy]` button on fenced code.** Same `renderNode` callback\n// intercepts `code` tokens and parents the default body in a\n// {@link CodeBlockWrapper} BoxRenderable subclass that hosts a\n// one-row `[lang] … [copy]` header. Clicking the indicator\n// pushes the body to the OS clipboard via OSC 52. The wrapper\n// lives entirely inside OpenTUI — React sees ONE `<markdown>`\n// element across the whole stream lifecycle, so there's no\n// React-tree restructure at the streaming → finalize boundary\n// (that was the failure mode of an earlier \"split markdown into\n// React subtrees\" approach).\n//\n// Coalesced + `renderNode` strips the \"space\" tokens the markdown's\n// top-level pass would normally fold into `block.marginTop`. The fix is\n// a small spacing rule applied uniformly: every non-first block gets\n// `marginTop: 1`, and the `CodeBlockWrapper` pins its own\n// `marginBottom: 0` so a code fence followed by prose doesn't stack the\n// markdown's default `getInterBlockMargin(code) = 1` with the next\n// block's `marginTop`. In every common configuration (prose ↔ prose,\n// prose ↔ code, code ↔ code, all separated by `\\n\\n` in the source)\n// the resulting visual shape is bit-identical to what top-level mode\n// would have produced from the parser's space tokens — the rule simply\n// reimplements that pass at our layer.\n//\n// The callback is captured at `<markdown>` construction time (OpenTUI\n// doesn't expose a setter for `renderNode`), so it must close over\n// stable references. We feed it via a `ref` updated on every render so\n// live colour values drive newly-built renderables AND any\n// already-mounted {@link CodeBlockWrapper}s — the React component\n// registers each wrapper in `bag.wrappers` on construction and a\n// `useEffect` sweep re-paints their header chrome on every theme\n// switch. The wrapper's `destroy()` removes it from the set so the\n// sweep never touches a dead renderable.\n\n/**\n * Mutable state shared with the markdown's `renderNode` callback and\n * every {@link CodeBlockWrapper} mounted by it.\n *\n * `wrappers` is the registry the React component sweeps on every theme\n * change to re-paint already-mounted copy buttons / header chrome — see\n * {@link MarkdownBlock} for the `useEffect` that drives it. Wrappers add\n * themselves on construction and remove themselves in `destroy()`.\n */\ninterface RenderNodeBag {\n ctx: CliRenderer\n colors: ThemeColors\n surfaces: ThemeSurfaces\n wrappers: Set<CodeBlockWrapper>\n}\n\n/** Click-feedback delay before `[copied]` reverts to `[copy]`, in ms. */\nconst COPY_FEEDBACK_MS = 1200\n\n/**\n * BoxRenderable subclass that hosts a `[lang] … [copy]` header above\n * an inner {@link CodeRenderable} body. Returned from\n * {@link makeMarkdownRenderNode} for every `code` token so the affordance\n * lives entirely inside OpenTUI — React doesn't see the wrapper.\n *\n * The accessor pairs below mirror the CodeRenderable surface (`content`,\n * `filetype`, `syntaxStyle`, `fg`, `bg`, `conceal`, `streaming`) so\n * coalesced mode's `applyCodeBlockRenderable` — which writes those\n * properties on the wrapper for every delta — lands on the body where\n * it produces visible output.\n *\n * Two of those forwarders deliberately ignore the value the markdown\n * writes:\n *\n * - `drawUnstyledText` is pinned to `false` so the body's `content`\n * setter stays on the \"preserve last-styled buffer while async\n * highlight runs\" path. The markdown's `applyCodeBlockRenderable`\n * writes `!(streaming && concealCode)` here, which evaluates to\n * `true` for our defaults — that would put the body on the\n * synchronous `textBuffer.setText` path and reintroduce a per-delta\n * plain → styled colour swap.\n *\n * - `marginBottom` is pinned to `0`. The markdown writes\n * `getInterBlockMargin(code) = 1` here on every delta; combined with\n * the `marginTop: 1` `renderNode` applies to the next block, it\n * would stack into a two-row gap below every fence. Pinning to 0\n * makes the next block's `marginTop` the single source of truth for\n * inter-block spacing.\n */\nclass CodeBlockWrapper extends BoxRenderable {\n /** Live code body owned by this wrapper. Updates pass through here. */\n private readonly body: CodeRenderable\n /** Header chrome — re-themed on every {@link applyTheme} call. */\n private readonly header: BoxRenderable\n private readonly spacer: BoxRenderable\n private readonly langLabel: TextRenderable\n private readonly button: TextRenderable\n /** Bag we belong to. Used for live theme lookups and self-deregistration. */\n private readonly bag: RenderNodeBag\n /** Pending feedback-reset timer for the `[copied]` flash. */\n private feedbackTimer: ReturnType<typeof setTimeout> | null = null\n /** Whether the button is currently showing `[copied]`. */\n private copied = false\n /**\n * Latest body content, captured every time the markdown writes to\n * `content`. Read by the click handler so a copy click pushes the\n * up-to-date code (not whatever was there on first mount).\n */\n private latestContent: string\n\n constructor(ctx: CliRenderer, body: CodeRenderable, options: {\n lang: string\n bag: RenderNodeBag\n }) {\n const { colors, surfaces } = options.bag\n super(ctx, {\n flexDirection: 'column',\n flexShrink: 0,\n alignSelf: 'stretch',\n // Elevated \"code panel\" paint — same tier as modal overlays so the\n // fenced block reads as a distinct surface against the prose body.\n // Theme switches re-drive this via `applyTheme` (and pinned bg\n // setter on the body keeps it from being clobbered by markdown\n // delta passes — see class doc).\n backgroundColor: surfaces.modal,\n })\n this.body = body\n this.bag = options.bag\n this.latestContent = body.content\n\n // Outer margin lives on us, not on the body, so the header sits flush\n // with the code below it. Pin `drawUnstyledText` to false up front so\n // even the very first render uses the \"wait for highlight, keep prior\n // styled buffer\" path (see class doc). Pin `bg` here to the elevated\n // surface so every code row picks up the panel paint; the pinned\n // setter below keeps the markdown's per-delta `bg` writes from\n // clobbering it back to the prose surface.\n body.marginTop = 0\n body.marginBottom = 0\n body.drawUnstyledText = false\n body.bg = surfaces.modal\n\n this.header = new BoxRenderable(ctx, {\n flexDirection: 'row',\n height: 1,\n alignSelf: 'stretch',\n backgroundColor: surfaces.modal,\n })\n this.langLabel = new TextRenderable(ctx, {\n content: options.lang && options.lang.length > 0 ? options.lang : 'code',\n fg: colors.mute,\n selectable: false,\n })\n this.spacer = new BoxRenderable(ctx, {\n flexGrow: 1,\n backgroundColor: surfaces.modal,\n })\n this.button = new TextRenderable(ctx, {\n content: '[copy]',\n fg: colors.warn,\n selectable: false,\n })\n this.button.onMouseDown = (event) => {\n event.stopPropagation()\n event.preventDefault()\n if (!writeToClipboard(this.latestContent))\n return\n // Always read the LATEST colours from the bag — a theme switch\n // mid-click would otherwise leave the button painted with the\n // construction-time accent / warn.\n const live = this.bag.colors\n if (!this.copied) {\n this.copied = true\n this.button.content = '[copied]'\n this.button.fg = live.accent\n }\n if (this.feedbackTimer)\n clearTimeout(this.feedbackTimer)\n this.feedbackTimer = setTimeout(() => {\n this.copied = false\n this.feedbackTimer = null\n if (this.button.isDestroyed)\n return\n this.button.content = '[copy]'\n this.button.fg = this.bag.colors.warn\n }, COPY_FEEDBACK_MS)\n }\n\n this.header.add(this.langLabel)\n this.header.add(this.spacer)\n this.header.add(this.button)\n this.add(this.header)\n this.add(this.body)\n\n // Register so {@link MarkdownBlock}'s theme effect can find us.\n options.bag.wrappers.add(this)\n }\n\n /**\n * Re-paint header chrome with the current theme. Called from\n * {@link MarkdownBlock}'s `useEffect` after every theme switch — the\n * body's `fg`/`bg` are already re-driven by the markdown's own\n * `rerenderBlocks` pass through our forwarding accessors, so we only\n * need to refresh the bits the markdown doesn't manage: the wrapper's\n * own background, the header row, the spacer, the lang label, and the\n * `[copy]`/`[copied]` button colour (which depends on `this.copied`).\n */\n applyTheme(colors: ThemeColors, surfaces: ThemeSurfaces): void {\n this.backgroundColor = surfaces.modal\n this.header.backgroundColor = surfaces.modal\n this.spacer.backgroundColor = surfaces.modal\n // Refresh the body's per-line bg so the panel paint follows a theme\n // switch even though the pinned setter below ignores delta-time\n // writes from the markdown.\n this.body.bg = surfaces.modal\n this.langLabel.fg = colors.mute\n this.button.fg = this.copied ? colors.accent : colors.warn\n }\n\n // Forwarding accessors — the markdown's `applyCodeBlockRenderable`\n // writes to these by name on whatever `state.renderable` ended up\n // being. The setters proxy to the body so updates land where they\n // actually produce visible output; the getters are paired for ESLint's\n // accessor-pairs rule and for any future reader that wants to inspect\n // current state without reaching into `.body`.\n\n get content(): string {\n return this.body.content\n }\n\n set content(value: string) {\n this.latestContent = value\n this.body.content = value\n }\n\n get filetype(): string | undefined {\n return this.body.filetype\n }\n\n set filetype(value: string | undefined) {\n this.body.filetype = value\n }\n\n get syntaxStyle(): CodeRenderable['syntaxStyle'] {\n return this.body.syntaxStyle\n }\n\n set syntaxStyle(value: ConstructorParameters<typeof CodeRenderable>[1]['syntaxStyle']) {\n this.body.syntaxStyle = value\n }\n\n get fg(): CodeRenderable['fg'] {\n return this.body.fg\n }\n\n set fg(value: ConstructorParameters<typeof CodeRenderable>[1]['fg']) {\n this.body.fg = value\n }\n\n /**\n * Always reports the live elevated-panel paint: see the setter doc.\n */\n get bg(): CodeRenderable['bg'] {\n return this.body.bg\n }\n\n /**\n * Pinned to {@link ThemeSurfaces.modal} — the elevated code-panel\n * surface. The markdown's `applyCodeBlockRenderable` writes `bg` on\n * every delta from whatever surface the parent `<markdown>` carries\n * (the prose body paint). Forwarding that through would flip the\n * code panel back to the prose surface on every keystroke and erase\n * the visual lift. Theme switches are funnelled through\n * {@link applyTheme} instead, which rewrites `body.bg` directly.\n */\n set bg(_value: ConstructorParameters<typeof CodeRenderable>[1]['bg']) {\n this.body.bg = this.bag.surfaces.modal\n }\n\n get conceal(): boolean {\n return this.body.conceal\n }\n\n set conceal(value: boolean) {\n this.body.conceal = value\n }\n\n get streaming(): boolean {\n return this.body.streaming\n }\n\n set streaming(value: boolean) {\n this.body.streaming = value\n }\n\n /**\n * Always reports `false`: see the setter doc.\n */\n get drawUnstyledText(): boolean {\n return false\n }\n\n /**\n * Pinned to `false` regardless of what the markdown writes. See the\n * class doc — leaving this open to the markdown's default would put\n * the body's `content` setter on the synchronous-plain-text path on\n * every delta, replacing the styled buffer with raw text for one\n * frame and producing a visible colour swap.\n */\n set drawUnstyledText(_value: boolean) {\n if (this.body.drawUnstyledText !== false)\n this.body.drawUnstyledText = false\n }\n\n /**\n * Always reports `0`: see the setter doc.\n */\n override get marginBottom(): number {\n return 0\n }\n\n /**\n * Pinned to `0`. The next block's `marginTop` (which `renderNode`\n * sets to `1` on every non-first block) is the single source of\n * inter-block spacing. Letting the markdown also write\n * `getInterBlockMargin(code) = 1` here on every delta would stack to\n * a two-row gap below every fence.\n */\n override set marginBottom(_value: number | 'auto' | `${number}%` | null | undefined) {\n // no-op\n }\n\n override destroy(): void {\n this.bag.wrappers.delete(this)\n if (this.feedbackTimer) {\n clearTimeout(this.feedbackTimer)\n this.feedbackTimer = null\n }\n super.destroy()\n }\n}\n\n/**\n * Block-index regex applied to OpenTUI's auto-generated renderable ids\n * (`${markdown.id}-block-${index}`). The trailing digits give us the\n * block's position inside the markdown's child list, which is the only\n * piece of context we need to gate the \"non-first block → marginTop: 1\"\n * spacing rule.\n */\nconst BLOCK_ID_INDEX_RE = /-block-(\\d+)$/\n\n/**\n * Build a stable `renderNode` callback wired to a live `RenderNodeBag`.\n *\n * Returns:\n * - `code` tokens → {@link CodeBlockWrapper} (hosts the `[lang] …\n * [copy]` header).\n * - everything else (prose, heading, list, table, …) → the renderable\n * we got from `context.defaultRender()`, mutated in place.\n *\n * Critical: we MUST return the renderable we mutated, not `undefined`.\n * OpenTUI's coalesced-mode loop, when `renderNode` returns a falsy\n * value, creates a fresh default renderable (`markdown.ts ~8851`) — so\n * anything we mutated via `defaultRender()` is thrown away.\n *\n * Tables: returning the default `TextTableRenderable` looks like it\n * should orphan the markdown's `tableContentCache` tuple (incremental\n * table updates depend on it), but the markdown has a follow-up branch\n * (`markdown.ts ~8854`) that re-builds the cache from the token when\n * `renderNode` returned a `TextTableRenderable` without one. So we can\n * safely mutate + return the table renderable too, and the spacing\n * rule applies uniformly.\n *\n * Every non-first block gets `marginTop: 1`. This reimplements the\n * inter-block spacing that top-level mode would compute from the\n * parser's space tokens — coalesced mode + `renderNode` strips them\n * (see the file-level comment), so we recompute the spacing ourselves.\n * The result is bit-identical to top-level mode for every\n * well-formatted markdown source.\n */\nfunction makeMarkdownRenderNode(bag: MutableRefObject<RenderNodeBag>) {\n return (token: Token, context: RenderNodeContext) => {\n const inner = context.defaultRender()\n if (!inner)\n return undefined\n\n const isFirstBlock = parseBlockIndex(inner.id) === 0\n const topMargin = isFirstBlock ? 0 : 1\n\n if (token.type === 'code' && inner instanceof CodeRenderable) {\n // Code fence: wrap with our `[lang] … [copy]` header.\n const lang = typeof (token as { lang?: unknown }).lang === 'string'\n ? (token as { lang: string }).lang\n : ''\n const wrapper = new CodeBlockWrapper(bag.current.ctx, inner, { lang, bag: bag.current })\n wrapper.marginTop = topMargin\n return wrapper\n }\n\n // Prose, heading, list, table, etc. — mutate the default renderable's\n // top margin and return it. The markdown's downstream logic\n // (`applyMarkdownCodeRenderable` / `applyCodeBlockRenderable` /\n // table cache build) handles whatever specific renderable type we\n // return without further intervention.\n inner.marginTop = topMargin\n return inner\n }\n}\n\n/**\n * Pull the trailing block index out of an OpenTUI renderable id. Returns\n * `0` for any id that doesn't match the expected `…-block-N` shape — the\n * spacing rule then treats unparseable ids as \"first block\" (no\n * `marginTop`), which is the safer side of the rendering edge case.\n */\nfunction parseBlockIndex(id: string | undefined): number {\n if (!id)\n return 0\n const match = BLOCK_ID_INDEX_RE.exec(id)\n if (!match)\n return 0\n const parsed = Number.parseInt(match[1] ?? '', 10)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nconst MarkdownBlock = memo(({ text, dim, selected = false }: {\n text: string\n dim: boolean\n /**\n * Whether the enclosing row is painted with the selection surface.\n * Switches the `syntaxStyle` to a variant whose inline-code chips\n * (`markup.raw`) and fenced code blocks (`markup.raw.block`) carry\n * the selection bg — without this swap they keep their resting\n * `bg` and read as \"punched out\" rectangles against the highlight.\n */\n selected?: boolean\n}) => {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const mdStyle = useMdStyle({ selected })\n const renderer = useRenderer()\n\n // Strip leading / trailing newlines. The model commonly emits text\n // with `\\n\\n` paragraph breaks at the boundaries between tool calls\n // and narration; the markdown renderable paints those as phantom\n // blank rows above and below the content, producing the visible\n // \"huge gap between tool calls and the next text\" complaint. Inner\n // newlines stay — they ARE the paragraph breaks the model intended.\n // `String.trim` would also drop leading whitespace inside the first\n // line (indented code blocks, etc.) so we trim only newlines.\n const content = text.replace(/^\\n+|\\n+$/g, '')\n\n // Live values for the renderNode callback. The callback identity must\n // stay stable across renders (OpenTUI captures it at <markdown>\n // construction time and never re-reads), so we route the current values\n // through a ref and let the callback read `bag.current.*` lazily. The\n // `wrappers` set tracks every live {@link CodeBlockWrapper} so the theme\n // effect below can re-paint their non-body chrome on a theme switch.\n const bag = useRef<RenderNodeBag>({\n ctx: renderer,\n colors: COLOR,\n surfaces: SURFACE,\n wrappers: new Set(),\n })\n bag.current.ctx = renderer\n bag.current.colors = COLOR\n bag.current.surfaces = SURFACE\n\n // Re-paint live copy-button chrome on theme switch. The markdown's own\n // `rerenderBlocks` pass refreshes the body colours through our\n // forwarding accessors (`bg`, `fg`); the wrapper's surrounding\n // `BoxRenderable`s + `[lang]` / `[copy]` `TextRenderable`s aren't part\n // of that pass, so we sweep the registry ourselves.\n useEffect(() => {\n for (const wrapper of bag.current.wrappers)\n wrapper.applyTheme(COLOR, SURFACE)\n }, [COLOR, SURFACE])\n\n // Stable for the lifetime of this instance (see comment above).\n const renderNode = useMemo(() => makeMarkdownRenderNode(bag), [])\n\n return (\n <markdown\n content={content}\n syntaxStyle={mdStyle}\n // Pinned constants — see the JSDoc on this component.\n streaming={true}\n internalBlockMode=\"coalesced\"\n fg={dim ? COLOR.dim : undefined}\n // Solid background paints over the prior frame's cells in one\n // pass. Without it the renderer leaves cells transparent and\n // partial repaints can briefly show torn text underneath a\n // re-flowing trailing block. Tracks the row's selection state\n // so the markdown bg merges into the highlight (the inline-code\n // chips also flip in `useMdStyle({ selected })` above — paint\n // and chip both flow into one continuous selection band).\n bg={selected ? SURFACE.selection : SURFACE.background}\n renderNode={renderNode}\n />\n )\n})\nMarkdownBlock.displayName = 'MarkdownBlock'\n\n// ---------------------------------------------------------------------------\n// Tool result — left-bar block, truncated. Caps at TOOL_RESULT_MAX_LINES so a\n// 200-line file doesn't drown the transcript.\n// ---------------------------------------------------------------------------\n\nconst TOOL_RESULT_MAX_LINES = 6\n\nfunction ToolResultBlock({ text, indent }: { text: string, indent: number }) {\n const COLOR = useColors()\n // Strip ANSI / OSC / control sequences before splitting. Shell stdout\n // routinely carries CSI color codes, cursor moves, and screen-clear\n // sequences (e.g. when piping a log captured from another TUI). OpenTUI's\n // `<text>` does not parse escapes — they'd land in the cell buffer and\n // either render as garbage or be re-interpreted by the host terminal,\n // jumping the cursor and producing rows that look \"empty\" (just the\n // outer borders). Sanitize once at the rendering boundary so the model\n // still sees raw bytes upstream but the TUI gets a clean string.\n const sanitized = stripAnsiSequences(text)\n const rawLines = sanitized.split('\\n')\n let end = rawLines.length\n while (end > 0 && rawLines[end - 1]!.trim() === '')\n end--\n if (end === 0)\n return null\n const lines = rawLines.slice(0, end)\n const visible = lines.slice(0, TOOL_RESULT_MAX_LINES)\n const omitted = Math.max(0, lines.length - TOOL_RESULT_MAX_LINES)\n\n return (\n <box style={{ paddingLeft: indent, flexDirection: 'column' }}>\n {visible.map((line, i) => (\n <text key={i} fg={COLOR.mute}>\n <span fg={COLOR.borderActive}>┃ </span>\n {line || ' '}\n </text>\n ))}\n {omitted > 0 && (\n <text fg={COLOR.mute}>\n <span fg={COLOR.borderActive}>┃ </span>\n {`… ${omitted} more line${omitted === 1 ? '' : 's'}`}\n </text>\n )}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Edit diff — native <diff> renderable wrapper.\n//\n// OpenTUI ships a `DiffRenderable` that parses unified diff syntax and\n// renders it with full per-language syntax highlighting (via tree-sitter,\n// same client the markdown renderer uses for fenced blocks), bg row\n// coloring, and word/char/none wrap. We just serialize our `EditPayload`\n// to a unified diff string and pass it through; the renderable does the\n// rest.\n//\n// Header rides the same `↳ <tool> <path>` shape as a normal tool line.\n// `replace_all` and multi-hunk badges live in the meta tail of the\n// header so the renderable below stays clean.\n// ---------------------------------------------------------------------------\n\nfunction EditDiffBlock({ payload, dim }: { payload: EditPayload, dim: boolean }) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const mdStyle = useMdStyle()\n const { settings } = useSettings()\n const filetype = useMemo(() => filetypeFromPath(payload.path), [payload.path])\n\n // Badge for replace_all (edit/multi_edit only). Surfaces in the\n // header meta so the diff body itself stays clean of annotation\n // lines. Multi-edit with multiple hunks gets a count too.\n const replaceAllCount = payload.hunks.filter(h => h.replaceAll).length\n const hunkBadge = payload.tool === 'multi_edit' && payload.hunks.length > 1\n ? `${payload.hunks.length} hunks`\n : null\n\n const outcomeSummary = summarizeOutcomes(payload.outcomes)\n const hasMixedOutcomes = !!payload.outcomes && (outcomeSummary.denied + outcomeSummary.skipped + outcomeSummary.failed) > 0\n\n // Pre-compute total +/− stats once for the header. Cheap enough to\n // always show — the user gets a glance signal of how big the change\n // is regardless of the density setting below.\n const editSummary = useMemo(() => summarizeEditPayload(payload), [payload])\n\n // Per-hunk rendering — used whenever outcomes are present (live OR\n // replay). Splits the call into one diff per hunk with a status badge\n // above each so denied / skipped / failed hunks read as discrete units\n // instead of being silently dropped from a combined diff body.\n const perHunkMode = hasMixedOutcomes\n const compact = settings.editDiffDisplay === 'compact'\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, alignSelf: 'stretch' }}>\n <text fg={dim ? COLOR.dim : COLOR.model}>\n <span fg={COLOR.mute}>↳ </span>\n <span fg={dim ? COLOR.dim : COLOR.model}>{displayNameFor(payload.tool)}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={dim ? COLOR.dim : COLOR.warn}>{payload.path}</span>\n {(editSummary.totalAdded > 0 || editSummary.totalRemoved > 0) && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n {editSummary.totalAdded > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{`+${editSummary.totalAdded}`}</span>\n )}\n {editSummary.totalAdded > 0 && editSummary.totalRemoved > 0 && (\n <span fg={COLOR.mute}>{' '}</span>\n )}\n {editSummary.totalRemoved > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{`−${editSummary.totalRemoved}`}</span>\n )}\n </>\n )}\n {hunkBadge && <span fg={COLOR.mute}>{` · ${hunkBadge}`}</span>}\n {replaceAllCount > 0 && (\n <span fg={COLOR.mute}>{` · ${replaceAllCount > 1 ? `${replaceAllCount}× ` : ''}replace all`}</span>\n )}\n {payload.outcomes && payload.outcomes.length > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={outcomeSummary.applied > 0 ? COLOR.accent : COLOR.mute}>{`${outcomeSummary.applied} applied`}</span>\n {outcomeSummary.denied > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.error}>{`${outcomeSummary.denied} denied`}</span>\n </>\n )}\n {outcomeSummary.skipped > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.warn}>{`${outcomeSummary.skipped} skipped`}</span>\n </>\n )}\n {outcomeSummary.failed > 0 && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.error}>{`${outcomeSummary.failed} failed`}</span>\n </>\n )}\n </>\n )}\n </text>\n {compact\n ? <CompactDiffSummary summary={editSummary} dim={dim} />\n : perHunkMode\n ? (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {payload.hunks.map((hunk, i) => (\n <HunkBlock\n key={i}\n index={i}\n hunk={hunk}\n outcome={payload.outcomes?.[i]}\n tool={payload.tool}\n path={payload.path}\n filetype={filetype}\n mdStyle={mdStyle}\n COLOR={COLOR}\n SURFACE={SURFACE}\n dim={dim}\n />\n ))}\n </box>\n )\n : (\n <diff\n diff={payload.priorContent !== undefined\n ? buildContextualDiff(payload, payload.priorContent)\n : buildUnifiedDiff(payload)}\n view=\"unified\"\n wrapMode=\"word\"\n showLineNumbers={true}\n {...(filetype ? { filetype } : {})}\n syntaxStyle={mdStyle}\n addedBg={SURFACE.diff.addBg}\n removedBg={SURFACE.diff.removeBg}\n {...(SURFACE.diff.addContentBg ? { addedContentBg: SURFACE.diff.addContentBg } : {})}\n {...(SURFACE.diff.removeContentBg ? { removedContentBg: SURFACE.diff.removeContentBg } : {})}\n {...(SURFACE.diff.contextBg ? { contextBg: SURFACE.diff.contextBg } : {})}\n addedSignColor={SURFACE.diff.addFg}\n removedSignColor={SURFACE.diff.removeFg}\n treeSitterClient={getTreeSitterClient()}\n {...(dim ? { fg: COLOR.dim } : {})}\n />\n )}\n </box>\n )\n}\n\n/**\n * Compact-mode body — one line per hunk under the tool header. Format:\n *\n * ` L42 · +2 −1 · old → new`\n *\n * Where `L<n>` is the new-file line position (omitted when priorContent\n * is absent and the position is unknown), and the `old → new` preview\n * is the FIRST changed line on each side, ASCII-arrowed and truncated\n * by the renderer's own word-wrap. A pure addition shows `+ new` only;\n * a pure deletion shows `− old` only.\n */\nfunction CompactDiffSummary({\n summary,\n dim,\n}: {\n summary: ReturnType<typeof summarizeEditPayload>\n dim: boolean\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n if (summary.hunks.length === 0)\n return null\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {summary.hunks.map((h, i) => {\n const oldPreview = h.firstOld?.trim()\n const newPreview = h.firstNew?.trim()\n return (\n <text key={i} fg={dim ? COLOR.dim : COLOR.mute} wrapMode=\"word\">\n <span fg={COLOR.mute}>{' '}</span>\n {h.line !== undefined && (\n <>\n <span fg={dim ? COLOR.dim : COLOR.mute}>{`L${h.line}`}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n </>\n )}\n {h.added > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{`+${h.added}`}</span>\n )}\n {h.added > 0 && h.removed > 0 && <span fg={COLOR.mute}>{' '}</span>}\n {h.removed > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{`−${h.removed}`}</span>\n )}\n {(oldPreview || newPreview) && <span fg={COLOR.mute}>{' · '}</span>}\n {oldPreview && newPreview\n ? (\n <>\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{oldPreview}</span>\n <span fg={COLOR.mute}>{' → '}</span>\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{newPreview}</span>\n </>\n )\n : oldPreview\n ? (\n <>\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{'− '}</span>\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{oldPreview}</span>\n </>\n )\n : newPreview\n ? (\n <>\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{'+ '}</span>\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{newPreview}</span>\n </>\n )\n : null}\n </text>\n )\n })}\n </box>\n )\n}\n\n/**\n * One hunk inside an `EditDiffBlock` rendered as its own mini-diff with\n * a status badge above it. Used only in the per-hunk view (multi_edit\n * with mixed outcomes) so denied / skipped / failed edits remain\n * visible alongside the applied ones.\n */\n// Pin every status badge to one column width so they read as a clean\n// gutter when multiple hunks stack. `skipped` is the longest at 7\n// chars; everything else gets right-padded to match.\nconst BADGE_WIDTH = 7\n\nfunction HunkBlock({\n index,\n hunk,\n outcome,\n tool,\n path,\n filetype,\n mdStyle,\n COLOR,\n SURFACE,\n dim,\n}: {\n index: number\n hunk: EditPayload['hunks'][number]\n outcome: EditOutcome | undefined\n tool: EditPayload['tool']\n path: string\n filetype: string | undefined\n mdStyle: ReturnType<typeof useMdStyle>\n COLOR: ReturnType<typeof useColors>\n SURFACE: ReturnType<typeof useSurfaces>\n dim: boolean\n}) {\n const { settings } = useSettings()\n const kind = outcome?.kind ?? 'applied'\n const reason = outcome?.reason\n const dimmed = dim || kind !== 'applied'\n const compact = settings.editDiffDisplay === 'compact'\n\n // Per-hunk diff body — one mini unified diff. We build a stand-alone\n // EditPayload around this single hunk so `buildUnifiedDiff` produces a\n // valid `--- / +++ / @@` header even for denied / skipped hunks\n // (where the file content was never actually modified). Line numbers\n // here are synthetic within the snippet (start at 1) — we can't anchor\n // an unapplied hunk to a real file position.\n const diffText = useMemo(() => buildUnifiedDiff({\n tool,\n path,\n hunks: [hunk],\n }), [hunk, tool, path])\n\n // One-liner stats + first-line preview shown alongside the badge in\n // compact mode AND as a small caption in full mode. Same shape as\n // `CompactDiffSummary` so the two modes read consistently.\n const hunkStats = useMemo(\n () => summarizeEditPayload({ tool, path, hunks: [hunk] }).hunks[0],\n [hunk, tool, path],\n )\n\n const badge = kind === 'applied'\n ? { label: 'applied', fg: COLOR.accent }\n : kind === 'denied'\n ? { label: 'denied', fg: COLOR.error }\n : kind === 'skipped'\n ? { label: 'skipped', fg: COLOR.warn }\n : kind === 'failed'\n ? { label: 'failed', fg: COLOR.error }\n : { label: 'pending', fg: COLOR.mute }\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, marginTop: index === 0 ? 0 : 1 }}>\n <text fg={COLOR.mute} wrapMode=\"word\">\n <span fg={COLOR.mute}>{` #${(index + 1).toString().padStart(2)} `}</span>\n <span fg={badge.fg}>{badge.label.padEnd(BADGE_WIDTH)}</span>\n {hunkStats && (hunkStats.added > 0 || hunkStats.removed > 0) && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n {hunkStats.added > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.addFg}>{`+${hunkStats.added}`}</span>\n )}\n {hunkStats.added > 0 && hunkStats.removed > 0 && <span fg={COLOR.mute}>{' '}</span>}\n {hunkStats.removed > 0 && (\n <span fg={dim ? COLOR.dim : SURFACE.diff.removeFg}>{`−${hunkStats.removed}`}</span>\n )}\n </>\n )}\n {reason && (\n <>\n <span fg={COLOR.mute}>{': '}</span>\n <span fg={COLOR.dim}>{reason}</span>\n </>\n )}\n </text>\n {!compact && (\n <diff\n diff={diffText}\n view=\"unified\"\n wrapMode=\"word\"\n showLineNumbers={true}\n {...(filetype ? { filetype } : {})}\n syntaxStyle={mdStyle}\n addedBg={SURFACE.diff.addBg}\n removedBg={SURFACE.diff.removeBg}\n {...(SURFACE.diff.addContentBg ? { addedContentBg: SURFACE.diff.addContentBg } : {})}\n {...(SURFACE.diff.removeContentBg ? { removedContentBg: SURFACE.diff.removeContentBg } : {})}\n {...(SURFACE.diff.contextBg ? { contextBg: SURFACE.diff.contextBg } : {})}\n addedSignColor={SURFACE.diff.addFg}\n removedSignColor={SURFACE.diff.removeFg}\n treeSitterClient={getTreeSitterClient()}\n {...(dimmed ? { fg: COLOR.dim } : {})}\n />\n )}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Tool call — three display modes:\n//\n// - `'formatted'` (default) — per-tool curated one-liner. Uses the\n// {@link formatToolCall} registry; native tools get a clean\n// `↳ <Verb> <target> · <meta>` line, MCP / host tools fall back\n// to a `↳ <Title Case>` line with no args.\n// - `'full'` — debug view: `↳ <name>` header + the entire raw\n// `input` rendered as syntax-highlighted JSON via the native\n// `<code>` renderable. Useful for inspecting exactly what the\n// model emitted.\n// - `'hidden'` — gated upstream by `isVisible`, never reaches\n// this component.\n//\n// `event.text` carries the legacy `name({json})` preview as a final\n// fallback for `'formatted'` when neither a curated formatter nor\n// structured input is available (e.g. session reloaded from disk\n// without an input record — defensive guard).\n// ---------------------------------------------------------------------------\n\nconst FULL_VIEW_MAX_INPUT_BYTES = 32 * 1024\n\nfunction ToolCallBlock({\n event,\n display,\n dim,\n}: {\n event: StreamEvent\n display: 'formatted' | 'full'\n dim: boolean\n}) {\n const COLOR = useColors()\n const mdStyle = useMdStyle()\n const name = event.tool ?? ''\n const verb = displayNameFor(name, event.input)\n // Hooks must run unconditionally — both `pretty` (for `'full'`) and\n // `line` (for `'formatted'`) memo on `event.input` so the branch\n // below is a pure data-conditional, not a hooks-conditional.\n const pretty = useMemo(() => {\n if (!event.input)\n return null\n try {\n const text = JSON.stringify(event.input, null, 2)\n return text.length > FULL_VIEW_MAX_INPUT_BYTES\n ? `${text.slice(0, FULL_VIEW_MAX_INPUT_BYTES)}\\n…`\n : text\n }\n catch {\n return null\n }\n }, [event.input])\n const line = useMemo(\n () => (event.input ? formatToolCall(name, event.input) : null),\n [event.input, name],\n )\n\n if (display === 'full') {\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, alignSelf: 'stretch' }}>\n <text fg={dim ? COLOR.dim : COLOR.model}>\n <span fg={COLOR.mute}>↳ </span>\n <span fg={dim ? COLOR.dim : COLOR.model}>{verb}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.mute}>{name}</span>\n </text>\n {pretty\n ? (\n <code\n content={pretty}\n filetype=\"json\"\n syntaxStyle={mdStyle}\n {...(dim ? { fg: COLOR.dim } : {})}\n />\n )\n : (\n <text fg={COLOR.mute}>{' (no structured input)'}</text>\n )}\n </box>\n )\n }\n\n return (\n <text fg={dim ? COLOR.dim : COLOR.model}>\n <span fg={COLOR.mute}>↳ </span>\n <span fg={dim ? COLOR.dim : COLOR.model}>{verb}</span>\n {line?.target && (\n <>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={dim ? COLOR.dim : COLOR.warn}>{line.target}</span>\n </>\n )}\n {line?.meta && line.meta.length > 0 && (\n <span fg={COLOR.mute}>{` · ${line.meta.join(' · ')}`}</span>\n )}\n </text>\n )\n}\n\n// ---------------------------------------------------------------------------\n// TodoInProgressList — inline \"what's in progress\" affordance painted\n// directly under a `todowrite` tool call. Reads the call's `input.todos`\n// (the model's checkpoint payload) and renders one indented row per\n// `in_progress` item. Other statuses are out of scope — the existing\n// tool-result tally already reports them and the modal is one keystroke\n// away for the full picture.\n//\n// Rendered as a sibling of `ToolCallBlock`, NOT a child, so it inherits\n// the `tool` row's top margin and contributes its own bottom rhythm.\n// Empty payload (or no in-progress items) collapses to `null` so the\n// transcript stays tight when there's nothing live.\n// ---------------------------------------------------------------------------\n\nfunction TodoInProgressList({\n input,\n dim,\n}: {\n input: Record<string, unknown> | undefined\n dim: boolean\n}) {\n const COLOR = useColors()\n const items = useMemo(() => {\n const raw = (input as { todos?: unknown } | undefined)?.todos\n if (!Array.isArray(raw))\n return [] as { id: string, content: string }[]\n // Defensive parse — the renderer never trusts the model's payload\n // shape, only its own type checks. Anything that doesn't look like\n // `{ id, content, status: 'in_progress' }` is silently dropped.\n return raw.flatMap((t) => {\n if (t == null || typeof t !== 'object')\n return []\n const rec = t as Record<string, unknown>\n if (rec.status !== 'in_progress')\n return []\n const id = typeof rec.id === 'string' ? rec.id : ''\n const content = typeof rec.content === 'string' ? rec.content : ''\n if (!id || !content)\n return []\n return [{ id, content }]\n })\n }, [input])\n if (items.length === 0)\n return null\n const glyph = TODO_STATUS_GLYPHS.in_progress\n return (\n <box style={{ flexDirection: 'column', marginLeft: 2 }}>\n {items.map(item => (\n <text key={item.id} wrapMode=\"none\">\n <span fg={COLOR.mute}>{`${glyph} `}</span>\n <span fg={dim ? COLOR.dim : COLOR.warn}>{item.content}</span>\n </text>\n ))}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Back-compat re-exports\n//\n// These symbols used to live in this module and have since moved into\n// `zidane/chat` so a GUI shell can consume them without pulling OpenTUI.\n// The re-exports here preserve the existing `zidane/tui` import surface\n// for one minor release; new code should import directly from\n// `zidane/chat` (or `zidane/chat/<module>` where applicable).\n//\n// @deprecated These re-exports will be removed in the next minor.\n// ---------------------------------------------------------------------------\n\n/** @deprecated Import from `zidane/chat` (`../chat/hints`). */\nexport type { Hint } from '../chat/hints'\n/** @deprecated Import from `zidane/chat` (`../chat/hints`). */\nexport { clipHintsToWidth, hintsLength, truncateTrailing } from '../chat/hints'\n/** @deprecated Import from `zidane/chat` (`../chat/markdown-segments`). */\nexport type { MarkdownSegment } from '../chat/markdown-segments'\n/** @deprecated Import from `zidane/chat` (`../chat/markdown-segments`). */\nexport { splitMarkdownCodeBlocks } from '../chat/markdown-segments'\n/** @deprecated Import from `zidane/chat` (`../chat/store`). */\nexport { isEditErrorResult, isVisible, marginTopFor } from '../chat/store'\n/** @deprecated Import from `zidane/chat` (`../chat/tool-formatters`). */\nexport { displayNameFor, formatToolCall, TOOL_DISPLAY } from '../chat/tool-formatters'\n/** @deprecated Import from `zidane/chat` (`../chat/tool-formatters`). */\nexport type { ToolDisplayMeta, ToolFormatLine } from '../chat/tool-formatters'\n/** @deprecated Import from `zidane/chat` (`../chat/transcript-anchors`). */\nexport type { TranscriptItem } from '../chat/transcript-anchors'\n/** @deprecated Import from `zidane/chat` (`../chat/transcript-anchors`). */\nexport { computeTurnAnchors } from '../chat/transcript-anchors'\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable } from '@opentui/core'\nimport type { FzfResultItem } from 'fzf'\nimport { readdirSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, relative } from 'node:path'\nimport { useKeyboard } from '@opentui/react'\nimport { byLengthAsc, Fzf } from 'fzf'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\nconst VISIBLE_ROWS = 12\nconst HOME = homedir()\nconst MAX_ENTRIES = 50_000\nconst MAX_DEPTH = 5\n\nconst SKIP_DIRS = new Set([\n 'node_modules',\n '.git',\n '.hg',\n '.svn',\n '__pycache__',\n '.cache',\n '.npm',\n '.yarn',\n 'dist',\n 'build',\n '.next',\n '.nuxt',\n 'coverage',\n '.venv',\n 'venv',\n '.tox',\n 'vendor',\n 'target',\n '.gradle',\n '.idea',\n '.vscode',\n])\n\ninterface DirEntry {\n rel: string\n path: string\n}\n\nfunction walkDirs(base: string): DirEntry[] {\n const results: DirEntry[] = []\n const queue: { dir: string, depth: number }[] = [{ dir: base, depth: 0 }]\n\n while (queue.length > 0 && results.length < MAX_ENTRIES) {\n const { dir, depth } = queue.shift()!\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n }\n catch {\n continue\n }\n for (const e of entries) {\n if (results.length >= MAX_ENTRIES)\n break\n if (e.name.startsWith('.') || SKIP_DIRS.has(e.name))\n continue\n try {\n const full = join(dir, e.name)\n if (e.isDirectory() || (e.isSymbolicLink() && statSync(full).isDirectory())) {\n results.push({ rel: relative(base, full), path: full })\n if (depth + 1 < MAX_DEPTH)\n queue.push({ dir: full, depth: depth + 1 })\n }\n }\n catch {}\n }\n }\n return results\n}\n\nfunction compactHome(p: string): string {\n return p === HOME ? '~' : p.startsWith(`${HOME}/`) ? `~${p.slice(HOME.length)}` : p\n}\n\nfunction highlightName(name: string, positions: Set<number>, hlColor: string, dimColor: string) {\n const spans: { text: string, color: string }[] = []\n let run = ''\n let runHL = false\n for (let i = 0; i < name.length; i++) {\n const hl = positions.has(i)\n if (hl !== runHL && run) {\n spans.push({ text: run, color: runHL ? hlColor : dimColor })\n run = ''\n }\n run += name[i]\n runHL = hl\n }\n if (run)\n spans.push({ text: run, color: runHL ? hlColor : dimColor })\n return spans\n}\n\nexport function CwdPickerModal({\n currentCwd,\n onPick,\n}: {\n currentCwd: string\n onPick: (dir: string) => void\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const inputRef = useRef<InputRenderable | null>(null)\n const [query, setQuery] = useState('')\n const [browsePath, setBrowsePath] = useState(HOME)\n const [selectedIdx, setSelectedIdx] = useState(0)\n\n useEffect(() => { inputRef.current?.focus() }, [])\n\n const dirs = useMemo(() => walkDirs(browsePath), [browsePath])\n\n const fzf = useMemo(\n () => new Fzf(dirs, { selector: d => d.rel, tiebreakers: [byLengthAsc], limit: 200 }),\n [dirs],\n )\n\n const results: FzfResultItem<DirEntry>[] = useMemo(() => {\n if (!query)\n return dirs.slice(0, 200).map(item => ({ item, start: 0, end: 0, score: 0, positions: new Set<number>() }))\n return fzf.find(query)\n }, [fzf, dirs, query])\n\n const safeIndex = results.length === 0 ? 0 : Math.min(selectedIdx, results.length - 1)\n\n const handleQueryChange = useCallback((next: string) => {\n setQuery(next)\n setSelectedIdx(0)\n }, [])\n\n const commit = () => {\n const row = results[safeIndex]\n if (row)\n onPick(row.item.path)\n }\n\n const drillDown = () => {\n const row = results[safeIndex]\n if (row) {\n setBrowsePath(row.item.path)\n setQuery('')\n setSelectedIdx(0)\n }\n }\n\n const goUp = () => {\n const parent = join(browsePath, '..')\n if (parent !== browsePath) {\n setBrowsePath(parent)\n setQuery('')\n setSelectedIdx(0)\n }\n }\n\n const viewport = useMemo(() => {\n if (results.length <= VISIBLE_ROWS)\n return { start: 0, slice: results }\n const half = Math.floor(VISIBLE_ROWS / 2)\n let start = Math.max(0, safeIndex - half)\n if (start + VISIBLE_ROWS > results.length)\n start = results.length - VISIBLE_ROWS\n return { start, slice: results.slice(start, start + VISIBLE_ROWS) }\n }, [results, safeIndex])\n\n useKeyboard((key) => {\n if (key.name === 'up') {\n setSelectedIdx(i => results.length === 0 ? i : ((i - 1) % results.length + results.length) % results.length)\n return\n }\n if (key.name === 'down') {\n setSelectedIdx(i => results.length === 0 ? i : (i + 1) % results.length)\n return\n }\n if (key.name === 'tab') {\n drillDown()\n return\n }\n if (key.name === 'left' && !query) {\n goUp()\n return\n }\n if (key.name === 'right' && !query) {\n drillDown()\n return\n }\n if (key.name === 'return') {\n commit()\n }\n })\n\n return (\n <Modal title=\"change directory\" maxWidth={80}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>{'browsing '}</span>\n <span fg={COLOR.brand}>{compactHome(browsePath)}</span>\n <span fg={COLOR.mute}>{` · ${dirs.length} dirs`}</span>\n </text>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n focused\n placeholder=\"fuzzy search directories…\"\n onInput={handleQueryChange}\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n\n <box style={{ flexDirection: 'column', height: VISIBLE_ROWS, flexShrink: 0 }}>\n {results.length === 0\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>{'no directories match '}</span>\n <span fg={COLOR.warn}>{query.trim()}</span>\n </text>\n )\n : (\n viewport.slice.map((result, i) => {\n const focused = viewport.start + i === safeIndex\n const current = result.item.path === currentCwd\n const marker = current ? '●' : ' '\n const nameColor = focused ? COLOR.brand : COLOR.dim\n const spans = query\n ? highlightName(result.item.rel, result.positions, COLOR.warn, nameColor)\n : [{ text: result.item.rel, color: nameColor }]\n return (\n <box\n key={result.item.path}\n style={{\n height: 1,\n paddingLeft: 1,\n paddingRight: 1,\n flexShrink: 0,\n backgroundColor: focused ? SURFACE.selection : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={current ? COLOR.brand : COLOR.mute}>{marker}</span>\n <span fg={COLOR.mute}>{' '}</span>\n {spans.map((s, si) => <span key={si} fg={s.color}>{s.text}</span>)}\n <span fg={COLOR.mute}>/</span>\n </text>\n </box>\n )\n })\n )}\n </box>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>tab</span>\n {' enter dir · '}\n <span fg={COLOR.warn}>←</span>\n {' parent · '}\n <span fg={COLOR.warn}>↵</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close · '}\n <span fg={COLOR.mute}>{`${results.length} match${results.length === 1 ? '' : 'es'}`}</span>\n </text>\n </Modal>\n )\n}\n","/** @jsxImportSource @opentui/react */\n\n/**\n * Workspace-discovery state container.\n *\n * Sits ABOVE `<ModalRoot>` so that the modal's active node (rendered\n * as a sibling of ModalRoot's children, see `modal.tsx`) sees fresh\n * catalogs through context propagation. Putting this state inside\n * `<AppShell>` would expose it to AppShell's tree but NOT to the\n * modal layer — modals would freeze their catalog snapshot at open\n * time and a `setSkillsCatalog` would never reach them.\n *\n * Owns the per-project discovery slots (stale-while-revalidate\n * coordinators) for files + skills, the eager MCP discovery, and the\n * action thunks (`ensureFiles`, `refreshSkills`, …). Catalogs are\n * pushed through `<DiscoveryProvider>`; AppShell + every modal read\n * via `useDiscovery()`.\n */\n\nimport type { ReactNode } from 'react'\nimport type { FileEntry } from '../chat/files-discovery'\nimport type { DiscoveredMcp, DiscoveryError } from '../chat/mcps-discovery'\nimport type { SkillConfig } from '../skills'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { bootTick } from '../chat/boot-profiler'\nimport { useConfig } from '../chat/config-context'\nimport { DiscoveryProvider } from '../chat/discovery-context'\nimport { createDiscoverySlot } from '../chat/discovery-slot'\nimport { listProjectFiles } from '../chat/files-discovery'\nimport { useMcpAuthDispatch } from '../chat/mcp-auth-context'\nimport { createFileMcpCredentialStore } from '../chat/mcp-credentials'\nimport { discoverProjectMcps } from '../chat/mcps-discovery'\nimport { findGitRoot } from '../chat/project-root'\nimport { discoverProjectSkills } from '../chat/skills-discovery'\nimport { errorMessage } from '../errors'\n\n/**\n * SWR throttles. `files` is short so a long-open `@` popover picks up\n * new files within seconds; `skills` is long because SKILL.md changes\n * are rare and the walk is cheap enough that we already eager-load it.\n */\nconst FILES_REFRESH_THROTTLE_MS = 3_000\nconst SKILLS_REFRESH_THROTTLE_MS = 30_000\n\nfunction debugLog(...args: unknown[]): void {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/tui] ${args.map(errorMessage).join(' ')}\\n`)\n}\n\nexport function DiscoveryShell({ children }: { children: ReactNode }) {\n const config = useConfig()\n const dispatchAuth = useMcpAuthDispatch()\n const dispatchAuthRef = useRef(dispatchAuth)\n dispatchAuthRef.current = dispatchAuth\n\n // `projectDir` is the git root the session anchors to. Set once at\n // mount via `useState` initializer so concurrent renders see a\n // stable value — switching project means rebooting the process.\n const [projectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd())\n const dataDir = config.paths.userDir\n const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir])\n\n const [skillsCatalog, setSkillsCatalog] = useState<readonly SkillConfig[]>([])\n const [mcpsCatalog, setMcpsCatalog] = useState<readonly DiscoveredMcp[]>([])\n const [mcpsErrors, setMcpsErrors] = useState<readonly DiscoveryError[]>([])\n const [filesCatalog, setFilesCatalog] = useState<readonly FileEntry[]>([])\n\n const filesSlotRef = useRef<ReturnType<typeof createDiscoverySlot<FileEntry>> | null>(null)\n const skillsSlotRef = useRef<ReturnType<typeof createDiscoverySlot<SkillConfig>> | null>(null)\n\n useEffect(() => {\n // Slot rotation: abort any in-flight walk from the previous\n // `projectDir`, build fresh slots, and rewind catalogs to empty.\n // The empty rewind is deliberate — a stale catalog from another\n // project would surface in the new project's UI before the\n // ensure() walk landed.\n filesSlotRef.current?.abort()\n skillsSlotRef.current?.abort()\n setFilesCatalog([])\n setSkillsCatalog([])\n\n const filesSlot = createDiscoverySlot<FileEntry>({\n throttleMs: FILES_REFRESH_THROTTLE_MS,\n walk: signal => listProjectFiles({ cwd: projectDir, signal }),\n onLoad: (items) => {\n if (filesSlotRef.current === filesSlot)\n setFilesCatalog(items)\n },\n onError: (err, phase) => {\n debugLog(`listProjectFiles ${phase} failed`, err)\n },\n })\n const skillsSlot = createDiscoverySlot<SkillConfig>({\n throttleMs: SKILLS_REFRESH_THROTTLE_MS,\n walk: () => discoverProjectSkills({ cwd: projectDir, prefix: config.prefix }),\n onLoad: (items) => {\n if (skillsSlotRef.current === skillsSlot)\n setSkillsCatalog(items)\n },\n onError: (err, phase) => {\n debugLog(`discoverProjectSkills ${phase} failed`, err)\n },\n })\n filesSlotRef.current = filesSlot\n skillsSlotRef.current = skillsSlot\n\n // Eager skills load — the settings modal expects a populated\n // catalog whenever it opens, independent of whether the user has\n // hit `/` in the prompt yet. The walk is cheap (~10–50ms typical)\n // so it's fine on the discovery-effect path.\n void skillsSlot.ensure().then(skills => bootTick(`discovery:skills (${skills.length})`))\n\n try {\n const { servers, errors } = discoverProjectMcps({ cwd: projectDir, prefix: config.prefix })\n setMcpsCatalog(servers)\n setMcpsErrors(errors)\n bootTick(`discovery:mcps (${servers.length} servers, ${errors.length} parse errors)`)\n // Seed auth state from the credential store BEFORE the lazy MCP\n // bootstrap runs (the bootstrap doesn't fire until the first\n // `agent.run()` / `warmup()`, so a user who opens the MCP picker\n // before sending any message would otherwise see no status badge).\n for (const entry of servers) {\n if (entry.config.auth !== 'oauth')\n continue\n const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens\n dispatchAuthRef.current(\n hasTokens\n ? { type: 'auth-success', name: entry.config.name }\n : { type: 'auth-required', name: entry.config.name, reason: 'no-tokens' },\n )\n }\n }\n catch (err) {\n debugLog('discoverProjectMcps failed', err)\n }\n\n return () => {\n filesSlot.abort()\n skillsSlot.abort()\n }\n }, [projectDir, config.prefix, mcpCredentialStore])\n\n const ensureFiles = useCallback(\n (): Promise<readonly FileEntry[]> =>\n filesSlotRef.current?.ensure() ?? Promise.resolve([] as readonly FileEntry[]),\n [],\n )\n const ensureSkills = useCallback(\n (): Promise<readonly SkillConfig[]> =>\n skillsSlotRef.current?.ensure() ?? Promise.resolve([] as readonly SkillConfig[]),\n [],\n )\n const refreshFiles = useCallback(\n (): Promise<void> => filesSlotRef.current?.refresh() ?? Promise.resolve(),\n [],\n )\n const refreshSkills = useCallback(\n (): Promise<void> => skillsSlotRef.current?.refresh() ?? Promise.resolve(),\n [],\n )\n const refreshMcps = useCallback((): Promise<void> => {\n try {\n const { servers, errors } = discoverProjectMcps({ cwd: projectDir, prefix: config.prefix })\n setMcpsCatalog(servers)\n setMcpsErrors(errors)\n for (const entry of servers) {\n if (entry.config.auth !== 'oauth')\n continue\n const hasTokens = !!mcpCredentialStore.load(entry.config.name)?.tokens\n dispatchAuthRef.current(\n hasTokens\n ? { type: 'auth-success', name: entry.config.name }\n : { type: 'auth-required', name: entry.config.name, reason: 'no-tokens' },\n )\n }\n }\n catch (err) {\n debugLog('refreshMcps failed', err)\n }\n return Promise.resolve()\n }, [projectDir, config.prefix, mcpCredentialStore])\n\n // Stable value identity is NOT a goal here — the whole point of\n // this context is to re-render consumers on every catalog change.\n // The wrapper above ModalRoot makes that propagation reach the\n // active modal's subtree regardless of element identity.\n const value = useMemo(() => ({\n skillsCatalog,\n mcpsCatalog,\n mcpsErrors,\n filesCatalog,\n refreshSkills,\n refreshMcps,\n refreshFiles,\n ensureSkills,\n ensureFiles,\n }), [\n skillsCatalog,\n mcpsCatalog,\n mcpsErrors,\n filesCatalog,\n refreshSkills,\n refreshMcps,\n refreshFiles,\n ensureSkills,\n ensureFiles,\n ])\n\n return <DiscoveryProvider value={value}>{children}</DiscoveryProvider>\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable } from '@opentui/core'\nimport type { ThinkingLevel } from '../types'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/**\n * Modal that lets the user pick a reasoning effort for the active\n * model. Mirrors `ModelPickerModal`'s shape — search input on top,\n * filterable row list below, hint row at the bottom — so the two\n * pickers feel like siblings in the same family. Only surfaced for\n * models whose registry entry reports `reasoning: true` (see\n * `modelSupportsReasoning`).\n *\n * Unlike the model picker, the list isn't windowed: there are at most\n * 6 levels, so we just render them all. The search input still earns\n * its keep — typing the first letter of a level (`h` → `high`, `m` →\n * `minimal` + `medium`) commits in a single keystroke after `↵`.\n *\n * `'adaptive'` is Anthropic-only; pass `supportsAdaptive` to surface\n * it. Other providers silently treat it as `'off'`, so we hide it\n * rather than let the user pick a level that does nothing on their\n * active model.\n */\n\ninterface EffortLevel {\n id: ThinkingLevel\n description: string\n}\n\nconst BASE_LEVELS: readonly EffortLevel[] = [\n { id: 'off', description: 'no reasoning — fastest, smallest output' },\n { id: 'minimal', description: 'tiny reasoning budget (gpt-5 family)' },\n { id: 'low', description: 'short reasoning pass' },\n { id: 'medium', description: 'balanced — sensible default' },\n { id: 'high', description: 'deep reasoning — slowest, longest' },\n]\n\nconst ADAPTIVE_LEVEL: EffortLevel = {\n id: 'adaptive',\n description: 'model decides per-turn (Anthropic)',\n}\n\nexport function EffortPickerModal({\n current,\n supportsAdaptive,\n onPick,\n}: {\n current: ThinkingLevel | undefined\n supportsAdaptive: boolean\n onPick: (effort: ThinkingLevel) => void\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const inputRef = useRef<InputRenderable | null>(null)\n const [query, setQuery] = useState('')\n\n // Build the level catalog + its lowercased search corpus together so\n // filtering on every keystroke is O(levels × queryLength) without\n // re-normalizing per call.\n const levels = useMemo(() => {\n const base = supportsAdaptive ? [...BASE_LEVELS, ADAPTIVE_LEVEL] : BASE_LEVELS\n return base.map(l => ({\n ...l,\n searchCorpus: `${l.id} ${l.description}`.toLowerCase(),\n }))\n }, [supportsAdaptive])\n\n const filtered = useMemo(() => {\n const trimmed = query.trim().toLowerCase()\n if (!trimmed)\n return levels\n const terms = trimmed.split(/\\s+/)\n return levels.filter(l => terms.every(t => l.searchCorpus.includes(t)))\n }, [levels, query])\n\n // Seed selection at the active level (or `medium` if nothing's\n // remembered — same fallback the old `<select>`-based picker used).\n // Anchored to the un-filtered list because the picker opens with an\n // empty query; once the user types, `handleQueryChange` resets to 0.\n const [selectedIdx, setSelectedIdx] = useState(() => {\n const idx = levels.findIndex(l => l.id === current)\n if (idx >= 0)\n return idx\n const fallback = levels.findIndex(l => l.id === 'medium')\n return fallback < 0 ? 0 : fallback\n })\n const handleQueryChange = useCallback((next: string) => {\n setQuery(next)\n setSelectedIdx(0)\n }, [])\n\n const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1)\n\n const commit = () => {\n const row = filtered[safeIndex]\n if (row)\n onPick(row.id)\n }\n\n // Same imperative-focus dance as the model picker — see the long\n // comment there for why `focused={true}` alone isn't enough when\n // the picker mounts on top of an already-focused chat textarea.\n useEffect(() => {\n inputRef.current?.focus()\n }, [])\n\n useKeyboard((key) => {\n if (key.name === 'up') {\n // Wrap at the edges so ↑ on row 0 lands on the last entry. Same\n // pattern the model picker + settings modal use.\n setSelectedIdx((i) => {\n if (filtered.length === 0)\n return i\n return ((i - 1) % filtered.length + filtered.length) % filtered.length\n })\n return\n }\n if (key.name === 'down') {\n setSelectedIdx((i) => {\n if (filtered.length === 0)\n return i\n return (i + 1) % filtered.length\n })\n return\n }\n if (key.name === 'return') {\n commit()\n }\n })\n\n return (\n <Modal title=\"select reasoning effort\" maxWidth={80}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n // Hard-coded `true` — see `ModelPickerModal` for the rationale\n // (the picker IS a modal, so `useModalAwareFocus()` would\n // report `false` here and fight our imperative focus).\n focused\n placeholder=\"search effort levels…\"\n onInput={handleQueryChange}\n // Suppress the input's own submit handler — ↵ commits the\n // selected row via our top-level keyboard handler.\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {filtered.length === 0\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>no levels match </span>\n <span fg={COLOR.warn}>{query.trim()}</span>\n </text>\n )\n : (\n filtered.map((level, i) => (\n <EffortRow\n key={level.id}\n level={level}\n isCurrent={level.id === current}\n isFocused={i === safeIndex}\n highlightBg={SURFACE.selection}\n />\n ))\n )}\n </box>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close · '}\n <span fg={COLOR.mute}>{`${filtered.length} / ${levels.length} level${levels.length === 1 ? '' : 's'}`}</span>\n </text>\n </Modal>\n )\n}\n\n/**\n * Single row in the picker. Mirrors `ModelRow` in `model-picker.tsx`:\n * `●` marker for the current pick, single-space middle-dot separators,\n * focused row gets the `surfaces.selection` background lift.\n */\nfunction EffortRow({\n level,\n isCurrent,\n isFocused,\n highlightBg,\n}: {\n level: EffortLevel\n isCurrent: boolean\n isFocused: boolean\n highlightBg: string\n}) {\n const COLOR = useColors()\n const marker = isCurrent ? '●' : ' '\n return (\n <box\n style={{\n height: 1,\n paddingLeft: 1,\n paddingRight: 1,\n flexShrink: 0,\n backgroundColor: isFocused ? highlightBg : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isCurrent ? COLOR.brand : COLOR.mute}>{marker}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isFocused ? COLOR.brand : COLOR.dim}>{level.id}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.mute}>{level.description}</span>\n </text>\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ScrollBoxRenderable } from '@opentui/core'\nimport type { KeyBindingDef, KeyBindings, KeyBindingSection } from '../chat/keybindings'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useMemo, useRef } from 'react'\nimport { compactPath } from '../chat/format'\nimport {\n formatBindingForDisplay,\n groupBindings,\n KEYBINDING_DEFS,\n KEYBINDING_KEY_COL_WIDTH,\n} from '../chat/keybindings'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n// ---------------------------------------------------------------------------\n// KeybindingsModal — read-only catalog of every action shortcut, grouped\n// by surface (Global, Message queue, Turn details, Session details).\n//\n// Layout (illustrative — actual widths reflow against the terminal):\n// ┌─ keybindings ────────────────────────── 23 actions ─┐\n// │ │\n// │ Global │\n// │ ctrl+o settings │\n// │ open the Settings modal (toggles…) │\n// │ ctrl+x session │\n// │ open the session details modal… │\n// │ … │\n// │ │\n// │ Message queue │\n// │ … │\n// │ │\n// │ ▶ Edit keybindings file › │\n// │ ~/.zidane/keybindings.json │\n// │ │\n// └────────────── ↵ edit file · esc close ──────────────┘\n//\n// Purely informational — no per-row selection. ↵ opens the JSON file in\n// `$EDITOR`; ↑/↓ scroll the list (via the OpenTUI scrollbox's own focus,\n// when the catalog grows past the modal's max-height); esc closes.\n//\n// `KEYBINDING_DEFS` is the source of truth for both content and ordering\n// — render order tracks the catalog order, with a section header\n// inserted whenever the `group` field changes between adjacent entries.\n// Adding a new action in `keybindings.ts` automatically lands here with\n// no edits required.\n// ---------------------------------------------------------------------------\n\ninterface Props {\n /** Effective bindings — defaults merged with user overrides. */\n bindings: KeyBindings\n /**\n * Absolute path to the user-level `keybindings.json`. Threaded\n * through so the \"edit file\" button's caption shows the real path\n * (with `$HOME` collapsed to `~`) — falls back to the install\n * default when not provided. The actual open path is opaque to\n * the modal; that's owned by `onEditFile`.\n */\n filePath?: string\n /** Open `<userDir>/keybindings.json` in `$EDITOR`. Closes the modal first. */\n onEditFile: () => void\n /** Dismiss the modal without opening the editor. */\n onClose: () => void\n}\n\nexport function KeybindingsModal({ bindings, filePath, onEditFile, onClose }: Props) {\n const SURFACE = useSurfaces()\n const { height: termHeight } = useTerminalDimensions()\n const scrollRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Pre-compute the grouped rows ONCE per (bindings, defs) change. The\n // catalog is small (<30 rows); the memo is mostly so the section\n // header insertion stays a pure derivation of `KEYBINDING_DEFS`.\n const sections = useMemo(() => groupBindings(bindings), [bindings])\n\n useKeyboard((key) => {\n if (key.name === 'return') {\n onEditFile()\n }\n })\n\n // Cap the modal at ~two-thirds of the terminal — same floor/ceiling\n // logic the todos modal uses. Leaves the chat behind visible while\n // giving the list room to scroll on tall terminals.\n const idealHeight = Math.floor((termHeight - 4) * 0.7)\n const maxHeight = Math.max(18, Math.min(40, idealHeight))\n\n const totalCount = KEYBINDING_DEFS.length\n\n return (\n <Modal\n title=\"keybindings\"\n bottomTitle=\"↵ edit file · esc close\"\n rightTitle={<CountsBadge count={totalCount} />}\n maxWidth={104}\n minWidth={64}\n maxHeight={maxHeight}\n onClose={onClose}\n >\n <box\n style={{\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n }}\n >\n <scrollbox\n ref={scrollRef}\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n <KeybindingsCatalog sections={sections} />\n </scrollbox>\n </box>\n\n <KeybindingsEditFileButton\n filePath={filePath}\n highlightBg={SURFACE.selection}\n />\n </Modal>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Shared catalog primitives — re-used by `SettingsModal`'s Keybindings\n// tab so both entry points (standalone `ctrl+y` modal and the in-\n// settings tab) render the same content with the same geometry. The\n// caller pre-computes + filters `sections` via `groupBindings` so the\n// catalog itself stays unaware of search / scope semantics.\n// ---------------------------------------------------------------------------\n\nexport function KeybindingsCatalog({ sections }: { sections: readonly KeyBindingSection[] }) {\n return (\n <box style={{ flexDirection: 'column' }}>\n {sections.map((section, sectionIdx) => (\n <box\n key={section.group}\n style={{\n flexDirection: 'column',\n flexShrink: 0,\n marginTop: sectionIdx === 0 ? 0 : 1,\n }}\n >\n <SectionHeader label={section.group} />\n {section.rows.map(row => (\n <BindingRow key={row.def.action} def={row.def} spec={row.spec} />\n ))}\n </box>\n ))}\n </box>\n )\n}\n\n/**\n * Highlighted \"Edit keybindings file\" button. Used as the standalone\n * modal's pinned footer AND as the Settings-modal Keybindings tab's\n * pinned action row. Activation is the caller's responsibility (the\n * standalone modal uses its own `useKeyboard`; the Settings modal\n * routes `↵` through its top-level handler).\n */\nexport function KeybindingsEditFileButton({\n filePath,\n highlightBg,\n}: {\n filePath: string | undefined\n highlightBg: string\n}) {\n const COLOR = useColors()\n // `compactPath` collapses `$HOME` to `~` so the displayed string is\n // short and recognizable. Fall back to the install-default location\n // when the host didn't thread a path — keeps the legend useful even\n // for embedders that don't wire the prop.\n const display = filePath ? compactPath(filePath) : '~/.zidane/keybindings.json'\n return (\n <box\n style={{\n flexShrink: 0,\n flexDirection: 'column',\n paddingLeft: 1,\n paddingRight: 1,\n backgroundColor: highlightBg,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={COLOR.brand}>▶ </span>\n <span fg={COLOR.brand}>Edit keybindings file</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.brand}>›</span>\n </text>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.dim}>{`${display} — restart to apply changes`}</span>\n </text>\n </box>\n )\n}\n\nfunction SectionHeader({ label }: { label: string }) {\n const COLOR = useColors()\n return (\n <box style={{ flexShrink: 0, paddingLeft: 1, paddingRight: 1 }}>\n <text wrapMode=\"none\">\n <span fg={COLOR.brand}>{label}</span>\n </text>\n </box>\n )\n}\n\nfunction BindingRow({ def, spec }: { def: KeyBindingDef, spec: string }) {\n const COLOR = useColors()\n const display = formatBindingForDisplay(spec)\n // Unbound actions render `—` in the mute palette so the gap stays\n // legible. Header line uses `wrapMode=\"none\"` — key + label never\n // wrap; the column padding guarantees label-column alignment via\n // `padEnd` (every glyph we substitute is single-cell, so character\n // count == column count).\n //\n // The description sits in its OWN sibling box with\n // `paddingLeft: KEYBINDING_KEY_COL_WIDTH` instead of a space-prefixed\n // string. That makes OpenTUI compute wrap width against the\n // narrower (indented) column, so the wrap continuation lands under\n // the description's left edge instead of leaking back to the row's\n // left edge.\n const keyText = (display || '—').padEnd(KEYBINDING_KEY_COL_WIDTH, ' ')\n const keyColor = display ? COLOR.warn : COLOR.mute\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 3, paddingRight: 1 }}>\n <text wrapMode=\"none\">\n <span fg={keyColor}>{keyText}</span>\n <span fg={COLOR.dim}>{def.label}</span>\n </text>\n <box style={{ flexShrink: 0, paddingLeft: KEYBINDING_KEY_COL_WIDTH }}>\n <text wrapMode=\"word\" fg={COLOR.mute}>{def.description}</text>\n </box>\n </box>\n )\n}\n\nfunction CountsBadge({ count }: { count: number }) {\n const COLOR = useColors()\n return (\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.accent}>{String(count)}</span>\n <span fg={COLOR.mute}>{` action${count === 1 ? '' : 's'} `}</span>\n </text>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable } from '@opentui/core'\nimport type { ProviderAuth, ProviderKey } from '../chat/auth'\nimport type { CatalogEntry } from '../chat/model-catalog'\nimport type { ModelInfo } from '../chat/providers'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { fmtTokens } from '../chat/format'\nimport { buildModelCatalog, filterModelCatalog, indexOfEntry } from '../chat/model-catalog'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/**\n * Cross-provider, searchable model picker.\n *\n * The picker unions every available provider's models into one flat\n * catalog, lets the user filter with a typed query, and returns both\n * the chosen provider and model id on commit. Pick a model from a\n * different provider than the current one and the caller is expected\n * to swap the active `ProviderAuth` + rebuild the agent — see\n * `app.tsx`'s `onPickModel` for the canonical wiring.\n *\n * Geometry:\n * - Top: single-row search input. The input owns focus so the user\n * can type immediately; ↑/↓/↵/⎋ are intercepted before the input's\n * text handler sees them so navigation works without losing focus.\n * - Middle: windowed list of catalog rows around the current\n * selection. Each row shows the model name (or id) in brand color,\n * the provider label in dim, and a compact capability suffix\n * (`ctx 200k · reasoning · vision`).\n * - Bottom: shortcut hint row.\n *\n * Empty states:\n * - No available providers → \"no providers configured\" notice.\n * - Query has no matches → \"no matches\" row, keep the search input\n * live so the user can backspace out.\n */\n\n/** Visible rows in the windowed list. Keeps the modal a stable size on long catalogs. */\nconst VISIBLE_ROWS = 10\n\nexport interface PickedModel {\n providerKey: ProviderKey\n modelId: string\n}\n\nexport function ModelPickerModal({\n providers,\n modelsFor,\n current,\n onPick,\n}: {\n /** Authed providers — the catalog is unioned across these. */\n providers: readonly ProviderAuth[]\n /** Resolver from `ResolvedConfig.modelsFor`. */\n modelsFor: (key: ProviderKey) => readonly ModelInfo[]\n /** Currently active selection. Promoted to top of its provider section + pre-highlighted. */\n current: PickedModel\n /** Called on commit with the chosen `{ providerKey, modelId }`. */\n onPick: (picked: PickedModel) => void\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const inputRef = useRef<InputRenderable | null>(null)\n const [query, setQuery] = useState('')\n\n // Imperatively grab focus on mount. Two things conspire to make the\n // search input lose focus without this:\n //\n // 1. When the picker mounts on top of a chat screen that owned\n // focus (the prompt textarea), OpenTUI's prop diff doesn't\n // always steal focus from the previously-focused renderable\n // just because we declared `focused={true}` on a freshly-\n // mounted input.\n // 2. The picker is rendered INSIDE the modal layer, so\n // `useModalAwareFocus()` (which background inputs use to blur\n // when a modal opens) would report `false` here and short-circuit\n // the focus transfer — we'd be using a hook designed to shed\n // focus right when we want to claim it.\n //\n // We sidestep both by declaring `focused={true}` unconditionally on\n // the input and forcing `.focus()` once on mount, then letting the\n // keyboard handler below own the up/down/return overrides.\n useEffect(() => {\n inputRef.current?.focus()\n }, [])\n\n // Pre-compute the catalog once per `providers`. `modelsFor` is stable\n // for a `ResolvedConfig` lifetime, so we don't depend on it in the\n // memo key — depending on the function identity would invalidate\n // every render where the App re-creates a wrapper closure.\n const catalog = useMemo(\n () => buildModelCatalog({ providers, modelsFor, current }),\n [providers, modelsFor, current],\n )\n\n const filtered = useMemo(() => filterModelCatalog(catalog, query), [catalog, query])\n\n // Selection index INTO `filtered`. Seeded at the current pick so\n // the picker opens with the active model highlighted (and scrolled\n // into view via the window calc below). Anchoring at 0 in a\n // `useEffect([query])` would have fired on MOUNT too — clobbering\n // the seed before the user saw it — so the reset lives in\n // `handleQueryChange` instead, only firing on actual edits.\n const [selectedIdx, setSelectedIdx] = useState(() =>\n Math.max(0, indexOfEntry(catalog, current)),\n )\n const handleQueryChange = useCallback((next: string) => {\n setQuery(next)\n // The previously-selected row may have been filtered out, so anchor\n // at the first match — the natural read position after a search.\n setSelectedIdx(0)\n }, [])\n\n const safeIndex = filtered.length === 0 ? 0 : Math.min(selectedIdx, filtered.length - 1)\n\n const commit = () => {\n const row = filtered[safeIndex]\n if (row)\n onPick({ providerKey: row.providerKey, modelId: row.model.id })\n }\n\n // Viewport of rows actually rendered. Centers the selection inside\n // the cap when possible; clamps at edges so scrolling stays smooth\n // at the tail. Named `viewport` rather than `window` to avoid\n // shadowing the global.\n const viewport = useMemo(() => {\n if (filtered.length <= VISIBLE_ROWS)\n return { start: 0, slice: filtered }\n const half = Math.floor(VISIBLE_ROWS / 2)\n let start = Math.max(0, safeIndex - half)\n if (start + VISIBLE_ROWS > filtered.length)\n start = filtered.length - VISIBLE_ROWS\n return { start, slice: filtered.slice(start, start + VISIBLE_ROWS) }\n }, [filtered, safeIndex])\n\n // Keyboard handler — wins over the input's default text handling\n // for the navigation keys + commit. We register at the modal level\n // (not the input's onKeyDown) because the modal is the topmost\n // surface and `useKeyboard` gives us a single place to coordinate\n // arrow-driven list navigation alongside the input's free-text\n // editing. No focus guard: this component only lives while its\n // modal is open, and the single-slot Modal layer guarantees no\n // sibling modal can steal events from us. The input still receives\n // every non-overridden keystroke through its own text path\n // (character input, backspace, cursor moves, etc.) — single-line\n // inputs treat `up`/`down` as no-ops, so there's no conflict.\n useKeyboard((key) => {\n if (key.name === 'up') {\n // Wrap at the edges: ↑ on the first row jumps to the last entry\n // (and ↓ at the bottom comes back to the first) — keeps long\n // catalogs navigable without forcing the user to scroll back up.\n setSelectedIdx((i) => {\n if (filtered.length === 0)\n return i\n return ((i - 1) % filtered.length + filtered.length) % filtered.length\n })\n return\n }\n if (key.name === 'down') {\n setSelectedIdx((i) => {\n if (filtered.length === 0)\n return i\n return (i + 1) % filtered.length\n })\n return\n }\n if (key.name === 'return') {\n commit()\n }\n })\n\n if (providers.length === 0)\n return <EmptyProvidersState />\n\n return (\n <Modal title=\"select model\" maxWidth={100}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n // Hard-coded `true` instead of `useModalAwareFocus()` (which\n // would return `false` here because the picker IS a modal —\n // the hook is designed to blur background inputs when a\n // modal opens, not to focus inputs inside the modal).\n focused\n placeholder=\"search models — provider, name, capability…\"\n onInput={handleQueryChange}\n // Suppress the input's own submit handler — ↵ commits the\n // selected row via our top-level keyboard handler. Without\n // an `onSubmit` of `() => {}`, the OpenTUI input runtime\n // would forward the keypress, then we'd commit twice.\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n\n <box style={{ flexDirection: 'column', height: VISIBLE_ROWS, flexShrink: 0 }}>\n {filtered.length === 0\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>no models match </span>\n <span fg={COLOR.warn}>{query.trim()}</span>\n </text>\n )\n : (\n viewport.slice.map((entry, i) => (\n <ModelRow\n key={`${entry.providerKey}:${entry.model.id}`}\n entry={entry}\n isCurrent={\n entry.providerKey === current.providerKey\n && entry.model.id === current.modelId\n }\n isFocused={viewport.start + i === safeIndex}\n // Surface color so the focused row pops without\n // pulling in a per-row prop chain just for theming.\n highlightBg={SURFACE.selection}\n />\n ))\n )}\n </box>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close · '}\n <span fg={COLOR.mute}>{`${filtered.length} / ${catalog.length} model${catalog.length === 1 ? '' : 's'}`}</span>\n </text>\n </Modal>\n )\n}\n\n/**\n * Single row in the picker. Renders the model name in `brand`, the\n * provider tag in `dim`, and the capability suffix in `mute`. The\n * focused row gets a subtle selection background (same surface as\n * select-turn mode in the transcript) so it pops without a separate\n * marker glyph competing with the `●` \"current\" indicator.\n */\nfunction ModelRow({\n entry,\n isCurrent,\n isFocused,\n highlightBg,\n}: {\n entry: CatalogEntry\n isCurrent: boolean\n isFocused: boolean\n highlightBg: string\n}) {\n const COLOR = useColors()\n const marker = isCurrent ? '●' : ' '\n return (\n <box\n style={{\n height: 1,\n paddingLeft: 1,\n paddingRight: 1,\n flexShrink: 0,\n backgroundColor: isFocused ? highlightBg : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isCurrent ? COLOR.brand : COLOR.mute}>{marker}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isFocused ? COLOR.brand : COLOR.dim}>{entry.model.name ?? entry.model.id}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.model}>{entry.providerLabel}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.mute}>{describeModel(entry.model)}</span>\n </text>\n </box>\n )\n}\n\nfunction EmptyProvidersState() {\n const COLOR = useColors()\n return (\n <Modal title=\"select model\">\n <text fg={COLOR.dim}>No authed providers — configure one via</text>\n <text fg={COLOR.dim}>\n <span fg={COLOR.model}>{' settings → re-configure providers'}</span>\n {' or '}\n <span fg={COLOR.model}>esc → sessions → settings</span>\n {' first.'}\n </text>\n </Modal>\n )\n}\n\n/** \"ctx 200k · reasoning · vision\" — compact capability blurb. */\nfunction describeModel(m: ModelInfo): string {\n const parts: string[] = [`ctx ${fmtTokens(m.contextWindow)}`]\n if (m.reasoning)\n parts.push('reasoning')\n if (m.input?.includes('image'))\n parts.push('vision')\n return parts.join(' · ')\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ReactNode } from 'react'\nimport type { CompletionState } from '../chat/completion'\nimport { resolveChipColor } from '../chat/theme'\nimport { useColors, useSelectStyle, useSurfaces } from '../chat/theme-context'\n\n/**\n * Popover above the textarea showing the active provider's items. Provider-\n * agnostic — reads `label` + `description` off each `CompletionItem`. The\n * TUI hosts can pass any `CompletionState<TItem>`; the popup never needs\n * to know what `TItem` is.\n *\n * Geometry: 1 row of chrome + min(N, visibleRows) item rows + 1 hint row.\n * `flexShrink: 0` pins the height so a long transcript can't squeeze it.\n *\n * The popup is invisible (`null`-rendered) when `state.active` is null or\n * `state.items` is empty — the prompt block keeps its layout calm.\n *\n * Solid `backgroundColor: SURFACE.modal` is load-bearing: in `PromptBlock`\n * the popup floats over the transcript via `position: absolute`, and a\n * transparent fill would let the transcript text bleed through. Pairs\n * with the modal panel surface so floating UI shares one visual identity.\n *\n * Title overlays are painted on the top border (same trick as\n * `PromptHints`): provider label on the left in the chip-id's accent\n * color, match count on the right in dim text. Both ride absolute\n * positions so they take no flow space; the popup's height comes\n * entirely from the bordered body. The accent reuses\n * `resolveChipColor(...).bg` (foreground only, no background pill) so\n * the picker title still reads as \"this is the X provider\" without\n * carrying the chip pill's heavy visual weight.\n */\nexport function CompletionPopup({\n state,\n /** Cap on visible rows. 6 keeps the popover compact even with long catalogs. */\n visibleRows = 6,\n}: {\n state: CompletionState<unknown>\n visibleRows?: number\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const SELECT = useSelectStyle()\n\n if (!state.active)\n return null\n\n const loading = state.loading && state.items.length === 0\n if (state.items.length === 0 && !loading)\n return null\n\n const chip = resolveChipColor(SURFACE.chips, state.active.provider.id)\n const providerLabel = state.active.provider.label.toLowerCase()\n\n let body: ReactNode\n let height: number\n if (loading) {\n body = <text fg={COLOR.dim}>loading…</text>\n height = 3\n }\n else {\n const rows = Math.min(state.items.length, visibleRows)\n const half = Math.floor(rows / 2)\n let start = Math.max(0, state.selectedIndex - half)\n if (start + rows > state.items.length)\n start = state.items.length - rows\n const slice = state.items.slice(start, start + rows)\n height = 2 + rows + 1\n body = (\n <>\n <box style={{ flexDirection: 'column' }}>\n {slice.map((item, i) => {\n const absoluteIndex = start + i\n const focused = absoluteIndex === state.selectedIndex\n return (\n <box key={item.id} style={{ height: 1, overflow: 'hidden', flexShrink: 0 }}>\n <text\n fg={focused ? COLOR.brand : COLOR.dim}\n wrapMode=\"none\"\n // `truncate` asks OpenTUI's text-buffer-view to\n // collapse the overflow with an ellipsis when the\n // styled text would exceed the row's viewport —\n // labels stay intact, descriptions tail off cleanly\n // with `…` instead of being chopped mid-word.\n truncate\n >\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={focused ? COLOR.brand : SELECT.textColor}>{item.label}</span>\n {item.description && (\n <span fg={COLOR.mute}>\n {' '}\n {item.description}\n </span>\n )}\n </text>\n </box>\n )\n })}\n </box>\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' / '}\n <span fg={COLOR.warn}>tab</span>\n {' select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </>\n )\n }\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n backgroundColor: SURFACE.modal,\n paddingLeft: 1,\n paddingRight: 1,\n height,\n flexShrink: 0,\n alignSelf: 'stretch',\n flexDirection: 'column',\n }}\n >\n {body}\n </box>\n {/*\n Title + meta overlays — both siblings of the bordered box,\n declared after it so the renderer paints them on top of the top\n border. Leading + trailing mute spaces overwrite the border\n characters underneath so each segment reads as a self-contained\n chunk. `position: absolute` means they take no flow space.\n\n The provider label rides the chip-id's accent as foreground only\n (no pill background) so the picker chrome reads light against\n the modal surface — the chip background is reserved for the\n in-prompt reference pills, where the heavier visual weight\n actually signals \"this is a structured token in your buffer\".\n */}\n <text style={{ position: 'absolute', top: 0, left: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={chip.bg}>{providerLabel}</span>\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n {loading\n ? <span fg={COLOR.dim}>loading…</span>\n : <span fg={COLOR.dim}>{`${state.items.length} match${state.items.length === 1 ? '' : 'es'}`}</span>}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\n// ---------------------------------------------------------------------------\n// FileEditApprovalModal — file-edit permission dialog with per-edit toggles.\n//\n// Two layouts, picked off the call's hunk count:\n//\n// - Single-hunk (`edit`, `write_file`, single-step `multi_edit`):\n// ┌─ Permission Request ─────────────────────────┐\n// │ edit · src/foo.ts │\n// │ <split / unified diff> │\n// │ [ Allow ] [ Allow session ] [ Always ] [Deny]│\n// └───────────────────────────────────────────────┘\n//\n// - Multi-hunk `multi_edit`:\n// ┌─ Permission Request ─────────────────────────┐\n// │ multi_edit · src/foo.ts · 2/3 selected │\n// │ ▸ ✓ Edit 1/3 -old / +new (one-line preview)│\n// │ ✗ Edit 2/3 -old / +new (one-line preview)│\n// │ ✓ Edit 3/3 -old / +new (one-line preview)│\n// │ │\n// │ <unified diff of focused edit> │\n// │ │\n// │ [ Allow All ] [ Deny All ] [ Apply Selected ]│\n// └───────────────────────────────────────────────┘\n//\n// Both layouts share the same keyboard contract for the \"bulk\" decisions\n// (`a` / `s` / `p` / `d`), with `space` reserved for the multi-edit list\n// toggle and `↑/↓` to navigate.\n// ---------------------------------------------------------------------------\n\nimport type { ScrollBoxRenderable } from '@opentui/core'\nimport type { ApprovalDecision, ApprovalOriginator, ApprovalRequest } from '../chat/safe-mode-context'\nimport type { EditPayload } from '../chat/types'\nimport * as fs from 'node:fs'\nimport { getTreeSitterClient } from '@opentui/core'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { extractEditPayload, filetypeFromPath, previewEditPayload } from '../chat/edit-diff'\nimport { compactPath } from '../chat/format'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { errorMessage } from '../errors'\nimport { useModal } from './modal'\nimport { useMdStyle } from './theme'\n\n// Tools we treat as file-edit operations and route through this modal.\n// Everything else stays on the inline `ApprovalBlock` flow.\nconst FILE_EDIT_TOOLS = new Set(['edit', 'multi_edit', 'write_file'])\n\nexport function isFileEditTool(tool: string): boolean {\n return FILE_EDIT_TOOLS.has(tool)\n}\n\ninterface FileEditApprovalModalProps {\n request: ApprovalRequest\n onDecide: (decision: ApprovalDecision) => void\n}\n\n// ---------------------------------------------------------------------------\n// Shared shell — reads the prior file content + builds the EditPayload.\n// ---------------------------------------------------------------------------\n\nfunction useEditPayloadFromRequest(request: ApprovalRequest): {\n payload: EditPayload | null\n priorContent: string\n priorError: string | null\n targetPath: string\n} {\n const targetPath = String(request.input.path ?? '')\n\n const [priorContent, priorError] = useMemo<[string, string | null]>(() => {\n try {\n return [fs.readFileSync(targetPath, 'utf8'), null]\n }\n catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT')\n return ['', null]\n return ['', errorMessage(e)]\n }\n }, [targetPath])\n\n const payload = useMemo<EditPayload | null>(() => {\n try {\n return extractEditPayload(request.tool, request.input, priorContent) ?? null\n }\n catch {\n return null\n }\n }, [request.tool, request.input, priorContent])\n\n return { payload, priorContent, priorError, targetPath }\n}\n\nexport function FileEditApprovalModal({ request, onDecide }: FileEditApprovalModalProps) {\n const { payload, priorContent, priorError, targetPath } = useEditPayloadFromRequest(request)\n\n // Multi-edit with >1 hunk routes to the per-edit list view. Everything\n // else (edit, write_file, single-step multi_edit) keeps the legacy\n // single-diff layout — same keystrokes, no behavioral change for the\n // common case.\n const isPerEdit = payload?.tool === 'multi_edit' && payload.hunks.length > 1\n\n if (isPerEdit && payload) {\n return (\n <MultiEditApprovalModal\n request={request}\n payload={payload}\n priorContent={priorContent}\n priorError={priorError}\n targetPath={targetPath}\n onDecide={onDecide}\n />\n )\n }\n\n return (\n <SingleEditApprovalModal\n request={request}\n payload={payload}\n priorContent={priorContent}\n priorError={priorError}\n targetPath={targetPath}\n onDecide={onDecide}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// Action bar — bulk decisions shared between single-edit + multi-edit.\n// ---------------------------------------------------------------------------\n\ninterface ActionButton {\n label: string\n decision: ApprovalDecision\n shortcut: string\n destructive?: boolean\n}\n\nconst BULK_ACTIONS: ActionButton[] = [\n { label: 'Allow', decision: 'accept-once', shortcut: 'a' },\n { label: 'Allow for session', decision: 'accept-session', shortcut: 's' },\n { label: 'Always allow', decision: 'accept-safelist', shortcut: 'p' },\n { label: 'Deny', decision: 'deny', shortcut: 'd', destructive: true },\n]\n\ninterface ActionBarProps {\n actions: readonly ActionButton[]\n selected: number\n customLabel?: { idx: number, label: string }\n}\n\nfunction ActionBar({ actions, selected, customLabel }: ActionBarProps) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n return (\n <box style={{ flexDirection: 'row', height: 1, flexShrink: 0 }}>\n {actions.map((action, i) => {\n const isSelected = i === selected\n const tint = action.destructive ? COLOR.error : COLOR.brand\n const baseLabel = customLabel && customLabel.idx === i ? customLabel.label : action.label\n const labelText = ` ${baseLabel} (${action.shortcut}) `\n return (\n <box key={`${action.decision === 'deny' ? 'deny' : i}`} style={{ marginRight: 2, flexShrink: 0 }}>\n {isSelected\n ? (\n <text bg={tint} fg={SURFACE.background} wrapMode=\"none\">\n {labelText}\n </text>\n )\n : (\n <text bg={SURFACE.selection} fg={COLOR.dim} wrapMode=\"none\">\n {labelText}\n </text>\n )}\n </box>\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Single-edit layout — original Crush-style dialog with per-action shortcuts.\n// ---------------------------------------------------------------------------\n\ninterface SingleEditApprovalModalProps extends FileEditApprovalModalProps {\n payload: EditPayload | null\n priorContent: string\n priorError: string | null\n targetPath: string\n}\n\n// Top-left title rendered on the modal's top border. Surrounding spaces\n// punch through the border `─` ticks so the label reads cleanly.\nconst SINGLE_EDIT_TITLE = ' edit approval '\nconst MULTI_EDIT_TITLE = ' multi-edit approval '\n\n/**\n * Render the originator-attribution suffix for the modal's right-side\n * title overlay. Returns `''` for parent calls (no suffix); subagent\n * calls get a ` · child-N` tag so the user can tell which agent issued\n * the gate request when subagents bubble their gates up through the\n * parent's hook bus.\n */\nfunction originatorSuffix(originator: ApprovalOriginator | undefined): string {\n if (!originator || originator.kind === 'parent')\n return ''\n return ` · ${originator.label}`\n}\n\n/**\n * Budget a basename for the modal's top-right title overlay so it never\n * overflows into the left title or off-screen. Leaves room for the left\n * title, the surrounding mute padding spaces on both ends, the corner\n * glyphs, and an optional trailing suffix (e.g. ` · N/M selected` or\n * ` · child-N`).\n */\nfunction rightTitleFilename(\n targetPath: string,\n termWidth: number,\n leftTitleLen: number,\n suffixLen = 0,\n): string {\n const base = targetPath ? (targetPath.split('/').pop() ?? targetPath) : '(no path)'\n const budget = Math.max(8, termWidth - leftTitleLen - suffixLen - 6)\n return compactPath(base, budget)\n}\n\nfunction SingleEditApprovalModal({ request, payload, priorContent, priorError, targetPath, onDecide }: SingleEditApprovalModalProps) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const mdStyle = useMdStyle()\n const { width: termWidth } = useTerminalDimensions()\n const modal = useModal()\n\n const preview = useMemo(\n () => (payload ? previewEditPayload(payload, priorContent, 6) : null),\n [payload, priorContent],\n )\n const diffText = preview?.diffText ?? ''\n const focusedResolved = preview?.resolution[0]?.resolved ?? true\n\n const [selected, setSelected] = useState(0)\n const filetype = useMemo(() => filetypeFromPath(targetPath), [targetPath])\n const childSuffix = originatorSuffix(request.originator)\n const filename = useMemo(\n () => rightTitleFilename(targetPath, termWidth, SINGLE_EDIT_TITLE.length, childSuffix.length),\n [targetPath, termWidth, childSuffix.length],\n )\n\n const decide = useCallback((decision: ApprovalDecision) => {\n onDecide(decision)\n modal.close()\n }, [onDecide, modal])\n\n useKeyboard((key) => {\n if (key.name === 'left') {\n setSelected(i => (i - 1 + BULK_ACTIONS.length) % BULK_ACTIONS.length)\n return\n }\n if (key.name === 'right' || key.name === 'tab') {\n setSelected(i => (i + 1) % BULK_ACTIONS.length)\n return\n }\n if (key.name === 'return') {\n decide(BULK_ACTIONS[selected].decision)\n return\n }\n if (key.name === 'escape') {\n decide('deny')\n return\n }\n const ch = (key.sequence ?? '').toLowerCase()\n const hit = BULK_ACTIONS.find(a => a.shortcut === ch)\n if (hit)\n decide(hit.decision)\n })\n\n // Two columns of border + two columns of padding eat 4 cells of the\n // outer panel width; the inner diff (and its border) share the rest.\n const useSplit = termWidth >= 100\n const diffWidth = termWidth - 8\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1, flexShrink: 1, overflow: 'hidden' }}>\n <box\n title={SINGLE_EDIT_TITLE}\n titleAlignment=\"left\"\n bottomTitle=\" ←→ navigate · ↵ confirm · esc deny · a/s/p/d shortcuts \"\n style={{\n flexGrow: 1,\n flexShrink: 1,\n // overflow:hidden does double duty: scissors any rogue child\n // paint at the modal's bounds AND tells Yoga to constrain\n // children below their intrinsic measured size, so a tall\n // diff can never push the action bar below the bottom border.\n overflow: 'hidden',\n border: true,\n borderColor: COLOR.brand,\n backgroundColor: SURFACE.modal,\n padding: 1,\n flexDirection: 'column',\n }}\n >\n {priorError\n ? (\n <box style={{ height: 3, flexShrink: 0, marginBottom: 1 }}>\n <text fg={COLOR.error} wrapMode=\"word\">\n {`Couldn't read ${targetPath}: ${priorError}`}\n </text>\n </box>\n )\n : !focusedResolved && payload\n ? (\n <UnresolvedHunkPanel\n hunk={payload.hunks[0]}\n targetPath={targetPath}\n width={diffWidth + 2}\n />\n )\n : (\n <box\n style={{\n border: true,\n borderColor: COLOR.mute,\n width: diffWidth + 2,\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n marginBottom: 1,\n }}\n >\n {/* Big diffs scroll inside this panel — the inner <diff>\n sizes to its intrinsic content height via the Code\n renderable's measure func, the scrollbox bounds the\n visible viewport to whatever Yoga gave the parent.\n Mouse-wheel drives scroll via OpenTUI's hit-tester\n regardless of focus; keyboard stays on the modal's\n approval shortcuts. */}\n <scrollbox\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n <diff\n diff={diffText}\n view={useSplit ? 'split' : 'unified'}\n wrapMode=\"word\"\n showLineNumbers={true}\n syntaxStyle={mdStyle}\n treeSitterClient={getTreeSitterClient()}\n {...(filetype ? { filetype } : {})}\n addedBg={SURFACE.diff.addBg}\n removedBg={SURFACE.diff.removeBg}\n {...(SURFACE.diff.addContentBg ? { addedContentBg: SURFACE.diff.addContentBg } : {})}\n {...(SURFACE.diff.removeContentBg ? { removedContentBg: SURFACE.diff.removeContentBg } : {})}\n addedSignColor={SURFACE.diff.addFg}\n removedSignColor={SURFACE.diff.removeFg}\n style={{ width: diffWidth, flexShrink: 0 }}\n />\n </scrollbox>\n </box>\n )}\n\n <ActionBar actions={BULK_ACTIONS} selected={selected} />\n </box>\n {/* Top-right title overlay — sibling of the bordered modal so the\n renderer paints it on top of the top border (a child can't,\n OpenTUI's box scissor rect excludes the border row). Same\n trick as TitleOverlay / CompletionPopup. */}\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.model}>{filename}</span>\n {childSuffix.length > 0 && <span fg={COLOR.accent}>{childSuffix}</span>}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Multi-edit layout — per-hunk approval list.\n//\n// State machine (`mask: boolean[]`, 1:1 with payload.hunks):\n// - All true → \"Allow\" / \"Allow session\" / \"Always allow\" emit the\n// corresponding bulk decision (no `partial` wrapping —\n// keeps the safelist path identical to single-edit).\n// - All false → emits `'deny'`.\n// - Mixed → emits `{ kind: 'partial', mask }`.\n// `esc` always denies the whole call regardless of toggle state — same\n// \"stop everything\" rule the single-edit modal follows.\n// ---------------------------------------------------------------------------\n\nconst PER_EDIT_ACTIONS: ActionButton[] = [\n // Index 0 is dynamic: label reads `Allow N` / `Apply N selected` /\n // `Deny all` depending on the mask. The shortcut stays `a` for muscle\n // memory; submit logic interprets based on mask state.\n { label: 'Apply', decision: 'accept-once', shortcut: 'a' },\n { label: 'Allow for session', decision: 'accept-session', shortcut: 's' },\n { label: 'Always allow', decision: 'accept-safelist', shortcut: 'p' },\n { label: 'Deny all', decision: 'deny', shortcut: 'd', destructive: true },\n]\n\ninterface MultiEditApprovalModalProps extends FileEditApprovalModalProps {\n payload: EditPayload\n priorContent: string\n priorError: string | null\n targetPath: string\n}\n\nfunction MultiEditApprovalModal({ request, payload, priorContent, priorError, targetPath, onDecide }: MultiEditApprovalModalProps) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const mdStyle = useMdStyle()\n const { width: termWidth, height: termHeight } = useTerminalDimensions()\n const modal = useModal()\n const listScrollRef = useRef<ScrollBoxRenderable | null>(null)\n const diffScrollRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Per-edit approval mask. Default: every edit approved — matches the\n // user's most-likely intent and means a quick `enter` press = \"yes,\n // run everything\", same as the old modal.\n const [mask, setMask] = useState<readonly boolean[]>(\n () => payload.hunks.map(() => true),\n )\n const [cursor, setCursor] = useState(0)\n const [actionIdx, setActionIdx] = useState(0)\n // Two focus zones — list (toggle individual edits) or buttons (submit).\n // `tab` cycles between them; `↑/↓` moves within the active zone.\n const [zone, setZone] = useState<'list' | 'actions'>('list')\n\n const filetype = useMemo(() => filetypeFromPath(targetPath), [targetPath])\n\n const selectedCount = mask.filter(Boolean).length\n const total = mask.length\n\n // Build the decision payload from the current mask + chosen bulk action.\n const resolveDecision = useCallback((bulk: ApprovalDecision): ApprovalDecision => {\n if (bulk === 'deny')\n return 'deny'\n if (selectedCount === 0)\n return 'deny'\n if (selectedCount === total)\n return bulk\n // Partial — the safelist / session paths don't make sense for a\n // one-off mixed pick (the safelist entry is the tool name, with no\n // hunk-level scope), so we collapse them to `partial`.\n //\n // UX trade-off: pressing `p` (\"Always allow\") with a mixed mask\n // intentionally does NOT write a safelist entry — the user almost\n // certainly meant \"always allow THESE hunks\", which the safelist\n // can't represent. We could surface this in the action label\n // (e.g. \"Apply N selected (won't safelist)\") but the action bar\n // is already tight; the safe collapse to `partial` is the right\n // bias since the alternative (silently safelisting tool+path for\n // future calls based on a mixed pick) is far more surprising.\n return { kind: 'partial', mask: [...mask] }\n }, [mask, selectedCount, total])\n\n const decide = useCallback((decision: ApprovalDecision) => {\n onDecide(decision)\n modal.close()\n }, [onDecide, modal])\n\n const toggleAt = useCallback((idx: number) => {\n setMask((prev) => {\n const next = prev.slice()\n next[idx] = !next[idx]\n return next\n })\n }, [])\n\n const setAll = useCallback((value: boolean) => {\n setMask(prev => prev.map(() => value))\n }, [])\n\n useKeyboard((key) => {\n if (key.name === 'escape') {\n decide('deny')\n return\n }\n\n if (key.name === 'tab') {\n setZone(z => (z === 'list' ? 'actions' : 'list'))\n return\n }\n\n if (zone === 'list') {\n if (key.name === 'up' || key.name === 'k') {\n setCursor(c => (c - 1 + total) % total)\n return\n }\n if (key.name === 'down' || key.name === 'j') {\n setCursor(c => (c + 1) % total)\n return\n }\n if (key.name === 'space') {\n toggleAt(cursor)\n return\n }\n if (key.name === 'return') {\n // Sensible default: hopping into the actions row puts the cursor\n // on `Apply` so a follow-up enter submits.\n setZone('actions')\n setActionIdx(0)\n return\n }\n }\n else {\n if (key.name === 'left') {\n setActionIdx(i => (i - 1 + PER_EDIT_ACTIONS.length) % PER_EDIT_ACTIONS.length)\n return\n }\n if (key.name === 'right') {\n setActionIdx(i => (i + 1) % PER_EDIT_ACTIONS.length)\n return\n }\n if (key.name === 'return') {\n decide(resolveDecision(PER_EDIT_ACTIONS[actionIdx].decision))\n return\n }\n }\n\n // Bulk shortcuts work in either zone — keep `a/s/p/d` as muscle\n // memory regardless of where the cursor is.\n const ch = (key.sequence ?? '').toLowerCase()\n if (ch === 'y') {\n // `y` toggles all edits ON — quick \"approve every edit\" when the\n // user landed in a mixed state and wants to reset.\n setAll(true)\n return\n }\n if (ch === 'n') {\n setAll(false)\n return\n }\n const hit = PER_EDIT_ACTIONS.find(a => a.shortcut === ch)\n if (hit)\n decide(resolveDecision(hit.decision))\n })\n\n // Sizing — the modal renders inside ChatScreen's transcript slot\n // (`flexGrow: 1`), so the outer box claims the full conversation\n // area automatically. Inner widths pin to `panelWidth` (≈ termWidth\n // − chrome) so each inner box's right border lines up with the\n // outer right padding edge instead of overflowing. Heights are\n // distributed via `flexGrow` so the list + diff split the body\n // proportionally regardless of how tall the conversation slot\n // actually is.\n const panelWidth = Math.max(40, termWidth - 6)\n // The diff renderable lives inside the focused-diff box's own\n // border, so it gets `panelWidth - 2` columns of paint room.\n const diffWidth = panelWidth - 2\n\n // Run the lenient resolver once per (payload, priorContent) so every\n // hunk's resolvability is known — list rows badge unresolved hunks\n // with a warning glyph, and the focused-diff panel falls back to a\n // readable explanation instead of rendering a blank box when the\n // model produced an `old_string` the file doesn't contain.\n const preview = useMemo(\n () => previewEditPayload(payload, priorContent, 4),\n [payload, priorContent],\n )\n\n const focusedDiffText = preview.perHunkDiff[cursor] ?? ''\n const focusedResolved = preview.resolution[cursor]?.resolved ?? true\n const focusedAmbiguous = preview.resolution[cursor]?.ambiguous === true\n\n // List height cap — keeps the hunk list compact when there are\n // only a few edits (`numHunks + 2 borders`), but never lets it\n // claim more than ~⅓ of the available height when the batch is\n // long. Anything past the cap scrolls inside the panel. The diff\n // panel below takes the remaining space via `flexGrow: 1`.\n const listIdealRows = payload.hunks.length + 2 /* inner border */\n const listCap = Math.max(4, Math.min(Math.floor(termHeight / 3), 14))\n const listHeight = Math.min(listIdealRows, listCap)\n\n // Keep the focused row in view when the user scrolls past the\n // visible window. Deferred a frame for the same reason the\n // settings modal does it — scrollSize is computed post-commit.\n useEffect(() => {\n const sb = listScrollRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollChildIntoView(`edit-row-${cursor}`)\n })\n return () => cancelAnimationFrame(handle)\n }, [cursor])\n\n // Reset the focused-diff panel to the top whenever the user moves the\n // hunk cursor. The scrollbox is reused across hunks (React keeps it\n // mounted), so a previously-scrolled position would otherwise carry\n // into the next hunk's smaller diff.\n useEffect(() => {\n const sb = diffScrollRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollTop = 0\n })\n return () => cancelAnimationFrame(handle)\n }, [cursor])\n\n // Action button labels — dynamic on the first slot (\"Apply\" turns into\n // \"Apply N selected\" when the mask is mixed; back to \"Allow\" when all\n // are selected so the bulk safelist path stays discoverable).\n const applyLabel = selectedCount === 0\n ? 'Nothing selected'\n : selectedCount === total\n ? 'Apply all'\n : `Apply ${selectedCount}/${total}`\n\n // Top-right title overlay — `{filename}[ · child-N] · {N}/{total} selected`.\n // Compact the basename so the suffixes never get pushed off the right\n // edge on a narrow terminal.\n const selectionSuffix = ` · ${selectedCount}/${total} selected`\n const childSuffix = originatorSuffix(request.originator)\n const filename = rightTitleFilename(\n targetPath,\n termWidth,\n MULTI_EDIT_TITLE.length,\n selectionSuffix.length + childSuffix.length,\n )\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1, flexShrink: 1, overflow: 'hidden' }}>\n <box\n title={MULTI_EDIT_TITLE}\n titleAlignment=\"left\"\n bottomTitle=\" ↑↓ select · space toggle · y/n all/none · tab focus · a/s/p/d shortcuts · ↵ confirm · esc deny \"\n style={{\n flexGrow: 1,\n flexShrink: 1,\n // Same containment contract as the single-edit modal: clip any\n // child paint past the modal's bounds and let Yoga compress\n // the diff/list panels below their intrinsic measured size so\n // the action bar / bottom border stay anchored on screen.\n overflow: 'hidden',\n border: true,\n borderColor: COLOR.brand,\n backgroundColor: SURFACE.modal,\n padding: 1,\n flexDirection: 'column',\n }}\n >\n {priorError && (\n <box style={{ marginBottom: 1, flexShrink: 0 }}>\n <text fg={COLOR.error} wrapMode=\"word\">\n {`Couldn't read ${targetPath}: ${priorError}`}\n </text>\n </box>\n )}\n\n {/* Edit list — one row per hunk with a toggle indicator. The\n inner box's `width: panelWidth` matches the outer's content\n area exactly (outer `width: panelWidth + 4` minus 2 border\n + 2 padding) so the right border lands on the right padding\n edge of the parent instead of overflowing. */}\n <box\n style={{\n border: true,\n borderColor: zone === 'list' ? COLOR.brand : COLOR.mute,\n width: panelWidth,\n height: listHeight,\n flexShrink: 0,\n flexDirection: 'column',\n marginBottom: 1,\n }}\n >\n <scrollbox\n ref={listScrollRef}\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, paddingLeft: 1, paddingRight: 1 }}\n >\n {payload.hunks.map((hunk, i) => {\n const isCursor = i === cursor && zone === 'list'\n const checked = mask[i]\n const marker = checked ? '✓' : '✗'\n const markerColor = checked ? COLOR.accent : COLOR.error\n const cursorGlyph = isCursor ? '▸ ' : ' '\n const previewText = oneLinePreview(hunk.oldString, hunk.newString)\n const res = preview.resolution[i]\n const unresolved = res?.resolved === false\n return (\n <box\n key={i}\n id={`edit-row-${i}`}\n style={{\n flexShrink: 0,\n backgroundColor: isCursor ? SURFACE.selection : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isCursor ? COLOR.brand : COLOR.mute}>{cursorGlyph}</span>\n <span fg={markerColor}>{`${marker} `}</span>\n <span fg={isCursor ? COLOR.brand : COLOR.dim}>{`#${(i + 1).toString().padStart(2)} `}</span>\n {/* Two trailing spaces, not one: `⚠` (U+26A0) is East-Asian-Width\n Ambiguous and most terminals render it 2 cells wide while OpenTUI's\n text layout reserves 1, which paints the next char over the glyph's\n second cell. Matches the workaround in the unresolved-hunk panel below. */}\n {unresolved && <span fg={COLOR.error}>{'! '}</span>}\n <span fg={unresolved ? COLOR.error : COLOR.dim}>{previewText}</span>\n </text>\n </box>\n )\n })}\n </scrollbox>\n </box>\n\n {/* Focused hunk panel. When the lenient resolver couldn't locate the\n model's `old_string` in the file (or the match was ambiguous\n and `replace_all` is off), render an explanatory fallback so\n the user understands why nothing would land — instead of an\n empty diff box. Same `width: panelWidth` rule as the list. */}\n {focusedResolved\n ? (\n <box\n style={{\n border: true,\n borderColor: COLOR.mute,\n width: panelWidth,\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n marginBottom: 1,\n }}\n >\n {/* Mirror the single-edit panel: the inner <diff> sizes to\n its intrinsic content via the Code measure func, the\n scrollbox bounds the viewport so a 100-line focused\n hunk can't push the action bar off-screen. Mouse-wheel\n drives scroll via OpenTUI's hit-tester regardless of\n focus; keyboard stays on the modal's approval handler.\n `diffScrollRef` is kept so the cursor-change effect\n resets `scrollTop = 0` when a new hunk is focused. */}\n <scrollbox\n ref={diffScrollRef}\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n <diff\n diff={focusedDiffText}\n view=\"unified\"\n wrapMode=\"word\"\n showLineNumbers={true}\n syntaxStyle={mdStyle}\n treeSitterClient={getTreeSitterClient()}\n {...(filetype ? { filetype } : {})}\n addedBg={SURFACE.diff.addBg}\n removedBg={SURFACE.diff.removeBg}\n {...(SURFACE.diff.addContentBg ? { addedContentBg: SURFACE.diff.addContentBg } : {})}\n {...(SURFACE.diff.removeContentBg ? { removedContentBg: SURFACE.diff.removeContentBg } : {})}\n addedSignColor={SURFACE.diff.addFg}\n removedSignColor={SURFACE.diff.removeFg}\n style={{ width: diffWidth, flexShrink: 0 }}\n />\n </scrollbox>\n </box>\n )\n : (\n <UnresolvedHunkPanel\n hunk={payload.hunks[cursor]}\n targetPath={targetPath}\n width={panelWidth}\n ambiguous={focusedAmbiguous}\n />\n )}\n\n <ActionBar\n actions={PER_EDIT_ACTIONS}\n selected={zone === 'actions' ? actionIdx : -1}\n customLabel={{ idx: 0, label: applyLabel }}\n />\n </box>\n {/* Top-right title overlay — sibling of the bordered modal so the\n renderer paints it on top of the top border. Selection-count\n rides `error` when nothing is checked / `accent` otherwise,\n mirroring the old inline header semantics. */}\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.model}>{filename}</span>\n {childSuffix.length > 0 && <span fg={COLOR.accent}>{childSuffix}</span>}\n <span fg={selectedCount === 0 ? COLOR.error : COLOR.accent}>{selectionSuffix}</span>\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n </box>\n )\n}\n\n/**\n * Compact one-line summary of an edit step for the list view. Keeps the\n * row a single cell tall regardless of how multi-line the actual edit\n * is — the focused-hunk diff below shows the full content.\n */\nfunction oneLinePreview(oldString: string, newString: string): string {\n const left = oneLine(oldString)\n const right = oneLine(newString)\n return `${left} → ${right}`\n}\n\nfunction oneLine(s: string): string {\n const collapsed = s.replace(/\\s+/g, ' ').trim()\n const max = 40\n return collapsed.length > max ? `${collapsed.slice(0, max)}…` : (collapsed || '(empty)')\n}\n\n// ---------------------------------------------------------------------------\n// UnresolvedHunkPanel — shown when the lenient resolver couldn't locate\n// the model's `old_string` in the file (or matched ambiguously without\n// `replace_all`). The user needs to see what the model *intended* even\n// though no diff is paintable; the tool would otherwise report this edit\n// as `failed` after the modal closes.\n// ---------------------------------------------------------------------------\n\ninterface UnresolvedHunkPanelProps {\n hunk: { oldString: string, newString: string } | undefined\n targetPath: string\n width: number\n ambiguous?: boolean\n}\n\nfunction UnresolvedHunkPanel({ hunk, targetPath, width, ambiguous }: UnresolvedHunkPanelProps) {\n const COLOR = useColors()\n const oldPreview = hunk ? snippet(hunk.oldString) : '(missing)'\n const newPreview = hunk ? snippet(hunk.newString) : '(missing)'\n const headline = ambiguous\n ? `old_string matches multiple places in ${targetPath} — set replace_all=true or expand the match.`\n : `old_string not found in ${targetPath}.`\n return (\n <box\n style={{\n border: true,\n borderColor: COLOR.error,\n width,\n // Claim the panel slot but stay shrinkable — on a tight terminal\n // a long unresolved snippet must not push the action bar past\n // the modal's bottom border. overflow:hidden clips any excess\n // paint at the warning panel's bounds.\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n marginBottom: 1,\n paddingLeft: 1,\n paddingRight: 1,\n paddingTop: 1,\n paddingBottom: 1,\n flexDirection: 'column',\n }}\n >\n <text wrapMode=\"word\">\n <span fg={COLOR.error}>{'! '}</span>\n <span fg={COLOR.error}>{headline}</span>\n </text>\n <text wrapMode=\"word\">\n <span fg={COLOR.mute}>{'Approving will report this edit as '}</span>\n <span fg={COLOR.error}>failed</span>\n <span fg={COLOR.mute}>{' once the tool runs. Deny to skip it.'}</span>\n </text>\n <box style={{ marginTop: 1, flexDirection: 'column', flexShrink: 0 }}>\n <text wrapMode=\"word\">\n <span fg={COLOR.mute}>{'old_string '}</span>\n <span fg={COLOR.dim}>{`(${hunk?.oldString.length ?? 0} chars)`}</span>\n </text>\n <text wrapMode=\"word\">\n <span fg={COLOR.error}>{oldPreview}</span>\n </text>\n </box>\n <box style={{ marginTop: 1, flexDirection: 'column', flexShrink: 0 }}>\n <text wrapMode=\"word\">\n <span fg={COLOR.mute}>{'new_string '}</span>\n <span fg={COLOR.dim}>{`(${hunk?.newString.length ?? 0} chars)`}</span>\n </text>\n <text wrapMode=\"word\">\n <span fg={COLOR.accent}>{newPreview}</span>\n </text>\n </box>\n </box>\n )\n}\n\nfunction snippet(s: string): string {\n const max = 240\n const trimmed = s.replace(/\\r\\n/g, '\\n')\n return trimmed.length > max ? `${trimmed.slice(0, max)}…` : (trimmed || '(empty)')\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable, ScrollBoxRenderable, TextareaRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type {\n AnswerValue,\n ConfirmQuestion,\n InteractionRequest,\n InteractionResponse,\n PlanDecision,\n PlanRequest,\n Question,\n QuestionRequest,\n SelectQuestion,\n TextQuestion,\n} from '../chat/interactions'\nimport { defaultTextareaKeyBindings } from '@opentui/core'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useColors } from '../chat/theme-context'\nimport { useModalAwareFocus } from './modal'\nimport { useMdStyle } from './theme'\n\n// ---------------------------------------------------------------------------\n// Textarea binding — `return` submits, `shift+return` inserts a newline.\n// Mirrors `makeSubmitBindings(true)` in `screens.tsx` but kept private so\n// this module has no cross-import on screens.tsx. Declared early so the\n// component bodies below can reference it without a forward use.\n// ---------------------------------------------------------------------------\n\nconst COMMENT_TEXTAREA_BINDINGS = (() => {\n const base = defaultTextareaKeyBindings.filter(\n b => b.name !== 'return' && !(b.name === 'a' && b.ctrl && !b.shift && !b.meta),\n )\n return [\n ...base,\n { name: 'a' as const, ctrl: true, action: 'select-all' as const },\n { name: 'return' as const, action: 'submit' as const },\n { name: 'return' as const, shift: true, action: 'newline' as const },\n ]\n})()\n\n/**\n * InteractionBlock — picker UI for `present_plan` and `ask_user` tool calls.\n *\n * Sits in the chat screen's prompt slot whenever the head of the\n * interactions queue is non-empty (live or resumed — the App layer\n * doesn't differentiate at this layer). Two surfaces driven off the\n * request's discriminator:\n *\n * - `plan` → renders the title + markdown body + (optional) step list,\n * then a 3-option decision picker. Reject and revise can flow into a\n * comment textarea for free-text feedback before final submit.\n *\n * - `question` → renders the question text, then a choice picker\n * (when the model provided choices) or a free-text answer textarea\n * (otherwise).\n *\n * Esc aborts the entire run via the parent keyboard handler; per-call\n * accept/deny only happens through the picker below — same convention\n * as `ApprovalBlock`.\n */\nexport function InteractionBlock({\n request,\n onResolve,\n}: {\n request: InteractionRequest\n /** Submit the user's response to the head of the queue. */\n onResolve: (response: InteractionResponse) => void\n}) {\n if (request.kind === 'plan')\n return <PlanInteractionBlock request={request} onResolve={onResolve} />\n return <QuestionInteractionBlock request={request} onResolve={onResolve} />\n}\n\n// ---------------------------------------------------------------------------\n// Shared shell — bordered container with a colored title + esc hint.\n//\n// `maxHeight` caps how tall the shell can grow when its content overflows;\n// the inner scrollbox (in the question wizard) takes the remaining space\n// inside the cap. Plans don't pass a cap — they're size-bounded by\n// `PLAN_BODY_MAX_LINES` and would look weird in a tiny scrollable window.\n// ---------------------------------------------------------------------------\n\n/** Subset of Yoga `maxHeight` values that OpenTUI's `<box>` accepts. */\ntype MaxHeightValue = number | `${number}%` | 'auto'\n\nfunction InteractionShell({\n title,\n maxHeight,\n children,\n}: {\n title: string\n /** Yoga `maxHeight` value — cells, percentage of parent, or `'auto'`. */\n maxHeight?: MaxHeightValue\n children: ReactNode\n}) {\n const COLOR = useColors()\n return (\n <box\n title={title}\n style={{\n border: true,\n borderColor: COLOR.warn,\n paddingLeft: 1,\n paddingRight: 1,\n flexDirection: 'column',\n flexShrink: 0,\n ...(maxHeight !== undefined ? { maxHeight } : {}),\n }}\n >\n {children}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Plan picker\n// ---------------------------------------------------------------------------\n\n/** Number of plan-body lines rendered inline before we truncate. */\nconst PLAN_BODY_MAX_LINES = 8\n\ninterface PlanOptionItem extends OptionItem {\n id: PlanDecision\n}\n\nconst PLAN_OPTIONS: readonly PlanOptionItem[] = [\n { id: 'approve', label: 'approve', description: 'accept the plan and continue' },\n { id: 'revise', label: 'revise', description: 'ask the agent to refine the plan' },\n { id: 'reject', label: 'reject', description: 'refuse the plan — the agent will see your feedback' },\n]\n\nfunction PlanInteractionBlock({\n request,\n onResolve,\n}: {\n request: PlanRequest\n onResolve: (response: InteractionResponse) => void\n}) {\n const COLOR = useColors()\n const mdStyle = useMdStyle()\n const focused = useModalAwareFocus()\n const [stage, setStage] = useState<'pick' | 'comment'>('pick')\n const [decision, setDecision] = useState<PlanDecision | null>(null)\n const textareaRef = useRef<TextareaRenderable | null>(null)\n\n const { title, plan, steps } = request.payload\n\n // Back-navigation — `shift+↹` from the comment stage returns to the\n // decision picker so a user who pressed `revise` or `reject` by\n // mistake can switch to `approve` without aborting the whole run.\n // The picker's cursor is fresh on return (no need to remember the\n // prior pick — the user just chose differently). No-op while on\n // the `pick` stage; the wizard isn't deep enough to need back.\n useKeyboard((key) => {\n if (!focused)\n return\n if (key.name !== 'tab' || !key.shift)\n return\n if (key.ctrl || key.meta || key.option)\n return\n if (stage !== 'comment')\n return\n setStage('pick')\n })\n\n // Plan body is rendered as markdown but capped at PLAN_BODY_MAX_LINES so a\n // verbose plan can't push the picker off-screen. The full plan stays in\n // the persisted tool_call.input — the user can always re-read via the\n // transcript / session-details modal.\n const { bodyText, truncated } = useMemo(() => {\n const lines = plan.split('\\n')\n if (lines.length <= PLAN_BODY_MAX_LINES)\n return { bodyText: plan, truncated: false }\n return {\n bodyText: lines.slice(0, PLAN_BODY_MAX_LINES).join('\\n'),\n truncated: true,\n }\n }, [plan])\n\n const onPick = useCallback((value: string) => {\n if (value === 'approve') {\n onResolve({ kind: 'plan', decision: 'approve' })\n return\n }\n setDecision(value as PlanDecision)\n setStage('comment')\n }, [onResolve])\n\n const onSubmitComment = useCallback(() => {\n const value = textareaRef.current?.plainText?.trim() ?? ''\n onResolve({\n kind: 'plan',\n decision: decision ?? 'revise',\n ...(value ? { comment: value } : {}),\n })\n }, [onResolve, decision])\n\n if (stage === 'comment') {\n return (\n <InteractionShell title={` ${decision === 'reject' ? 'reject plan' : 'revise plan'} · esc to abort run `}>\n <text fg={COLOR.dim} wrapMode=\"word\">\n Add an optional comment for the agent (\n <span fg={COLOR.warn}>↵</span>\n {' '}\n to submit,\n {' '}\n <span fg={COLOR.warn}>shift+↵</span>\n {' '}\n for a newline). Empty = no comment.\n </text>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 5,\n flexDirection: 'column',\n marginTop: 1,\n }}\n >\n <textarea\n ref={textareaRef}\n focused={focused}\n keyBindings={COMMENT_TEXTAREA_BINDINGS}\n placeholder=\"comment (optional)…\"\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={onSubmitComment}\n />\n </box>\n <text fg={COLOR.mute} style={{ marginTop: 1 }}>\n <span fg={COLOR.warn}>shift+↹</span>\n {' back to decision'}\n </text>\n </InteractionShell>\n )\n }\n\n return (\n <InteractionShell title=\" approve plan · esc to abort run \">\n <text fg={COLOR.brand} wrapMode=\"none\">\n {title}\n </text>\n <box style={{ flexDirection: 'column', marginTop: 1, marginBottom: 1 }}>\n <markdown\n content={bodyText}\n syntaxStyle={mdStyle}\n streaming={false}\n internalBlockMode=\"coalesced\"\n fg={COLOR.dim}\n />\n {truncated && (\n <text fg={COLOR.mute}>\n {`… plan body truncated (${plan.split('\\n').length - PLAN_BODY_MAX_LINES} more lines). Open the session details to see full content.`}\n </text>\n )}\n {steps && steps.length > 0 && (\n <box style={{ flexDirection: 'column', marginTop: 1 }}>\n <text fg={COLOR.mute}>steps:</text>\n {steps.map((step, i) => (\n <text key={step.id} fg={COLOR.dim} wrapMode=\"word\">\n <span fg={COLOR.warn}>{` ${i + 1}. `}</span>\n <span fg={COLOR.brand}>{step.title}</span>\n {step.description && (\n <span fg={COLOR.mute}>{` — ${step.description}`}</span>\n )}\n </text>\n ))}\n </box>\n )}\n </box>\n <OptionList items={PLAN_OPTIONS} onPick={onPick} />\n </InteractionShell>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Question wizard — one form, one or more questions, mixed types.\n//\n// Renders every question stacked so the user always sees the form's\n// shape and their progress. Only the active question shows an input;\n// completed ones show their answer summary, pending ones a placeholder.\n// On submit the active question advances; the LAST submit resolves the\n// whole batch with the `answers` record.\n//\n// Forward-only by design — once a question is answered the user can't\n// scroll back. If they need to revise, esc aborts the run and the\n// model re-issues the request.\n// ---------------------------------------------------------------------------\n\n/**\n * Cap the question-wizard's height to ~60% of the terminal so the\n * transcript above stays readable when the model fires a long batch.\n * The inner scrollbox handles overflow; the active question is\n * scrolled into view on every advance.\n */\nconst QUESTION_WIZARD_MAX_HEIGHT: MaxHeightValue = '60%'\n\nfunction QuestionInteractionBlock({\n request,\n onResolve,\n}: {\n request: QuestionRequest\n onResolve: (response: InteractionResponse) => void\n}) {\n const COLOR = useColors()\n const mdStyle = useMdStyle()\n const focused = useModalAwareFocus()\n const { intro, questions } = request.payload\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Seed answers with any `default` values declared by the model so the\n // user sees them pre-filled when they're rendered as a \"done\" summary\n // after being skipped (only possible when `required: false`).\n const [activeIndex, setActiveIndex] = useState(0)\n const [answers, setAnswers] = useState<Record<string, AnswerValue>>(() => {\n const initial: Record<string, AnswerValue> = {}\n for (const q of questions) {\n if ((q.type === 'text' || q.type === 'textarea') && q.default)\n initial[q.id] = q.default\n }\n return initial\n })\n\n const onAnswer = useCallback((id: string, value: AnswerValue) => {\n const next = { ...answers, [id]: value }\n if (activeIndex + 1 >= questions.length) {\n onResolve({ kind: 'question', answers: next })\n return\n }\n setAnswers(next)\n setActiveIndex(activeIndex + 1)\n }, [answers, activeIndex, questions, onResolve])\n\n // Back-navigation — `shift+tab` rewinds one step. The prior answer\n // stays in `answers` so the input re-mounts pre-filled (see\n // `QuestionRow`'s `initialAnswer` prop) and the user can edit\n // instead of retyping. No-op on the first question.\n //\n // Multi-question batches only: a single-question wizard has nothing\n // to go back to, so we suppress the affordance entirely. `cycleAgent`\n // (also `shift+tab`) is gated on `!pendingInteraction` in\n // `app.tsx`, so the same key doesn't cycle profiles while the\n // wizard owns the screen.\n const canGoBack = activeIndex > 0\n useKeyboard((key) => {\n if (!focused)\n return\n if (key.name !== 'tab' || !key.shift)\n return\n if (key.ctrl || key.meta || key.option)\n return\n if (!canGoBack)\n return\n setActiveIndex(i => Math.max(0, i - 1))\n })\n\n // Auto-scroll the active row into view whenever it changes — same\n // pattern as the transcript's selected-turn anchor effect. Deferred\n // one frame via `requestAnimationFrame` because OpenTUI's scrollbar\n // computes `scrollSize` during its post-commit layout pass; reading\n // a child's `y` / `height` synchronously after a React commit can\n // see stale measurements (the inactive→active swap changed the row's\n // height, and Yoga hasn't reflowed yet).\n //\n // The LAST question's submit resolves the whole interaction, which\n // unmounts this component, so no extra \"snap to bottom\" branch is\n // needed for that case.\n useEffect(() => {\n const scrollbox = scrollboxRef.current\n if (!scrollbox)\n return\n const activeQuestion = questions[activeIndex]\n if (!activeQuestion)\n return\n const handle = requestAnimationFrame(() => {\n scrollbox.scrollChildIntoView(anchorIdFor(activeQuestion.id))\n })\n return () => cancelAnimationFrame(handle)\n }, [activeIndex, questions])\n\n const titleLabel = questions.length > 1\n ? ` answer ${activeIndex + 1}/${questions.length} · esc to abort run `\n : ' answer · esc to abort run '\n\n return (\n <InteractionShell title={titleLabel} maxHeight={QUESTION_WIZARD_MAX_HEIGHT}>\n {intro && (\n <box style={{ flexDirection: 'column', marginBottom: 1, flexShrink: 0 }}>\n <markdown\n content={intro}\n syntaxStyle={mdStyle}\n streaming={false}\n internalBlockMode=\"coalesced\"\n fg={COLOR.dim}\n />\n </box>\n )}\n <scrollbox\n ref={scrollboxRef}\n // Never grab focus — option-list / textarea inputs own the\n // keyboard. Mouse-wheel still works as a fallback for users\n // who want to peek at pending questions below the fold.\n focusable={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n {questions.map((q, i) => (\n <QuestionRow\n key={q.id}\n anchorId={anchorIdFor(q.id)}\n index={i}\n question={q}\n status={i < activeIndex ? 'done' : i === activeIndex ? 'active' : 'pending'}\n answer={answers[q.id]}\n initialAnswer={i === activeIndex ? answers[q.id] : undefined}\n onAnswer={onAnswer}\n />\n ))}\n </scrollbox>\n {questions.length > 1 && (\n <text fg={COLOR.mute} style={{ marginTop: 1, flexShrink: 0 }}>\n {canGoBack && (\n <>\n <span fg={COLOR.warn}>shift+↹</span>\n {' back'}\n <span fg={COLOR.mute}>{' · '}</span>\n </>\n )}\n <span fg={COLOR.mute}>\n {`question ${activeIndex + 1} of ${questions.length}`}\n </span>\n </text>\n )}\n </InteractionShell>\n )\n}\n\n/**\n * Stable DOM-style id per question used as the `scrollChildIntoView`\n * target. Lives outside the component so the effect's deps don't need a\n * referentially-stable map.\n */\nfunction anchorIdFor(questionId: string): string {\n return `interaction-q-${questionId}`\n}\n\ntype QuestionStatus = 'done' | 'active' | 'pending'\n\n/**\n * One row of the question form — header + (input | summary | placeholder)\n * depending on the row's status. Lays its own vertical breathing room so\n * a batch of rows reads as a list without a parent `gap`.\n */\nfunction QuestionRow({\n anchorId,\n index,\n question,\n status,\n answer,\n initialAnswer,\n onAnswer,\n}: {\n /** Render id targeted by `ScrollBoxRenderable.scrollChildIntoView`. */\n anchorId: string\n index: number\n question: Question\n status: QuestionStatus\n /** Answer for a done row's summary — undefined for active / pending rows. */\n answer: AnswerValue | undefined\n /**\n * Pre-fill the active input with this value. Used by `shift+↹` back\n * navigation so revisiting a previously-answered question re-mounts\n * the input with the prior value editable in place. Defaults to\n * `question.default` for free-text types when no prior answer\n * exists.\n */\n initialAnswer: AnswerValue | undefined\n onAnswer: (id: string, value: AnswerValue) => void\n}) {\n const COLOR = useColors()\n\n // Status marker matches the visual hierarchy: ✓ done, ▶ active, · pending.\n // The active row's prompt rides `brand` so it stands out from context.\n const marker = status === 'done' ? '✓' : status === 'active' ? '▶' : '·'\n const markerColor\n = status === 'done'\n ? COLOR.accent\n : status === 'active'\n ? COLOR.brand\n : COLOR.mute\n const promptColor = status === 'active' ? COLOR.brand : COLOR.dim\n\n return (\n <box id={anchorId} style={{ flexDirection: 'column', marginBottom: 1, flexShrink: 0 }}>\n <text wrapMode=\"word\">\n <span fg={markerColor}>{`${marker} `}</span>\n <span fg={COLOR.mute}>{`${index + 1}. `}</span>\n <span fg={promptColor}>{question.prompt}</span>\n </text>\n {question.description && (\n <text fg={COLOR.mute} wrapMode=\"word\">\n {` ${question.description}`}\n </text>\n )}\n <box style={{ flexDirection: 'column', marginTop: activeMarginTopFor(status, question.type) }}>\n {status === 'active' && (\n <ActiveQuestionInput\n question={question}\n initialAnswer={initialAnswer}\n onSubmit={value => onAnswer(question.id, value)}\n />\n )}\n {status === 'done' && (\n <AnswerSummary question={question} answer={answer} />\n )}\n {status === 'pending' && (\n <text fg={COLOR.mute}>{` waiting…`}</text>\n )}\n </box>\n </box>\n )\n}\n\n/**\n * Per-type breathing room between the question header and its body.\n *\n * - `select` / `confirm` (active): 0 — the inline option list reads as\n * a tight continuation of the prompt (`▶ 2. Pick one` → ` ↳ react`).\n * - `text` / `textarea` (active): 1 — the bordered input box needs a\n * visible gap or it sits glued to the prompt line.\n * - Non-active rows: 0 — the summary / `waiting…` line sits flush.\n */\nfunction activeMarginTopFor(status: QuestionStatus, type: Question['type']): number {\n if (status !== 'active')\n return 0\n return type === 'text' || type === 'textarea' ? 1 : 0\n}\n\n/** Render a finalized answer as compact text below the question header. */\nfunction AnswerSummary({\n question,\n answer,\n}: {\n question: Question\n answer: AnswerValue | undefined\n}) {\n const COLOR = useColors()\n const text = formatAnswerForDisplay(question, answer)\n return (\n <text fg={COLOR.dim} wrapMode=\"word\">\n <span fg={COLOR.mute}>{' ↳ '}</span>\n {text}\n </text>\n )\n}\n\nfunction formatAnswerForDisplay(question: Question, answer: AnswerValue | undefined): string {\n if (answer === undefined || answer === '')\n return '(skipped)'\n if (question.type === 'confirm') {\n return typeof answer === 'boolean'\n ? (answer ? (question.affirmLabel ?? 'yes') : (question.denyLabel ?? 'no'))\n : '(invalid)'\n }\n if (question.type === 'select') {\n const choice = question.choices.find(c => c.id === answer)\n return choice ? choice.label : String(answer)\n }\n // text / textarea — collapse newlines for the one-line summary; the\n // full answer still rides the persisted tool_result for the model.\n if (typeof answer !== 'string')\n return '(invalid)'\n const oneLine = answer.replace(/\\s+/g, ' ').trim()\n return oneLine.length > 80 ? `${oneLine.slice(0, 80)}…` : oneLine\n}\n\n// ---------------------------------------------------------------------------\n// Active question — dispatches to a per-type input renderer.\n// ---------------------------------------------------------------------------\n\nfunction ActiveQuestionInput({\n question,\n initialAnswer,\n onSubmit,\n}: {\n question: Question\n /**\n * Carried answer to pre-fill the input. For text types this becomes\n * the textarea's `initialValue` / input's `value`; for select /\n * confirm it positions the cursor on the matching option. `undefined`\n * means \"first time on this question\" — fall back to the model's\n * `default` (text) or cursor 0 (select / confirm).\n */\n initialAnswer: AnswerValue | undefined\n onSubmit: (value: AnswerValue) => void\n}) {\n switch (question.type) {\n case 'text':\n return (\n <TextQuestionInput\n question={question}\n initialValue={typeof initialAnswer === 'string' ? initialAnswer : (question.default ?? '')}\n onSubmit={onSubmit}\n multiLine={false}\n />\n )\n case 'textarea':\n return (\n <TextQuestionInput\n question={question}\n initialValue={typeof initialAnswer === 'string' ? initialAnswer : (question.default ?? '')}\n onSubmit={onSubmit}\n multiLine\n />\n )\n case 'select':\n return (\n <SelectQuestionInput\n question={question}\n initialChoiceId={typeof initialAnswer === 'string' ? initialAnswer : undefined}\n onSubmit={onSubmit}\n />\n )\n case 'confirm':\n return (\n <ConfirmQuestionInput\n question={question}\n initialValue={typeof initialAnswer === 'boolean' ? initialAnswer : undefined}\n onSubmit={onSubmit}\n />\n )\n }\n}\n\n/**\n * Single- or multi-line text input. The `multiLine` flavor uses\n * `<textarea>` with `shift+↵` for newline; the single-line flavor uses\n * OpenTUI's `<input>` and submits on plain `↵`. Both honor the\n * question's `placeholder` and `default`.\n *\n * Required-field handling: an empty answer is allowed only when\n * `question.required` is explicitly `false`. For required free-text\n * questions the submit is ignored on an empty buffer so the user\n * never gets stuck pressing enter \"by accident\".\n */\nfunction TextQuestionInput({\n question,\n initialValue,\n onSubmit,\n multiLine,\n}: {\n question: TextQuestion\n /**\n * Buffer the input mounts with. Caller resolves the `priorAnswer →\n * question.default → empty` precedence so this component stays\n * dumb (one source of truth = the prop).\n */\n initialValue: string\n onSubmit: (value: AnswerValue) => void\n multiLine: boolean\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const textareaRef = useRef<TextareaRenderable | null>(null)\n const inputRef = useRef<InputRenderable | null>(null)\n // Required free-text answers default to FALSE — the user can skip and\n // the model will treat it as an empty answer. Explicit `required:\n // true` flips the flag and the submit handler refuses an empty buffer.\n const required = question.required ?? false\n\n const submit = useCallback(() => {\n const value = multiLine\n ? (textareaRef.current?.plainText ?? '')\n : (inputRef.current?.value ?? '')\n if (required && !value.trim())\n return\n onSubmit(value)\n }, [onSubmit, multiLine, required])\n\n const defaultPlaceholder = multiLine\n ? 'type your answer (↵ submit · shift+↵ newline)…'\n : 'type your answer (↵ submit)…'\n const placeholder = question.placeholder ?? defaultPlaceholder\n\n if (multiLine) {\n return (\n <box style={{ flexDirection: 'column' }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 5,\n flexDirection: 'column',\n }}\n >\n <textarea\n ref={textareaRef}\n focused={focused}\n keyBindings={COMMENT_TEXTAREA_BINDINGS}\n placeholder={placeholder}\n initialValue={initialValue}\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={submit}\n />\n </box>\n <TextSubmitHint multiLine required={required} />\n </box>\n )\n }\n\n return (\n <box style={{ flexDirection: 'column' }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n focused={focused}\n value={initialValue}\n placeholder={placeholder}\n onSubmit={submit}\n style={{ flexGrow: 1 }}\n />\n </box>\n <TextSubmitHint multiLine={false} required={required} />\n </box>\n )\n}\n\n/**\n * Submit-row hint shown below `TextQuestionInput`. Renders the\n * relevant submit shortcut + an optional `required` badge so the user\n * sees up-front whether an empty answer will be accepted. Shared\n * across the single-line / multi-line branches to keep the visual\n * contract identical.\n */\nfunction TextSubmitHint({ multiLine, required }: { multiLine: boolean, required: boolean }) {\n const COLOR = useColors()\n return (\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↵</span>\n {' submit'}\n {multiLine && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.warn}>shift+↵</span>\n {' newline'}\n </>\n )}\n {required && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.warn}>required</span>\n </>\n )}\n </text>\n )\n}\n\n/** Single-choice picker — one row per choice, returns the choice id. */\nfunction SelectQuestionInput({\n question,\n initialChoiceId,\n onSubmit,\n}: {\n question: SelectQuestion\n /** Pre-position the cursor on this choice id when re-entering the question. */\n initialChoiceId: string | undefined\n onSubmit: (value: AnswerValue) => void\n}) {\n const items = useMemo(\n () => question.choices.map(c => ({\n id: c.id,\n label: c.label,\n ...(c.description ? { description: c.description } : {}),\n })),\n [question.choices],\n )\n const initialCursor = useMemo(() => {\n if (initialChoiceId === undefined)\n return 0\n const idx = items.findIndex(i => i.id === initialChoiceId)\n return idx === -1 ? 0 : idx\n }, [items, initialChoiceId])\n return <OptionList items={items} initialCursor={initialCursor} onPick={id => onSubmit(id)} />\n}\n\n/** Yes/no picker — returns a boolean. */\nfunction ConfirmQuestionInput({\n question,\n initialValue,\n onSubmit,\n}: {\n question: ConfirmQuestion\n /** Pre-position the cursor on yes (true) or no (false) when re-entering. */\n initialValue: boolean | undefined\n onSubmit: (value: AnswerValue) => void\n}) {\n const items = useMemo(\n () => [\n { id: 'yes', label: question.affirmLabel ?? 'yes' },\n { id: 'no', label: question.denyLabel ?? 'no' },\n ],\n [question.affirmLabel, question.denyLabel],\n )\n const initialCursor = initialValue === false ? 1 : 0\n return <OptionList items={items} initialCursor={initialCursor} onPick={id => onSubmit(id === 'yes')} />\n}\n\n/**\n * Inline option list rendered as text rows — replaces OpenTUI's `<select>`\n * so the focused option uses the same `↳ <label>` format and indentation\n * as a finished question's `AnswerSummary`. Visually, the active row in\n * the form reads as the answer the user is in the middle of picking.\n *\n * Same pattern as `SessionsScreen` / `SettingsModal`: custom rendering\n * + a local `useKeyboard` listener. Renders one `<text>` per option,\n * with the focused row in `brand` and others in `dim`. `up`/`down`/\n * `j`/`k` move the cursor; `return` commits.\n *\n * Indent budget — 3 columns total:\n * - ` ` 3-space pad to align with the question prompt's text column\n * (the question header eats `▶ 1. ` = 5 cells, but the body text\n * starts at the prompt — we mirror the ` ↳ ` indent the\n * `AnswerSummary` uses for done rows).\n * - `↳ ` or `· ` glyph (focused vs unfocused).\n *\n * Wraps on horizontal edges (`up` at top → bottom, `down` at bottom →\n * top), matching the OpenTUI `wrapSelection` behavior.\n */\ninterface OptionItem {\n id: string\n label: string\n description?: string\n}\n\nfunction OptionList({\n items,\n initialCursor,\n onPick,\n}: {\n items: readonly OptionItem[]\n /** Seed the cursor on a specific row at mount. Clamped to bounds. Defaults to 0. */\n initialCursor?: number\n onPick: (id: string) => void\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const [cursor, setCursor] = useState(() => {\n if (typeof initialCursor !== 'number' || items.length === 0)\n return 0\n return Math.max(0, Math.min(items.length - 1, initialCursor))\n })\n\n const moveCursor = useCallback((delta: -1 | 1) => {\n setCursor((c) => {\n if (items.length === 0)\n return c\n return ((c + delta) % items.length + items.length) % items.length\n })\n }, [items.length])\n\n useKeyboard((key) => {\n if (!focused)\n return\n // Drop any key with a modifier so global shortcuts (ctrl+o, esc, …)\n // still reach the AppShell's keyboard handler while an option list\n // is mounted. OpenTUI's `ParsedKey` exposes `option` for Alt.\n if (key.ctrl || key.meta || key.shift || key.option)\n return\n if (key.name === 'up' || key.name === 'k') {\n moveCursor(-1)\n return\n }\n if (key.name === 'down' || key.name === 'j') {\n moveCursor(1)\n return\n }\n if (key.name === 'return') {\n const item = items[cursor]\n if (item)\n onPick(item.id)\n }\n })\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {items.map((item, i) => {\n const isCursor = i === cursor\n return (\n <text key={item.id} wrapMode=\"word\">\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={isCursor ? COLOR.brand : COLOR.mute}>\n {isCursor ? '↳ ' : '· '}\n </span>\n <span fg={isCursor ? COLOR.brand : COLOR.dim}>{item.label}</span>\n {item.description && (\n <span fg={COLOR.mute}>{` · ${item.description}`}</span>\n )}\n </text>\n )\n })}\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport { useTerminalDimensions } from '@opentui/react'\n\n/**\n * Long URL rendered as N single-row OSC 8 hyperlinks instead of one\n * wrapped hyperlink.\n *\n * Why split: OpenTUI packs a `linkId` per cell into the attribute\n * bitfield, but its renderer emits an OSC 8 open/close pair per visual\n * row without the spec's `id=` parameter. Terminals (notably iTerm2)\n * need a matching `id=` to stitch hyperlink fragments across rows —\n * without it, only one row of a wrapped link ends up clickable. We\n * pre-chunk the URL into rows that fit on one line and render each as\n * its own intact `<a href>` with `wrapMode=\"none\"`, so the terminal\n * never sees a wrapped hyperlink and clicking any row opens the full\n * URL.\n *\n * Width: caller passes a hard cap (its container's content-area width).\n * We further clamp by the live terminal width minus `chromeWidth` so a\n * narrow terminal still chunks short enough to avoid forced wrap by\n * the layout engine. `chromeWidth` is the container's border + padding\n * budget — default 14 fits a typical modal; pass a smaller value for\n * less-padded containers (e.g. 6 for the auth wizard panel).\n */\nexport function OAuthUrlBlock({\n url,\n fg,\n maxLineWidth,\n chromeWidth = 14,\n}: {\n url: string\n fg: string | undefined\n /** Caller's container content-area width cap (container width minus padding+border). */\n maxLineWidth: number\n /** Reserved columns for the container's own border + padding. Defaults to a modal's budget. */\n chromeWidth?: number\n}) {\n const { width: termWidth } = useTerminalDimensions()\n const chunkWidth = Math.max(20, Math.min(maxLineWidth, termWidth - chromeWidth))\n const lines = chunkString(url, chunkWidth)\n return (\n <>\n {lines.map((line, i) => (\n <text key={i} wrapMode=\"none\" fg={fg}>\n <a href={url}>{line}</a>\n </text>\n ))}\n </>\n )\n}\n\nfunction chunkString(s: string, n: number): string[] {\n if (s.length <= n)\n return [s]\n const out: string[] = []\n for (let i = 0; i < s.length; i += n)\n out.push(s.slice(i, i + n))\n return out\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable } from '@opentui/core'\nimport type { RefObject } from 'react'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { tryOpenBrowser } from '../chat/browser'\nimport { fetchOAuthRedirect } from '../chat/oauth-redirect'\nimport { useColors } from '../chat/theme-context'\nimport { errorMessage } from '../errors'\nimport { writeToClipboard } from './clipboard'\nimport { OAuthUrlBlock } from './oauth-url-block'\n\n/** Keystroke shown next to the open-browser button. */\nconst OPEN_BROWSER_KEY = 'ctrl+b'\n\n/**\n * Unified affordance for surfacing an OAuth authorization URL in the TUI:\n *\n * 1. A prominent OSC 8 hyperlink button — \"Click here to open auth URL in\n * browser\". Honored by every terminal that supports clickable links\n * (iTerm2, Kitty, WezTerm, Ghostty, Alacritty, …). Lands on the user's\n * LOCAL terminal even over SSH because OSC 8 is interpreted client-side.\n * 2. The full URL rendered greyed below, chunked into per-row hyperlinks\n * via {@link OAuthUrlBlock}. Backup channel for terminals without OSC 8,\n * and for users who'd rather copy via drag-select than click.\n * 3. Optional paste-back input. When `paste` is provided, an `<input>`\n * renders below the URL so the user can paste the FULL redirect URL\n * their browser ended up at after authorizing — useful when zidane runs\n * over SSH and the browser-side redirect to loopback can't reach the\n * remote callback server. The caller's `onSubmit` typically pipes the\n * pasted value through {@link fetchOAuthRedirect} so the in-process\n * server receives the request and the OAuth promise resolves through\n * the same happy path a real browser would have taken.\n *\n * The component owns presentation only — keyboard focus, input ref, and\n * the submit handler stay with the caller so the affordance composes\n * cleanly with whatever picker / wizard / modal it lives in.\n */\nexport function OAuthAuthBlock({\n authUrl,\n maxLineWidth,\n chromeWidth = 14,\n paste,\n}: {\n authUrl: string\n /** Container content-area width cap (modal/panel width minus border + padding). */\n maxLineWidth: number\n /** Reserved columns for the container's own border + padding. */\n chromeWidth?: number\n paste?: {\n /** Ref the caller reads `value` from on submit. */\n inputRef: RefObject<InputRenderable | null>\n /** Whether the input should grab keys (caller controls focus). */\n focused: boolean\n /** Fired on `enter` — caller reads the input's value and handles the fetch. */\n onSubmit: () => void\n /** Optional override for the input placeholder. */\n placeholder?: string\n /** Optional inline error / progress message rendered above the input. */\n hint?: { text: string, tone: 'dim' | 'error' | 'accent' }\n }\n}) {\n const COLOR = useColors()\n return (\n <box style={{ flexDirection: 'column', gap: 1 }}>\n <OpenBrowserButton authUrl={authUrl} />\n <box style={{ flexDirection: 'column' }}>\n <text fg={COLOR.mute}>or copy the URL manually:</text>\n <OAuthUrlBlock\n url={authUrl}\n fg={COLOR.dim}\n maxLineWidth={maxLineWidth}\n chromeWidth={chromeWidth}\n />\n </box>\n {paste && (\n <box style={{ flexDirection: 'column' }}>\n <text fg={COLOR.mute}>\n or — if the browser couldn't reach this machine (SSH, firewall) — paste the URL it tried to redirect to:\n </text>\n {paste.hint && (\n <text fg={toneColor(paste.hint.tone, COLOR)}>{paste.hint.text}</text>\n )}\n <box\n style={{\n border: true,\n borderColor: paste.focused ? COLOR.borderActive : COLOR.border,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={paste.inputRef}\n focused={paste.focused}\n placeholder={paste.placeholder ?? 'paste redirect URL and press enter…'}\n onSubmit={paste.onSubmit}\n style={{ flexGrow: 1 }}\n />\n </box>\n </box>\n )}\n </box>\n )\n}\n\n/**\n * Real button: a bordered, brand-colored box hosting a labeled keystroke\n * hint. Pressing `ctrl+b` (anywhere this block is mounted) calls\n * {@link tryOpenBrowser} to launch the URL via the OS handler AND\n * {@link writeToClipboard} so the URL also lands in the user's clipboard\n * (OSC 52 → works over SSH, native helper → works locally). Both fire\n * because either alone can fail silently — `open`/`xdg-open` are no-ops\n * on a headless box, and OSC 52 is disabled on some terminals — and the\n * combination covers both failure modes without prompting again.\n *\n * The visible label is also wrapped in an OSC 8 hyperlink so a mouse\n * click in a supporting terminal (iTerm2, Kitty, WezTerm, …) reaches the\n * same browser. The keybind is the authoritative path — it doesn't\n * depend on terminal capability — and the hyperlink is the bonus.\n */\nfunction OpenBrowserButton({ authUrl }: { authUrl: string }) {\n const COLOR = useColors()\n const [feedback, setFeedback] = useState<string | null>(null)\n const feedbackTimer = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n const trigger = useCallback(() => {\n tryOpenBrowser(authUrl)\n const copied = writeToClipboard(authUrl)\n setFeedback(copied\n ? 'opened in browser · URL copied to clipboard'\n : 'opened in browser')\n if (feedbackTimer.current)\n clearTimeout(feedbackTimer.current)\n feedbackTimer.current = setTimeout(setFeedback, 4000, null)\n }, [authUrl])\n\n useKeyboard((key) => {\n // `ctrl+b` is not in OpenTUI's `defaultTextareaKeyBindings` action\n // table, so the focused paste input ignores it — meaning this\n // listener can fire regardless of which input owns focus.\n if (key.ctrl && key.name === 'b') {\n key.preventDefault()\n trigger()\n }\n })\n\n useEffect(() => () => {\n if (feedbackTimer.current)\n clearTimeout(feedbackTimer.current)\n }, [])\n\n return (\n <box style={{ flexDirection: 'column' }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.brand,\n paddingLeft: 1,\n paddingRight: 1,\n alignSelf: 'flex-start',\n }}\n >\n <text wrapMode=\"none\">\n <a href={authUrl} fg={COLOR.brand}>↗ Open auth URL in browser</a>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.warn}>{OPEN_BROWSER_KEY}</span>\n <span fg={COLOR.mute}>{' open · click also works'}</span>\n </text>\n </box>\n {feedback && (\n <text fg={COLOR.accent}>{`✓ ${feedback}`}</text>\n )}\n </box>\n )\n}\n\nfunction toneColor(\n tone: 'dim' | 'error' | 'accent',\n COLOR: ReturnType<typeof useColors>,\n): string {\n switch (tone) {\n case 'error': return COLOR.error\n case 'accent': return COLOR.accent\n case 'dim': return COLOR.dim\n }\n}\n\n/**\n * Self-contained MCP authorizing panel:\n *\n * - Renders the {@link OAuthAuthBlock} for the URL + paste input.\n * - Owns the input ref, the submit handler, and the hint state.\n * - Auto-fetches the pasted URL via {@link fetchOAuthRedirect} so the\n * MCP SDK's loopback callback server resolves the OAuth promise\n * through the same code path a real browser-redirect would have\n * taken — works over SSH where the browser can't reach the remote\n * callback server directly.\n *\n * `inputFocused` is forwarded as the input's `focused` prop AND read by\n * the parent picker's `useKeyboard` (via a ref) to suppress single-key\n * shortcuts (`l` / `o` / `r`) while the user is typing into the input.\n */\nexport function McpAuthorizingPanel({\n serverName,\n authUrl,\n maxLineWidth,\n chromeWidth,\n inputFocused,\n}: {\n serverName: string\n authUrl: string\n maxLineWidth: number\n chromeWidth?: number\n inputFocused: boolean\n}) {\n const COLOR = useColors()\n const inputRef = useRef<InputRenderable | null>(null)\n const [hint, setHint] = useState<{ text: string, tone: 'dim' | 'error' | 'accent' } | null>(null)\n\n const onSubmit = useCallback(() => {\n const value = inputRef.current?.value?.trim() ?? ''\n if (!value)\n return\n setHint({ text: 'submitting redirect URL…', tone: 'dim' })\n void (async () => {\n try {\n const result = await fetchOAuthRedirect(value)\n if (result.status >= 200 && result.status < 300) {\n setHint({ text: 'redirect accepted — finalizing…', tone: 'accent' })\n }\n else {\n setHint({\n text: `callback server rejected the URL (${result.status}${result.message ? `: ${result.message}` : ''}). Cancel and retry.`,\n tone: 'error',\n })\n }\n }\n catch (err) {\n setHint({ text: errorMessage(err), tone: 'error' })\n }\n })()\n }, [])\n\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.brand}>{`Authorizing ${serverName}`}</text>\n <text fg={COLOR.dim}>\n Your browser should have opened — complete the login, then return here.\n </text>\n <OAuthAuthBlock\n authUrl={authUrl}\n maxLineWidth={maxLineWidth}\n chromeWidth={chromeWidth}\n paste={{\n inputRef,\n focused: inputFocused,\n onSubmit,\n placeholder: 'paste redirect URL and press enter…',\n hint: hint ?? undefined,\n }}\n />\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { Session } from '../session'\nimport { useTerminalDimensions } from '@opentui/react'\nimport { useSettings } from '../chat/settings-context'\nimport { useColors } from '../chat/theme-context'\nimport { TODO_STATUS_GLYPHS, useActiveTodos } from '../chat/todos'\n\n// ---------------------------------------------------------------------------\n// TodoIndicator — single-line \"what is the agent working on?\" badge\n// above the prompt input.\n//\n// ◐ Currently in-progress todo title (truncated with ellipsis)\n//\n// Hides entirely (returns null) when:\n// - No session.\n// - No active run.\n// - Active run's todo list has no `in_progress` item.\n// - `Settings.showTodoIndicator` is off.\n//\n// Read-only — not focusable, not clickable. The user opens the full\n// list via `ctrl+t` (the `openTodos` keybinding); this bar is a\n// passive surface that surfaces the latest checkpoint at a glance.\n//\n// Renderer chrome only — the data slice + run resolution lives in\n// `chat/todos.ts`'s `useActiveTodos` so a future GUI shell can reuse it.\n// ---------------------------------------------------------------------------\n\ninterface TodoIndicatorProps {\n session: Session | null\n}\n\n/**\n * Layout budget when truncating the content. The bar never wraps —\n * single-line by contract — so we subtract every column consumed by\n * surrounding chrome before measuring how much room is left for the\n * todo's `content` text.\n *\n * - 2 cols ⇒ `<ChatScreen>`'s `border: true` (1 cell each side).\n * - 2 cols ⇒ this indicator's own `paddingLeft: 1 + paddingRight: 1`\n * (mirrors the queue block's outer padding so the bar\n * and queue read as aligned siblings).\n * - 4 cols ⇒ the indicator's own visible chrome: glyph \"◐\" (1) +\n * two spaces (2) + 1 col of trailing breathing room.\n *\n * That's 8 cols total of non-content overhead. Below `MIN_VISIBLE_TAIL`\n * the bar bails — a one-or-two-char tail isn't worth painting.\n */\nconst SCREEN_BORDER_COLS = 2\nconst INDICATOR_PADDING_COLS = 2\nconst CHROME_COLS = 4\nconst MIN_VISIBLE_TAIL = 8\n\nfunction truncateForWidth(text: string, max: number): string {\n if (max <= 0)\n return ''\n if (text.length <= max)\n return text\n if (max <= 1)\n return '…'\n return `${text.slice(0, max - 1)}…`\n}\n\nexport function TodoIndicator({ session }: TodoIndicatorProps) {\n const { settings } = useSettings()\n const COLOR = useColors()\n const { width: termWidth } = useTerminalDimensions()\n // Hooks before any early returns — keeps React's hook order stable.\n const state = useActiveTodos(session)\n\n if (!settings.showTodoIndicator)\n return null\n const item = state.inProgress\n if (!item)\n return null\n\n // Inside the ChatScreen's bordered outer box (1 cell each horizontal\n // side) and this indicator's own padding (1 each), the content area\n // is `termWidth - 4`. Subtract the visible chrome (glyph + spaces +\n // breathing room) to get the budget for the truncated content.\n const usable = Math.max(0, termWidth - SCREEN_BORDER_COLS - INDICATOR_PADDING_COLS - CHROME_COLS)\n if (usable < MIN_VISIBLE_TAIL)\n return null\n\n // Collapse internal whitespace + line breaks so the bar stays one\n // visual line even if a model emitted a multi-line `content`.\n const content = item.content.replace(/\\s+/g, ' ').trim()\n const display = truncateForWidth(content, usable)\n\n return (\n <box\n style={{\n // Hairline gap above so the bar visually separates from\n // whatever sits above (queue rows or the transcript bottom),\n // mirroring the queue box / interaction block spacing rules.\n marginTop: 1,\n flexShrink: 0,\n flexDirection: 'row',\n paddingLeft: 1,\n paddingRight: 1,\n }}\n >\n <text wrapMode=\"none\">\n {/* Glyph picks up the live-state `warn` tone — same color the\n modal uses for the in-progress row. Single source of truth\n for the visual language between the two surfaces. */}\n <span fg={COLOR.warn}>{TODO_STATUS_GLYPHS.in_progress}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={COLOR.dim}>{display}</span>\n </text>\n </box>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable, KeyEvent, PasteEvent, ScrollBoxRenderable, TextareaRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type { ProviderAuth } from '../chat/auth'\nimport type { CompletionProvider, CompletionReference } from '../chat/completion'\nimport type { Hint } from '../chat/hints'\nimport type {\n InteractionRequest,\n InteractionResponse,\n} from '../chat/interactions'\nimport type { ProviderDescriptor } from '../chat/providers'\nimport type { ApprovalDecision, ApprovalRequest } from '../chat/safe-mode-context'\nimport type { SessionMeta, Settings, StreamEvent } from '../chat/types'\nimport type { Session } from '../session'\nimport type { MetaSegment } from './components'\nimport { Buffer } from 'node:buffer'\nimport { readFileSync, statSync } from 'node:fs'\nimport { basename } from 'node:path'\nimport { decodePasteBytes, defaultTextareaKeyBindings, stripAnsiSequences } from '@opentui/core'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { detectAuth } from '../chat/auth'\nimport { useCompletion } from '../chat/completion'\nimport { useConfig } from '../chat/config-context'\nimport { setProviderCredential } from '../chat/credentials'\nimport { ageString, compactPath } from '../chat/format'\nimport { clipHintsToWidth, EMPTY_HINTS, hintsLength } from '../chat/hints'\nimport { formatBindingForDisplay } from '../chat/keybindings'\nimport { oauthUsesManualCodePaste, runOAuthLogin, supportsOAuth } from '../chat/oauth'\nimport { fetchOAuthRedirect } from '../chat/oauth-redirect'\nimport { suggestSafelistEntry } from '../chat/safe-mode'\nimport { useSettings } from '../chat/settings-context'\nimport { resolveChipColor } from '../chat/theme'\nimport { useColors, useSelectStyle, useSurfaces } from '../chat/theme-context'\nimport { errorMessage } from '../errors'\nimport { CompletionPopup } from './completion-popup'\nimport { renderHintSpans, Spinner, TitleOverlay, Transcript } from './components'\nimport { FileEditApprovalModal, isFileEditTool } from './file-edit-approval-modal'\nimport { InteractionBlock } from './interaction-block'\nimport { useModal, useModalAwareFocus } from './modal'\nimport { OAuthAuthBlock } from './oauth-auth-block'\nimport { useChipHighlights, useChipStyle } from './theme'\nimport { TodoIndicator } from './todo-indicator'\n\n/**\n * Prompt-level attachment captured by the textarea's paste pipeline.\n * Lives only in the TUI layer — converted to {@link PromptPart}s at\n * `agent.run()` time. Internal buffer-based shape is more convenient\n * than the wire format (which is base64 for binary and raw text for\n * text/plain).\n */\nexport interface Attachment {\n name: string\n content: Buffer\n mediaType: string\n}\n\n/**\n * Build a key-binding set for the prompt textarea / API-key input. Strips the\n * default `return` action and reinstalls it with our preferred meaning, so the\n * binding wins regardless of modifier state. Pass `allowShiftReturnNewline`\n * to enable `shift+enter` → newline (multi-line input).\n *\n * Also overrides `ctrl+a`: OpenTUI's default is the emacs-style `line-home`,\n * but every modern editor + browser binds it to \"select all\". The previous\n * behavior is still reachable via `home` / `ctrl+e` (line ends). Net effect:\n * users can press ctrl+a then any printable key (or backspace) to clear the\n * prompt the way they expect.\n */\nfunction makeSubmitBindings(allowShiftReturnNewline: boolean) {\n const base = defaultTextareaKeyBindings.filter(\n b => b.name !== 'return' && !(b.name === 'a' && b.ctrl && !b.shift && !b.meta),\n )\n const overrides = [\n { name: 'a', ctrl: true, action: 'select-all' as const },\n { name: 'return', action: 'submit' as const },\n ]\n return allowShiftReturnNewline\n ? [...base, ...overrides, { name: 'return', shift: true, action: 'newline' as const }]\n : [...base, ...overrides]\n}\n\nconst TEXTAREA_BINDINGS = makeSubmitBindings(true)\nconst API_KEY_INPUT_BINDINGS = makeSubmitBindings(false)\n\n/**\n * Look up a `{ key }` item by the value of a `<select>` option. Used by every\n * screen that mixes keyed-entry rows with sentinel \"+ new\" / \"← back\" rows —\n * sentinel handling stays explicit at the call site, this helper just trims\n * the boilerplate `.find(i => i.key === ...)` typing.\n */\nfunction findByKey<T extends { key: string }>(items: readonly T[], value: unknown): T | undefined {\n return typeof value === 'string' ? items.find(i => i.key === value) : undefined\n}\n\n// ---------------------------------------------------------------------------\n// AuthScreen — first-run provider picker.\n// ---------------------------------------------------------------------------\n\n/**\n * Sentinel value used by the picker's \"+ add / re-configure\" option. Lives\n * outside the provider key namespace (key strings are at least one char, no\n * leading `__`) so we can't collide with a real registry entry.\n */\nconst WIZARD_OPTION_VALUE = '__wizard__'\n\nexport function AuthScreen({ onPick }: { onPick: (p: ProviderAuth) => void }) {\n const config = useConfig()\n const { providers: registry } = config\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n\n // `providers` is state, not a memo, so the wizard can imperatively\n // refresh it after writing credentials without us reaching for `useMemo`\n // dep tricks. The effect below seeds it on mount + whenever the registry\n // changes; `refresh` does the wizard-triggered re-detect.\n //\n // Credentials always live under the USER dir — never under a project's\n // `.{prefix}/` (which can be checked in). `paths.userDir` makes that\n // explicit; `paths.dir` would silently land creds in the project dir\n // when project-db mode is on.\n const [providers, setProviders] = useState<ProviderAuth[]>([])\n const refresh = useCallback(\n () => setProviders(detectAuth(config.paths.userDir, registry)),\n [config.paths.userDir, registry],\n )\n useEffect(() => { refresh() }, [refresh])\n\n // Explicit \"show the wizard\" flag — set when the user picks the\n // `+ add or re-configure` option, cleared when they save or cancel back\n // to the picker. Without it, the picker would be sticky once any provider\n // had credentials.\n const [forceWizard, setForceWizard] = useState(false)\n\n const available = useMemo(() => providers.filter(p => p.available), [providers])\n\n const onWizardDone = useCallback(() => {\n setForceWizard(false)\n refresh()\n }, [refresh])\n\n if (available.length === 0 || forceWizard) {\n // \"← back\" only makes sense when we got here from the picker (forceWizard)\n // AND there's still something to go back to. The `&&` guards against the\n // edge case where credentials disappear between renders.\n const canCancel = forceWizard && available.length > 0\n return (\n <SetupWizard\n registry={registry}\n dataDir={config.paths.userDir}\n onConfigured={onWizardDone}\n onCancel={canCancel ? () => setForceWizard(false) : undefined}\n />\n )\n }\n\n const options = [\n ...available.map(p => ({\n name: p.label,\n description: p.methods.map(m => m.detail).join(' · '),\n value: p.key,\n })),\n {\n name: '+ add or re-configure a provider',\n description: 'launch the setup wizard',\n value: WIZARD_OPTION_VALUE,\n },\n ]\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.border,\n padding: 1,\n flexDirection: 'column',\n flexGrow: 1,\n }}\n >\n <select\n {...SELECT_THEME}\n focused={focused}\n options={options}\n wrapSelection\n onSelect={(_idx, option) => {\n if (!option)\n return\n if (option.value === WIZARD_OPTION_VALUE) {\n setForceWizard(true)\n return\n }\n const provider = findByKey(available, option.value)\n if (provider)\n onPick(provider)\n }}\n style={{ flexGrow: 1 }}\n />\n </box>\n <TitleOverlay title=\"pick a provider\" />\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SetupWizard — first-run credential setup. Three steps:\n// 1. Pick provider\n// 2. Pick auth method (apikey · OAuth where the descriptor supports it)\n// 3a. Enter API key → save to credentials.json → refresh AuthScreen\n// 3b. Open browser → OAuth callback server → save tokens → refresh\n//\n// Everything is driven by the host's {@link ProviderDescriptor}s — no\n// hardcoded provider metadata lives here.\n// ---------------------------------------------------------------------------\n\ntype WizardStep\n = | { kind: 'pick-provider' }\n | { kind: 'pick-method', descriptor: ProviderDescriptor }\n | { kind: 'enter-apikey', descriptor: ProviderDescriptor }\n | { kind: 'oauth-running', descriptor: ProviderDescriptor }\n\nfunction SetupWizard({\n registry,\n dataDir,\n onConfigured,\n onCancel,\n}: {\n registry: Readonly<Record<string, ProviderDescriptor>>\n dataDir: string\n onConfigured: () => void\n /**\n * Available only when the wizard was opened from the picker\n * (\"+ add or re-configure\"). On first launch (no providers yet) it's\n * undefined and the wizard has no cancel affordance — the user must set\n * up a provider to proceed.\n */\n onCancel?: () => void\n}) {\n const [step, setStep] = useState<WizardStep>({ kind: 'pick-provider' })\n const [error, setError] = useState<string | null>(null)\n\n const descriptors = useMemo(() => Object.values(registry), [registry])\n\n const onPickProvider = useCallback((descriptor: ProviderDescriptor) => {\n setError(null)\n setStep({ kind: 'pick-method', descriptor })\n }, [])\n\n const onPickMethod = useCallback((descriptor: ProviderDescriptor, method: 'apikey' | 'oauth') => {\n setError(null)\n if (method === 'apikey') {\n setStep({ kind: 'enter-apikey', descriptor })\n }\n else {\n setStep({ kind: 'oauth-running', descriptor })\n }\n }, [])\n\n const onApiKeySubmit = useCallback((descriptor: ProviderDescriptor, value: string) => {\n const trimmed = value.trim()\n if (!trimmed) {\n setError('API key cannot be empty.')\n return\n }\n try {\n setProviderCredential(dataDir, descriptor, { kind: 'apikey', value: trimmed })\n // Also expose via env so providers created later in this process pick it\n // up. Skip for descriptors with no env-var convention (custom providers\n // that resolve credentials some other way).\n if (descriptor.envKey)\n process.env[descriptor.envKey] = trimmed\n onConfigured()\n }\n catch (err) {\n setError(errorMessage(err))\n }\n }, [dataDir, onConfigured])\n\n // `OAuthRunningStep` lists `onError` in its main effect's dep array\n // (alongside descriptor + dataDir + onSuccess). Inlining a fresh arrow\n // here on every render would re-run the effect on any unrelated state\n // change (e.g. the wizard transitioning steps), tearing down the\n // in-flight `runOAuthLogin` and re-spawning a browser tab + callback\n // server. The memoized identity keeps the OAuth flow alive once it's\n // running. The body still references the latest `step.descriptor` via\n // the closure-stable setter call.\n const onOAuthError = useCallback((msg: string) => {\n setError(msg)\n setStep(prev => prev.kind === 'oauth-running'\n ? { kind: 'pick-method', descriptor: prev.descriptor }\n : prev)\n }, [])\n\n if (descriptors.length === 0)\n return <EmptyRegistryNotice />\n\n if (step.kind === 'pick-provider') {\n return (\n <PickProviderStep\n descriptors={descriptors}\n error={error}\n onPick={onPickProvider}\n onCancel={onCancel}\n />\n )\n }\n\n if (step.kind === 'pick-method')\n return <PickMethodStep descriptor={step.descriptor} error={error} onPick={onPickMethod} />\n\n if (step.kind === 'enter-apikey')\n return <EnterApiKeyStep descriptor={step.descriptor} error={error} onSubmit={onApiKeySubmit} />\n\n return (\n <OAuthRunningStep\n descriptor={step.descriptor}\n dataDir={dataDir}\n onSuccess={onConfigured}\n onError={onOAuthError}\n />\n )\n}\n\n/**\n * Shared wrapper for every wizard step — same border + padding + flex\n * layout with a customizable title and accent color. Footnote slot at the\n * bottom for an error banner.\n *\n * Title rides `accent` when present (so the empty-registry notice's red\n * title matches its red border) and falls back to `COLOR.brand`. The\n * outer flex column hosts both the bordered content box and the\n * `TitleOverlay`, which must be its sibling (not a child) so the\n * border-row paint isn't clipped by the bordered box's scissor rect.\n */\nfunction WizardPanel({\n title,\n accent,\n error,\n children,\n}: {\n title: string\n accent?: string\n error?: string | null\n children: ReactNode\n}) {\n const COLOR = useColors()\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n <box\n style={{\n border: true,\n borderColor: accent ?? COLOR.border,\n padding: 1,\n gap: 1,\n flexDirection: 'column',\n flexGrow: 1,\n }}\n >\n {children}\n {error && <text fg={COLOR.error}>{error}</text>}\n </box>\n <TitleOverlay title={title.trim()} titleColor={accent} />\n </box>\n )\n}\n\n/** \"esc to exit\" footer hint shared by every wizard step that doesn't offer a \"← back\" affordance. */\nfunction WizardEscHint() {\n const COLOR = useColors()\n return <text fg={COLOR.dim}>esc to exit</text>\n}\n\nfunction EmptyRegistryNotice() {\n const COLOR = useColors()\n return (\n <WizardPanel title=\"no providers configured\" accent={COLOR.error}>\n <text fg={COLOR.error}>This TUI has no providers registered.</text>\n <text fg={COLOR.dim}>\n Pass providers via\n <span fg={COLOR.model}>{' runTui({ providers }) '}</span>\n or use the built-ins via\n <span fg={COLOR.model}>{' BUILTIN_PROVIDERS '}</span>\n .\n </text>\n </WizardPanel>\n )\n}\n\n/** Sentinel option value used for the wizard's \"← back to picker\" entry. */\nconst WIZARD_BACK_VALUE = '__back__'\n\nfunction PickProviderStep({\n descriptors,\n error,\n onPick,\n onCancel,\n}: {\n descriptors: readonly ProviderDescriptor[]\n error: string | null\n onPick: (descriptor: ProviderDescriptor) => void\n /** When set, adds a \"← back\" option that calls this to bail out without saving. */\n onCancel?: () => void\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n const options = [\n ...descriptors.map((d) => {\n const methods: string[] = supportsOAuth(d) ? ['API key', 'OAuth'] : ['API key']\n return { name: d.label, description: methods.join(' · '), value: d.key }\n }),\n ...(onCancel\n ? [{ name: '← back', description: 'return to the provider list', value: WIZARD_BACK_VALUE }]\n : []),\n ]\n\n // Different copy + title for the two entry paths: first-launch users need\n // the \"credentials live here\" hint; re-config users already know how the\n // TUI works and just want to pick a provider.\n const title = onCancel\n ? 'add or re-configure a provider'\n : 'welcome to zidane · pick a provider'\n\n return (\n <WizardPanel title={title} error={error}>\n {!onCancel && (\n <text fg={COLOR.dim}>\n No provider credentials yet. Pick a provider to configure — keys are stored in\n <span fg={COLOR.model}>{' ~/.zidane/credentials.json '}</span>\n (owner-only).\n </text>\n )}\n <select\n {...SELECT_THEME}\n focused={focused}\n options={options}\n wrapSelection\n onSelect={(_idx, option) => {\n if (!option)\n return\n if (option.value === WIZARD_BACK_VALUE) {\n onCancel?.()\n return\n }\n const descriptor = findByKey(descriptors, option.value)\n if (descriptor)\n onPick(descriptor)\n }}\n style={{ flexGrow: 1 }}\n />\n </WizardPanel>\n )\n}\n\nfunction PickMethodStep({\n descriptor,\n error,\n onPick,\n}: {\n descriptor: ProviderDescriptor\n error: string | null\n onPick: (descriptor: ProviderDescriptor, method: 'apikey' | 'oauth') => void\n}) {\n const focused = useModalAwareFocus()\n const SELECT_THEME = useSelectStyle()\n\n const options = useMemo(() => {\n interface MethodOption { name: string, description: string, value: 'apikey' | 'oauth' }\n const items: MethodOption[] = [\n { name: 'API key', description: `paste your ${descriptor.label} API key`, value: 'apikey' },\n ]\n if (supportsOAuth(descriptor)) {\n // OAuth hint comes from the descriptor — built-in `anthropicDescriptor`\n // sets it to \"Claude Pro/Max subscription\". Hosts adding OAuth on their\n // own providers set their own hint (or omit it).\n const hint = descriptor.oauthHint ? ` (${descriptor.oauthHint})` : ''\n items.push({\n name: 'OAuth',\n description: `browser-based sign-in${hint}`,\n value: 'oauth',\n })\n }\n return items\n }, [descriptor])\n\n return (\n <WizardPanel title={`configure ${descriptor.label} — pick auth method`} error={error}>\n <WizardEscHint />\n <select\n {...SELECT_THEME}\n focused={focused}\n options={options}\n wrapSelection\n onSelect={(_idx, option) => {\n if (option)\n onPick(descriptor, option.value)\n }}\n style={{ flexGrow: 1 }}\n />\n </WizardPanel>\n )\n}\n\nfunction EnterApiKeyStep({\n descriptor,\n error,\n onSubmit,\n}: {\n descriptor: ProviderDescriptor\n error: string | null\n onSubmit: (descriptor: ProviderDescriptor, value: string) => void\n}) {\n const focused = useModalAwareFocus()\n const inputRef = useRef<InputRenderable | null>(null)\n const COLOR = useColors()\n\n const submit = useCallback(() => {\n const value = inputRef.current?.value ?? ''\n onSubmit(descriptor, value)\n }, [descriptor, onSubmit])\n\n return (\n <WizardPanel title={`configure ${descriptor.label} — paste API key`} error={error}>\n <text fg={COLOR.dim}>\n Paste your\n {` ${descriptor.label} `}\n API key and press\n <span fg={COLOR.model}> enter </span>\n to save. Esc to exit.\n </text>\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <input\n ref={inputRef}\n focused={focused}\n keyBindings={API_KEY_INPUT_BINDINGS}\n placeholder={descriptor.apiKeyPlaceholder ?? 'API key…'}\n onSubmit={submit}\n style={{ flexGrow: 1 }}\n />\n </box>\n </WizardPanel>\n )\n}\n\n/**\n * An in-flight pi-ai `onPrompt(prompt)` deferred. We resolve it when the\n * user submits the input, or reject it on cancel/unmount so the awaiting\n * pi-ai promise unwinds and `runOAuthLogin` rejects cleanly (the screen's\n * abort path already routes that into `onError`).\n */\ninterface PendingPrompt {\n message: string\n placeholder?: string\n allowEmpty?: boolean\n resolve: (value: string) => void\n reject: (err: Error) => void\n}\n\nfunction OAuthRunningStep({\n descriptor,\n dataDir,\n onSuccess,\n onError,\n}: {\n descriptor: ProviderDescriptor\n dataDir: string\n onSuccess: () => void\n onError: (msg: string) => void\n}) {\n const usesManualPaste = oauthUsesManualCodePaste(descriptor)\n const [url, setUrl] = useState<string | null>(null)\n const [status, setStatus] = useState(\n usesManualPaste\n ? 'opening browser…'\n : 'starting browser…',\n )\n const [pending, setPending] = useState<PendingPrompt | null>(null)\n const [pasteHint, setPasteHint] = useState<{ text: string, tone: 'dim' | 'error' | 'accent' } | null>(null)\n const focused = useModalAwareFocus()\n const inputRef = useRef<InputRenderable | null>(null)\n\n // Keep the latest `pending` reachable from the unmount cleanup without\n // re-binding the OAuth effect (which would tear down + restart the flow\n // on every keystroke that changes `pending`).\n const pendingRef = useRef<PendingPrompt | null>(null)\n pendingRef.current = pending\n\n useEffect(() => {\n const ac = new AbortController()\n let cancelled = false\n\n void (async () => {\n try {\n const creds = await runOAuthLogin(descriptor, {\n onUrl: (loginUrl) => {\n if (cancelled)\n return\n setUrl(loginUrl)\n setStatus(\n usesManualPaste\n ? 'complete the login in your browser, then paste the code below'\n : 'waiting for browser callback…',\n )\n },\n onPrompt: prompt => new Promise<string>((resolve, reject) => {\n if (cancelled) {\n reject(new Error('OAuth flow cancelled'))\n return\n }\n // Latest prompt wins — pi-ai shouldn't issue overlapping\n // prompts in practice, but if it did we'd want the newest\n // one visible and the older one rejected so its awaiter\n // unwinds rather than hanging.\n const prev = pendingRef.current\n prev?.reject(new Error('superseded by a newer OAuth prompt'))\n setPending({\n message: prompt.message,\n placeholder: prompt.placeholder,\n allowEmpty: prompt.allowEmpty,\n resolve,\n reject,\n })\n }),\n onProgress: (message) => {\n if (!cancelled)\n setStatus(message)\n },\n signal: ac.signal,\n })\n if (cancelled)\n return\n // `creds` carries `{ access, refresh, expires, ...extras }`. Spread\n // verbatim — the storage shape only adds our `kind` tag on top.\n setProviderCredential(dataDir, descriptor, { kind: 'oauth', ...creds })\n onSuccess()\n }\n catch (err) {\n if (cancelled)\n return\n onError(errorMessage(err))\n }\n })()\n\n return () => {\n cancelled = true\n // Unwind any awaiting `onPrompt` so the pi-ai promise rejects\n // instead of hanging the AbortController.\n pendingRef.current?.reject(new Error('OAuth flow cancelled'))\n pendingRef.current = null\n ac.abort()\n }\n }, [descriptor, dataDir, onSuccess, onError, usesManualPaste])\n\n // One input, two callers. If pi-ai's `onPrompt` is awaiting a value\n // (paste-the-code flows like Anthropic Claude Pro/Max), feed it\n // verbatim — pi-ai's `parseAuthorizationInput` already accepts both\n // bare codes and full redirect URLs. Otherwise (the normal happy path:\n // pi-ai's loopback server is still waiting), fire the pasted URL at\n // OUR own loopback — the request hits pi-ai's server handler, state\n // matches, `waitForCode` resolves, OAuth promise completes. This is\n // the SSH escape hatch: even when the browser sits on the user's local\n // box and can't reach the remote callback server, copy-pasting the\n // redirect URL into the TUI works.\n const submitInput = useCallback(() => {\n const value = inputRef.current?.value?.trim() ?? ''\n const current = pendingRef.current\n if (current) {\n if (!value && !current.allowEmpty)\n return\n pendingRef.current = null\n setPending(null)\n setStatus('exchanging code…')\n current.resolve(value)\n return\n }\n if (!value)\n return\n setPasteHint({ text: 'submitting redirect URL…', tone: 'dim' })\n void (async () => {\n try {\n const result = await fetchOAuthRedirect(value)\n if (result.status >= 200 && result.status < 300) {\n setPasteHint({ text: 'redirect accepted — exchanging code…', tone: 'accent' })\n setStatus('exchanging code…')\n }\n else {\n setPasteHint({\n text: `callback server rejected the URL (${result.status}${result.message ? `: ${result.message}` : ''}). Try again or restart the flow.`,\n tone: 'error',\n })\n }\n }\n catch (err) {\n setPasteHint({ text: errorMessage(err), tone: 'error' })\n }\n })()\n }, [])\n\n return (\n <WizardPanel title={`configure ${descriptor.label} — OAuth`}>\n <WizardEscHint />\n {pending\n ? <Spinner label={pending.message} />\n : <Spinner label={status} />}\n {url && (\n <OAuthAuthBlock\n authUrl={url}\n // Wizard panel = border (2) + padding (4) ≈ 6 cols of chrome.\n maxLineWidth={200}\n chromeWidth={6}\n paste={{\n inputRef,\n focused,\n onSubmit: submitInput,\n placeholder: pending?.placeholder ?? 'paste redirect URL (or code) and press enter…',\n hint: pasteHint ?? undefined,\n }}\n />\n )}\n </WizardPanel>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SessionsScreen — list of sessions with a synthetic \"+ new\" entry on top.\n//\n// Renders rows manually (scrollbox + per-row `<text>` spans) instead of\n// using OpenTUI's `<select>` because the row's secondary line carries\n// multi-colored stats — warn-tinted numbers + dim labels, matching the\n// chat screen's bottom-bar palette. The native select only paints its\n// description in a single fg color, so it couldn't carry that look.\n//\n// Owns its own keyboard handling (↑↓ + return + page-up/down + home/end)\n// so the affordance set matches what users expect from the picker\n// without depending on the select renderable.\n//\n// Render is fully controlled against `focusedSessionId` — the cursor\n// follows the row's IDENTITY rather than a numerical slot, so a\n// `generate title` that bumps `updatedAt` (and reorders the list) leaves\n// the cursor on the same row visually.\n// ---------------------------------------------------------------------------\n\n/**\n * Sentinel row id for the synthetic \"+ new\" row at the top of the\n * sessions list. Exposed so the AppShell can pass it through\n * `focusedSessionId` (or rather: the focused ROW id, which is either a\n * real session id or this sentinel) and gate global shortcuts that\n * only make sense on real sessions — `ctrl+x` is the canonical example.\n *\n * Stays a plain string (rather than a discriminated union) so the\n * parent's focus state remains a flat `string | null` — `null` means\n * \"no preference yet, please default to the first session\"; this\n * sentinel means \"+ new is the active row\".\n */\nexport const NEW_SESSION_ROW_ID = '__new__'\n\n/** Guard for the `ctrl+x` handler: only a real session id should open the details modal. */\nexport function isSessionRowId(rowId: string | null): rowId is string {\n return rowId !== null && rowId !== NEW_SESSION_ROW_ID\n}\n\n/** Page-up / page-down jump size — half a typical visible window. */\nconst PAGE_JUMP = 6\n\ninterface SessionRowItem {\n kind: 'new' | 'session'\n /** `NEW_ROW_ID` for the \"+ new\" row, else the session id. */\n rowId: string\n /** Present only on session rows — used by the renderer for the stats line. */\n meta: SessionMeta | null\n}\n\nexport function SessionsScreen({\n sessions,\n currentId,\n focusedSessionId,\n onPick,\n onCreate,\n onFocusChange,\n showAllProjects = false,\n currentProjectRoot,\n}: {\n sessions: SessionMeta[]\n /**\n * Identity of the row the parent considers focused. Drives the row\n * cursor so the highlight follows the SESSION (not its slot) when the\n * list reorders. `null` lands the cursor on the first session row, or\n * on \"+ new\" if there are no sessions yet.\n */\n focusedSessionId: string | null\n currentId: string | null\n onPick: (id: string) => void\n onCreate: () => void\n /**\n * Notified as the user navigates between rows. Receives the session id\n * of the focused row, or `null` when \"+ new\" is focused (no session to\n * act on). Parents use this to wire `ctrl+x` against the live focus.\n */\n onFocusChange?: (id: string | null) => void\n /**\n * When `true`, render each session's project label under the title.\n * Off by default — when the list is already filtered to one project\n * the label would just repeat that project on every row.\n */\n showAllProjects?: boolean\n /**\n * Current project root, used to label \"this project\" on rows that\n * belong to it when `showAllProjects` is on. Untagged (legacy)\n * sessions render as \"untagged\".\n */\n currentProjectRoot?: string\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const inputRef = useRef<InputRenderable | null>(null)\n\n // Free-text filter typed into the top input. Lowercased terms are\n // matched against a per-session search corpus (title + project basename\n // + full project path) — splitting on whitespace lets the user combine\n // narrowing terms (`agent docs`) without having to type them in order.\n const [query, setQuery] = useState('')\n const filteredSessions = useMemo(() => {\n const trimmed = query.trim().toLowerCase()\n if (!trimmed)\n return sessions\n const terms = trimmed.split(/\\s+/)\n return sessions.filter((meta) => {\n const projectBasename = meta.projectRoot?.split('/').pop() ?? ''\n const corpus = `${meta.title} ${projectBasename} ${meta.projectRoot ?? ''}`.toLowerCase()\n return terms.every(t => corpus.includes(t))\n })\n }, [sessions, query])\n\n const rows = useMemo<readonly SessionRowItem[]>(() => [\n { kind: 'new', rowId: NEW_SESSION_ROW_ID, meta: null },\n ...filteredSessions.map<SessionRowItem>(meta => ({ kind: 'session', rowId: meta.id, meta })),\n ], [filteredSessions])\n\n // Imperative focus on mount + whenever the parent grants focus back\n // (e.g. closing a modal). Mirrors the model picker's dance — declaring\n // `focused` alone isn't enough when another input previously owned focus.\n useEffect(() => {\n if (focused)\n inputRef.current?.focus()\n }, [focused])\n\n // Derive the cursor index from the focused row id. Re-runs every\n // time `rows` mutates — when a session reorders (`generate title`\n // bumps `updatedAt`) the cursor follows its identity to the new slot.\n //\n // Meaning of `focusedSessionId`:\n // - `null` → no preference yet; default to first session.\n // - `NEW_SESSION_ROW_ID` → \"+ new\" row is intentionally focused.\n // - any other string → that session's row.\n //\n // Fallback ladder when no id is focused or the focused session has\n // disappeared from the list:\n // - First session row when one exists (index 1; \"+ new\" sits at 0).\n // - Index 0 (\"+ new\") when no sessions exist.\n const cursorIndex = useMemo(() => {\n if (focusedSessionId === NEW_SESSION_ROW_ID)\n return 0\n if (focusedSessionId) {\n const idx = rows.findIndex(r => r.kind === 'session' && r.rowId === focusedSessionId)\n if (idx !== -1)\n return idx\n }\n return rows.length > 1 ? 1 : 0\n }, [rows, focusedSessionId])\n\n // Reconcile the parent's focus state when the rows list mutates\n // independently of user navigation: first mount (no id yet) or the\n // focused session disappearing (delete). Pushes the id at the new\n // `cursorIndex` so the parent's `focusedSessionId` matches what the\n // cursor is visually pointing at.\n useEffect(() => {\n if (!onFocusChange)\n return\n if (focusedSessionId === NEW_SESSION_ROW_ID)\n return // intentional \"+ new\" focus, leave it\n if (focusedSessionId && rows.some(r => r.kind === 'session' && r.rowId === focusedSessionId))\n return // valid session focus, leave it\n const fallback = rows[cursorIndex]\n onFocusChange(fallback?.rowId ?? null)\n }, [rows, focusedSessionId, cursorIndex, onFocusChange])\n\n // Push the row id at a new cursor position back to the parent. Used\n // by the keyboard handler to keep `focusedSessionId` in sync with\n // the visible cursor — including the \"+ new\" sentinel, so the user\n // can actually navigate up to it (previously emitted `null`, which\n // the cursor-derivation logic treats as \"default to first session\"\n // and immediately snaps the cursor back).\n const moveCursor = useCallback((nextIndex: number) => {\n if (!onFocusChange || rows.length === 0)\n return\n const clamped = ((nextIndex % rows.length) + rows.length) % rows.length // wrap on edges\n const row = rows[clamped]\n onFocusChange(row?.rowId ?? null)\n }, [rows, onFocusChange])\n\n const commitCurrent = useCallback(() => {\n const row = rows[cursorIndex]\n if (!row)\n return\n if (row.kind === 'new')\n onCreate()\n else\n onPick(row.rowId)\n }, [rows, cursorIndex, onCreate, onPick])\n // Note: keyboard handler below catches `↵` and routes to this. The\n // `kind === 'new'` branch fires `onCreate` regardless of whether\n // `focusedSessionId` is `NEW_SESSION_ROW_ID` or `null` — both land\n // the cursor at index 0 in `cursorIndex`'s derivation.\n\n useKeyboard((key) => {\n // Gated on `focused` so a modal opened on top steals input cleanly\n // (mirrors what `useModalAwareFocus` does for the textarea/select).\n //\n // Runs alongside the search `<input>` — opentui's `useKeyboard` is a\n // global listener, not tied to a focused renderable, so the same key\n // can reach both. Up/down/return are no-ops or suppressed in the\n // input, and home/end are intentionally NOT handled here so they\n // edit the search text instead.\n if (!focused)\n return\n if (key.name === 'up') {\n moveCursor(cursorIndex - 1)\n return\n }\n if (key.name === 'down') {\n moveCursor(cursorIndex + 1)\n return\n }\n if (key.name === 'pageup') {\n moveCursor(cursorIndex - PAGE_JUMP)\n return\n }\n if (key.name === 'pagedown') {\n moveCursor(cursorIndex + PAGE_JUMP)\n return\n }\n if (key.name === 'return') {\n commitCurrent()\n }\n })\n\n // Title meta — laid out RIGHT-aligned on the top border. Composed\n // (left to right) of: cwd · [all projects ·] {count}.\n //\n // Responsive layout:\n // - Wide: full path · all projects · 3 / 12 sessions\n // - Narrower: cwd left-truncated by `compactPath` to fit\n // - Narrow: drop the cwd segment, keep the count\n // - Very narrow: `TitleOverlay` drops the meta entirely\n //\n // The cwd is shown without a \"cwd \" label — paths read as paths in\n // a title slot, and the chat screen uses the same convention for\n // its meta values (`5 user messages · 12 turns · ctrl+x`).\n const { width: termWidth } = useTerminalDimensions()\n const titleMeta = useMemo<readonly MetaSegment[]>(() => {\n const filtering = query.trim().length > 0\n const countSegs: MetaSegment[] = sessions.length === 0\n ? [{ text: 'no sessions yet', color: COLOR.mute }]\n : filtering\n ? [\n { text: String(filteredSessions.length), color: COLOR.warn },\n { text: ' / ' },\n { text: String(sessions.length), color: COLOR.warn },\n { text: ` session${sessions.length === 1 ? '' : 's'}` },\n ]\n : [\n { text: String(sessions.length), color: COLOR.warn },\n { text: ` session${sessions.length === 1 ? '' : 's'}` },\n ]\n\n if (!currentProjectRoot)\n return countSegs\n\n // Compute the cwd budget that fits alongside the count inside\n // `TitleOverlay`'s remaining meta width. The overlay reserves\n // 6 cells (2× wrap + 1× gap) + the title length out of `termWidth - 4`\n // (terminal width minus the screen wrapper's 1-cell padding on each\n // side and the bordered box's two corners). The path takes whatever\n // is left after the count and separators.\n const OVERLAY_RESERVED = 6 // mirrors TITLE_OVERLAY_{WRAP,META_WRAP,GAP}\n const TITLE_LEN = 'sessions'.length\n const SEP_LEN = ' · '.length\n const allProjectsLen = showAllProjects ? 'all projects'.length + SEP_LEN : 0\n const countLen = countSegs.reduce((sum, s) => sum + s.text.length, 0)\n const metaBudget = Math.max(0, termWidth - 4 - TITLE_LEN - OVERLAY_RESERVED)\n const cwdBudget = metaBudget - countLen - SEP_LEN - allProjectsLen\n\n // Below ~6 cells the path is unreadable (basically just `…/x`),\n // so drop it entirely and let the count keep its slot.\n const MIN_CWD = 6\n if (cwdBudget < MIN_CWD)\n return countSegs\n\n const cwd = compactPath(currentProjectRoot, cwdBudget)\n const segs: MetaSegment[] = [{ text: cwd, color: COLOR.model }]\n if (showAllProjects) {\n segs.push(\n { text: ' · ', color: COLOR.mute },\n { text: 'all projects', color: COLOR.accent },\n )\n }\n segs.push({ text: ' · ', color: COLOR.mute }, ...countSegs)\n return segs\n }, [\n sessions.length,\n filteredSessions.length,\n query,\n currentProjectRoot,\n showAllProjects,\n termWidth,\n COLOR,\n ])\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.border,\n padding: 1,\n flexDirection: 'column',\n flexGrow: 1,\n }}\n >\n {/*\n Search input — owns text focus so the user can filter as they\n type. Runs alongside the `useKeyboard` handler above; that\n handler drives ↑/↓/pgup/pgdn/↵ navigation regardless of who\n currently has focus, so the user can search and pick without\n ever leaving the keyboard. `onSubmit` is suppressed so ↵\n doesn't fire twice (once via input, once via useKeyboard).\n */}\n <box\n style={{\n border: true,\n borderColor: COLOR.border,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n flexShrink: 0,\n marginBottom: 1,\n }}\n >\n <input\n ref={inputRef}\n focused={focused}\n placeholder=\"filter sessions — title or project…\"\n onInput={setQuery}\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n {/*\n Scrollbox owns the visible window when the list outgrows the\n viewport. `focusable={false}` keeps it from stealing keyboard\n focus — our local `useKeyboard` drives navigation against the\n parent's `focusedSessionId`.\n */}\n <scrollbox\n focusable={false}\n style={{ flexGrow: 1 }}\n >\n {rows.map((row, idx) => (\n <SessionRow\n key={row.rowId}\n row={row}\n focused={idx === cursorIndex && focused}\n isCurrent={row.kind === 'session' && row.rowId === currentId}\n showProject={showAllProjects}\n currentProjectRoot={currentProjectRoot}\n />\n ))}\n {filteredSessions.length === 0 && query.trim().length > 0 && (\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' no sessions match '}</span>\n <span fg={COLOR.warn}>{query.trim()}</span>\n </text>\n )}\n </scrollbox>\n </box>\n <TitleOverlay title=\"sessions\" meta={titleMeta} />\n </box>\n )\n}\n\n/**\n * Two-line row for `SessionsScreen`. Top line is the title with the\n * focus + active markers; bottom is the stats summary (turns / user /\n * runs / age) in the bottom-bar's warn-number + dim-label palette.\n *\n * Alignment contract: the stats line indents by `STATS_INDENT` cells —\n * exactly the width of the focus + current markers above — so the first\n * stat (the turn count) sits flush under the title's first letter.\n */\nfunction SessionRow({\n row,\n focused,\n isCurrent,\n showProject = false,\n currentProjectRoot,\n}: {\n row: SessionRowItem\n focused: boolean\n isCurrent: boolean\n /** Render the project-label line under the stats row. */\n showProject?: boolean\n /** Resolved current project root — used to render rows that belong to it as \"this project\". */\n currentProjectRoot?: string\n}) {\n const COLOR = useColors()\n\n // 2 cells (focus mark) + 2 cells (current mark) = 4. Keeping both\n // markers fixed-width means the title always starts at column 4 and\n // the stats line can mirror that with a single string of 4 spaces.\n const STATS_INDENT = ' '\n\n const focusMark = focused ? '▶ ' : ' '\n const focusColor = focused ? COLOR.brand : COLOR.mute\n const titleColor = focused ? COLOR.brand : COLOR.dim\n\n if (row.kind === 'new') {\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, alignSelf: 'stretch' }}>\n <text wrapMode=\"none\">\n <span fg={focusColor}>{focusMark}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={titleColor}>+ new session</span>\n </text>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{STATS_INDENT}</span>\n <span fg={COLOR.mute}>start fresh</span>\n </text>\n </box>\n )\n }\n\n const meta = row.meta!\n const currentMark = isCurrent ? '● ' : ' '\n const currentColor = isCurrent ? COLOR.accent : COLOR.mute\n\n // Stats palette: numbers in `warn` (anchor the reader's eye), labels +\n // separators + age in `mute` so the secondary line recedes behind the\n // title. Previously labels rode `COLOR.dim` — the same color as the\n // non-focused title, which made the two lines compete visually.\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0, alignSelf: 'stretch' }}>\n <text wrapMode=\"none\">\n <span fg={focusColor}>{focusMark}</span>\n <span fg={currentColor}>{currentMark}</span>\n <span fg={titleColor}>{meta.title}</span>\n </text>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{STATS_INDENT}</span>\n <span fg={COLOR.warn}>{meta.turnCount}</span>\n <span fg={COLOR.mute}>{` turn${meta.turnCount === 1 ? '' : 's'} · `}</span>\n <span fg={COLOR.warn}>{meta.userMessageCount}</span>\n <span fg={COLOR.mute}>{` user · `}</span>\n <span fg={COLOR.warn}>{meta.runCount}</span>\n <span fg={COLOR.mute}>{` run${meta.runCount === 1 ? '' : 's'} · `}</span>\n <span fg={COLOR.mute}>{ageString(meta.updatedAt)}</span>\n </text>\n {showProject && (\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{STATS_INDENT}</span>\n {renderProjectLabel(meta.projectRoot, currentProjectRoot, COLOR)}\n </text>\n )}\n </box>\n )\n}\n\n/**\n * Render the project label for the cross-project view. Three cases:\n *\n * - The row belongs to the current project → render \"this project\"\n * in `accent` so the user can quickly spot rows that match where\n * they are.\n * - The row is tagged but for a different project → render the\n * basename of the project root in `dim` (full path would be too\n * long for the row).\n * - The row is untagged (legacy) → render \"untagged\" in `mute`.\n */\nfunction renderProjectLabel(\n rowProject: string | undefined,\n currentProject: string | undefined,\n COLOR: ReturnType<typeof useColors>,\n): ReactNode {\n if (!rowProject) {\n return <span fg={COLOR.mute}>untagged</span>\n }\n if (currentProject && rowProject === currentProject) {\n return <span fg={COLOR.accent}>this project</span>\n }\n // Last path segment. Falls back to the full string if the path is\n // unsegmented (e.g. a Windows root or an unusual format).\n const basename = rowProject.split('/').pop() ?? rowProject\n return (\n <>\n <span fg={COLOR.mute}>project </span>\n <span fg={COLOR.dim}>{basename}</span>\n </>\n )\n}\n\n// ---------------------------------------------------------------------------\n// ChatScreen — transcript + auto-growing multi-line input + (running) spinner.\n// Enter inserts a newline; shift+enter submits; ctrl+↑↓ cycles prompt history.\n// ---------------------------------------------------------------------------\n\n/** Visible content lines: 1 minimum, 5 maximum (textarea scrolls past 5). */\nconst MIN_CONTENT_LINES = 1\nconst MAX_CONTENT_LINES = 5\n\nconst IMAGE_MEDIA_TYPES: Record<string, string> = {\n png: 'image/png',\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n gif: 'image/gif',\n webp: 'image/webp',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n}\n\nconst MIME_BY_EXT: Record<string, string> = {\n txt: 'text/plain',\n md: 'text/markdown',\n json: 'application/json',\n yaml: 'text/yaml',\n yml: 'text/yaml',\n toml: 'text/plain',\n xml: 'application/xml',\n html: 'text/html',\n htm: 'text/html',\n css: 'text/css',\n csv: 'text/csv',\n tsv: 'text/tab-separated-values',\n js: 'text/javascript',\n mjs: 'text/javascript',\n cjs: 'text/javascript',\n ts: 'text/typescript',\n mts: 'text/typescript',\n cts: 'text/typescript',\n tsx: 'text/typescript',\n jsx: 'text/javascript',\n py: 'text/x-python',\n rb: 'text/x-ruby',\n rs: 'text/x-rust',\n go: 'text/x-go',\n java: 'text/x-java',\n c: 'text/x-c',\n h: 'text/x-c',\n cpp: 'text/x-c++',\n hpp: 'text/x-c++',\n sh: 'text/x-shellscript',\n bash: 'text/x-shellscript',\n zsh: 'text/x-shellscript',\n fish: 'text/x-shellscript',\n sql: 'text/x-sql',\n graphql: 'text/x-graphql',\n pdf: 'application/pdf',\n zip: 'application/zip',\n tar: 'application/x-tar',\n gz: 'application/gzip',\n log: 'text/plain',\n env: 'text/plain',\n cfg: 'text/plain',\n ini: 'text/plain',\n conf: 'text/plain',\n}\n\n/**\n * Stable empty-array reference — keeps `queuedMessages` default referentially\n * stable across renders so memoized children don't bust their deps. Declared\n * here (not next to `QueuedPreview`) so it's defined before `ChatScreen`'s\n * default-value reference at the prop destructure.\n */\nconst EMPTY_QUEUED_MESSAGES: readonly QueuedPreview[] = []\n\nexport function ChatScreen({\n cwd,\n events,\n busy,\n compacting = false,\n queuedMessages = EMPTY_QUEUED_MESSAGES,\n queueSelectionIndex = null,\n queueShortcuts,\n onEnterQueueFromEmptyPrompt,\n settings,\n onSubmit,\n session,\n pending,\n onApproval,\n pendingInteraction,\n onInteraction,\n completionProviders,\n onPopupOpenChange,\n selectedTurnId,\n promptTriggerHints,\n liveSession = null,\n}: {\n cwd: string\n events: StreamEvent[]\n busy: boolean\n /**\n * `true` while a background auto-compaction is in flight. Drives the\n * same title-overlay spinner as {@link busy} but in a different color\n * — the conversation is \"active\" without anything streaming visibly.\n */\n compacting?: boolean\n /**\n * User prompts submitted while a run was already in flight. Rendered\n * stacked in a small box above the prompt input until the active\n * `agent.run()` finishes; each entry is then popped (FIFO) and pushed\n * into the transcript as a user message. Empty by default.\n */\n queuedMessages?: readonly QueuedPreview[]\n /**\n * Highlighted index inside {@link queuedMessages}, or `null` when the\n * prompt textarea has focus. When non-null, the textarea blurs (same\n * pattern as `selectMode`) so up / down / push / drop keys reach the\n * App's keyboard handler instead of being eaten by the input.\n */\n queueSelectionIndex?: number | null\n /**\n * Resolved keybinding specs (`\"ctrl+return\"`, `\"backspace\"` — and\n * whatever the user has configured) threaded into the queue UI so\n * the title hint + per-row action hints track `keybindings.json`\n * overrides. Required when the queue is wired — the binding strings\n * are only ever read here.\n */\n queueShortcuts?: QueueShortcuts\n /**\n * Called from `PromptBlock` when the user presses `↑` on an empty\n * prompt. Returning `true` parks focus on the last queued message\n * (the one closest to the prompt) and tells the caller to suppress\n * the default history-cycle behavior; `false` means \"no queue, do\n * your normal thing\" and history-cycling proceeds.\n */\n onEnterQueueFromEmptyPrompt?: () => boolean\n settings: Settings\n /**\n * Submit handler — receives the raw prompt text, the parsed references\n * (skills, files, …) so the App can act on them (e.g. activate the\n * referenced skill before `agent.run()`), and any attachments captured\n * by the textarea's paste pipeline.\n */\n onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => void\n session: SessionMeta | null\n /** Head of the safe-mode approval queue, or `null` when nothing is pending. */\n pending: ApprovalRequest | null\n /** Resolve the active prompt with the user's pick. */\n onApproval: (decision: ApprovalDecision) => void\n /**\n * Head of the interactions queue (plan approval / Q&A). When set, the\n * prompt slot renders {@link InteractionBlock} instead of the textarea\n * so the user resolves the request before continuing typing. Works the\n * same for live tool calls and resumed-session pending interactions —\n * the App layer only swaps the resolver behind {@link onInteraction}.\n */\n pendingInteraction: InteractionRequest | null\n /** Submit the user's response to the head of the interactions queue. */\n onInteraction: (response: InteractionResponse) => void\n /**\n * Optional autocomplete providers. When passed, `PromptBlock` shows a\n * popup above the textarea and intercepts navigation keys while it's\n * visible. Providers are typed by their item payload (e.g.\n * `CompletionProvider<SkillConfig>`).\n */\n completionProviders?: readonly CompletionProvider<unknown>[]\n /**\n * Notified whenever the popup opens / closes. The App uses this to gate\n * the esc-abort handler so esc dismisses the popup instead of aborting\n * the run when both could fire.\n */\n onPopupOpenChange?: (open: boolean) => void\n /**\n * Active turn id when the user is in select-turn mode. `null` = normal\n * mode. Drives the transcript highlight and unfocuses the textarea so\n * up/down/return reach the parent's keyboard handler instead of being\n * consumed by the input.\n */\n selectedTurnId?: string | null\n /**\n * Optional trigger affordances (e.g. `@ files`, `/ skills`) appended\n * to the prompt-box overlay's hint row when there's room. Caller\n * filters by provider availability (don't include `/ skills` if the\n * skills catalog is empty, etc.) — the prompt overlay drops the\n * triggers entirely on narrow terminals.\n */\n promptTriggerHints?: readonly Hint[]\n /**\n * Live `Session` reference for surfaces that read metadata directly\n * (currently {@link TodoIndicator}'s `useActiveTodos` slice). The\n * reference itself is stable across activations of the same session\n * — re-renders are driven by the `events` prop, which already\n * cascades on every tool-result. `null` while the screen is shown\n * without an attached session (the indicator hides itself in that\n * case, same as it does for \"no in_progress item\").\n *\n * Kept distinct from {@link session} (the lightweight\n * {@link SessionMeta} snapshot) so consumers that don't need live\n * data don't accidentally re-read mutable fields off of it.\n */\n liveSession?: Session | null\n}) {\n const COLOR = useColors()\n\n // Split the session header into title (left) + meta (right). Title rides\n // `COLOR.brand` via `TitleOverlay`'s default so the session name reads as\n // the surface's primary anchor. The meta carries two volume stats\n // (`user messages`, total `turns`) followed by the `ctrl+x session`\n // shortcut so the affordance lives next to the info it acts on. The\n // full session id rides the details modal (`ctrl+x`) — it's not\n // load-bearing for the at-a-glance read.\n //\n // - count digits ride `warn` (primary at-a-glance signal).\n // - descriptive suffix (`user messages`, `turns`, `session`) ride `dim`.\n // - `ctrl+x` rides `warn` (matches the bottom-bar shortcut palette).\n // - The shortcut is suppressed while a run is streaming or an\n // approval is pending — `app.tsx`'s ctrl+x handler is gated on the\n // same condition; the title meta and the bottom bar agree on what's\n // actionable.\n const titleText = session?.title ?? 'untitled'\n // Minimal UI mode drops the `ctrl+x session` chip from the title\n // meta — the shortcut is still bound, just no longer advertised in\n // the header. Users discover it via the keybindings panel (`ctrl+y`).\n const showSessionShortcut = !!session\n && !busy && !pending && !pendingInteraction\n && settings.uiMode !== 'minimal'\n // `user-prompt`-kind events are echoed user prompts (see `onSubmitPrompt`\n // in `app.tsx` and `eventsFromTurns` for the persisted case). Counting\n // them is equivalent to counting user turns in the session's history —\n // and stays correct mid-stream because we append the echo before the\n // agent run starts. The dedicated kind (vs filtering generic `info`)\n // means a future banner / status producer can emit `info` without\n // corrupting this counter.\n const userMessageCount = useMemo(\n () => events.filter(e => e.kind === 'user-prompt').length,\n [events],\n )\n const { width: termWidth } = useTerminalDimensions()\n const hasStatusIcon = busy || compacting\n const metaSegments = useMemo<readonly MetaSegment[] | null>(() => {\n if (!session)\n return null\n // Same two-tone pattern as the bottom bar: the primary value\n // (count, key) rides `warn`, the descriptive label rides `dim`,\n // separators ride `mute`. The user-message count replaces the\n // session id as the leading stat — it's the more meaningful\n // signal of \"how much have we said here\", and the id is one\n // `ctrl+x` away in the details modal when it's needed.\n const turnsSuffix = `turn${session.turnCount === 1 ? '' : 's'}`\n const messagesSuffix = `user message${userMessageCount === 1 ? '' : 's'}`\n const segments: MetaSegment[] = [\n { text: String(userMessageCount), color: COLOR.warn },\n { text: ` ${messagesSuffix}` },\n { text: ' · ', color: COLOR.mute },\n { text: String(session.turnCount), color: COLOR.warn },\n { text: ` ${turnsSuffix}` },\n ]\n if (showSessionShortcut) {\n segments.push(\n { text: ' · ', color: COLOR.mute },\n { text: 'ctrl+x', color: COLOR.warn },\n { text: ' session' },\n )\n }\n\n // Prepend the cwd when there's room alongside the title + stats.\n // Mirrors the SessionsScreen overlay: `TitleOverlay` reserves\n // WRAP(2) + META_WRAP(2) + GAP(2) = 6 cells against `termWidth - 4`\n // (terminal width minus the screen wrapper's 1-cell padding on each\n // side and the bordered box's two corners), plus 2 extra cells when\n // a status spinner rides the title slot. Drop the cwd entirely\n // below `MIN_CWD` so we don't show a glyph-only `…/x` fragment.\n const OVERLAY_RESERVED = 6\n const iconReserve = hasStatusIcon ? 2 : 0\n const statsLen = segments.reduce((sum, s) => sum + s.text.length, 0)\n const SEP_LEN = ' · '.length\n const cwdBudget = Math.max(\n 0,\n termWidth - 4 - titleText.length - iconReserve - OVERLAY_RESERVED - statsLen - SEP_LEN,\n )\n const MIN_CWD = 6\n if (cwdBudget >= MIN_CWD) {\n segments.unshift(\n { text: compactPath(cwd, cwdBudget), color: COLOR.dim },\n { text: ' · ', color: COLOR.mute },\n )\n }\n return segments\n }, [cwd, session, userMessageCount, COLOR, showSessionShortcut, termWidth, titleText, hasStatusIcon])\n\n // Prior user prompts sourced from the transcript itself — kept in submit\n // order so the history-navigation feels like a regular shell. `user-prompt`\n // events carry the raw prompt (no `❯ ` prefix), so the history is replayed\n // verbatim — including the rare case of a user message that starts with `❯`.\n const userPrompts = useMemo(\n () => events.filter(e => e.kind === 'user-prompt').map(e => e.text),\n [events],\n )\n\n // (The agent-status glyph that used to live in the title overlay has\n // moved to the Footer's left section — see `Footer status=…` in\n // `app.tsx`. Title overlay only carries title + meta now.)\n\n // Tracks whether the prompt's `/skill` / `@file` completion popup is\n // currently visible. Wired from `PromptBlock`'s `onPopupOpenChange`\n // and used to hide the queue box while the popup is open — see the\n // comment on the queue render below.\n const [completionPopupOpen, setCompletionPopupOpen] = useState(false)\n // Stable identity so `PromptBlock`'s `useEffect([popupOpen, onPopupOpenChange])`\n // doesn't fire on every parent re-render. Also forwards to the host's\n // `onPopupOpenChange` for any global gates (e.g. esc-abort suppression\n // in `app.tsx`) that depend on the popup state.\n const handlePopupOpenChange = useCallback((open: boolean) => {\n setCompletionPopupOpen(open)\n onPopupOpenChange?.(open)\n }, [onPopupOpenChange])\n\n // File-edit approvals (`edit` / `multi_edit` / `write_file`) take over\n // the transcript slot so the modal fills the exact conversation area\n // — same shape, prompt + footer stay anchored at the bottom. The\n // transcript stays mounted (display: 'none') so its internal state\n // — scroll position, computed turn anchors, lazy markdown chrome —\n // survives the modal open/close cycle.\n const fileEditPending = pending && isFileEditTool(pending.tool) ? pending : null\n\n // Signal \"modal is on screen\" so useModalAwareFocus blurs background\n // inputs and the app-level useKeyboard suppresses global shortcuts.\n // We use lock/unlock instead of open/close to avoid rendering a\n // full-screen overlay that would intercept mouse events (scroll, etc.).\n const modal = useModal()\n useEffect(() => {\n if (!fileEditPending)\n return\n modal.lock()\n return () => modal.unlock()\n }, [fileEditPending, modal])\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\n <box\n style={{\n border: true,\n borderColor: COLOR.border,\n flexGrow: 1,\n flexDirection: 'column',\n // Hide instead of unmounting so the Transcript's internal state\n // (memoized turn anchors, scrollbox position, mounted markdown\n // chrome) survives a modal open → close round-trip. OpenTUI's\n // `visible: false` maps to Yoga's `Display.None`, removing the\n // box from layout entirely so the sibling modal claims the slot\n // via its own `flexGrow: 1`.\n visible: !fileEditPending,\n }}\n >\n <Transcript\n events={events}\n settings={settings}\n selectedTurnId={selectedTurnId ?? null}\n // Hide the throbber while the agent is *waiting on us* — a\n // pending approval (file-edit modal, command gate) or a\n // pending question. In those states the agent isn't actually\n // working, it's blocked on user input, so the \"thinking\"\n // pulse would be misleading.\n busy={busy && !pending && !pendingInteraction}\n />\n </box>\n {fileEditPending && (\n // Key on the request id so React force-remounts when the safe-mode\n // queue advances to the next pending approval. Without the key,\n // `MultiEditApprovalModal`'s `mask` / `cursor` / `zone` state and\n // `SingleEditApprovalModal`'s `selected` carry over from the\n // previous call, leaving the user looking at a list whose\n // checkboxes match the prior modal, not the current one.\n <FileEditApprovalModal\n key={fileEditPending.id}\n request={fileEditPending}\n onDecide={onApproval}\n />\n )}\n\n {/*\n Slot priority below the transcript:\n 1. Non-file-edit safe-mode approval — bash gates, etc.\n 2. Pending interaction (plan / question) — the agent paused\n waiting on the user. Live (mid-run) and resumed cases\n share this slot; the App layer swaps the resolver.\n 3. Otherwise the PromptBlock is ALWAYS mounted, even while\n `busy` is true — that's what lets the user type ahead\n during a stream and queue messages for after it lands.\n The \"something's running\" affordance lives next to the\n session title (see `titleStatusIcon` below); a separate\n `QueuedMessagesBlock` above the prompt stacks any\n type-ahead prompts waiting for the drain loop.\n\n File-edit pending falls through to the prompt branch — the\n modal above owns the conversation area, esc/decisions/etc., so\n the prompt stays interactive (queue-only while `busy`).\n\n The pending-interaction case can fire while `busy` is still true\n (the agent's `run()` Promise is awaiting the tool's Promise), so\n it must beat `busy` in the priority chain.\n */}\n {pending && !fileEditPending\n ? <ApprovalBlock request={pending} onPick={onApproval} />\n : pendingInteraction\n ? <InteractionBlock request={pendingInteraction} onResolve={onInteraction} />\n : (\n <>\n {/*\n Hide the queue box while the `/skill` / `@file`\n completion popup is open. The popup's absolute-anchor\n paints above the prompt, and OpenTUI's `scrollbox`\n (the queue's viewport) composites its content at the\n end of its parent's paint pass, which makes them\n visually fight regardless of `zIndex` — the queue's\n rows bleed THROUGH the popup at the rows where they\n overlap. Suppressing the queue while the user is in\n the middle of a completion sidesteps the renderer\n conflict, and the contextual hit is small: the user\n isn't navigating the queue while typing a reference.\n Queue reappears the instant the popup closes (esc /\n commit / clear trigger).\n */}\n {queuedMessages.length > 0 && !completionPopupOpen && (\n <QueuedMessagesBlock\n messages={queuedMessages}\n selectionIndex={queueSelectionIndex}\n shortcuts={queueShortcuts}\n />\n )}\n {/* Subtle \"currently working on\" badge — hides itself when\n no `in_progress` todo exists, no live run is attached,\n or the user has disabled the indicator in Settings.\n Sits between the queue block and the prompt so the\n bar reads as part of the prompt-zone affordances. */}\n <TodoIndicator session={liveSession} />\n <PromptBlock\n userPrompts={userPrompts}\n onSubmit={onSubmit}\n completionProviders={completionProviders}\n onPopupOpenChange={handlePopupOpenChange}\n // Blur the textarea while the user is navigating either\n // the transcript (select-turn) OR the queue box — both\n // states route up/down/return to the App keyboard\n // handler instead of moving the input cursor. The\n // mode value also drives which hint set the prompt\n // shows below it.\n selectMode={\n queueSelectionIndex != null\n ? 'queue'\n : selectedTurnId != null\n ? 'turn'\n : null\n }\n triggerHints={promptTriggerHints}\n busy={busy}\n onEnterQueueFromEmpty={onEnterQueueFromEmptyPrompt}\n />\n </>\n )}\n\n <TitleOverlay title={titleText} meta={metaSegments} />\n </box>\n )\n}\n\n/** Max chars per scalar argument in the approval preview. */\nconst APPROVAL_ARG_MAX = 80\n\n/**\n * Render `{ path: 'x.ts', contents: 'long string' }` as\n * `path: \"x.ts\", contents: \"long string…\"` — readable, per-key, truncated\n * per value rather than dumping `JSON.stringify(input)` (which produces an\n * illegible 50KB blob for `write_file` etc.).\n */\nfunction formatApprovalArgs(input: Record<string, unknown>): string {\n const parts: string[] = []\n for (const [key, raw] of Object.entries(input)) {\n let value: string\n if (typeof raw === 'string') {\n const escaped = raw.replace(/\\n/g, '\\\\n')\n value = escaped.length > APPROVAL_ARG_MAX\n ? `\"${escaped.slice(0, APPROVAL_ARG_MAX)}…\"`\n : `\"${escaped}\"`\n }\n else {\n const json = JSON.stringify(raw)\n if (json.length > APPROVAL_ARG_MAX) {\n // Preserve the structural closer on object/array truncations so the\n // value reads as truncated JSON rather than a syntactically broken\n // fragment (`{ \"a\": 1, \"b…` is misleading; `{ \"a\": 1, \"b…}` reads).\n const closer = json[0] === '{' ? '…}' : json[0] === '[' ? '…]' : '…'\n value = `${json.slice(0, APPROVAL_ARG_MAX)}${closer}`\n }\n else {\n value = json\n }\n }\n parts.push(`${key}: ${value}`)\n }\n return parts.join(', ')\n}\n\n// `description` is required by OpenTUI's `SelectOption` shape even when the\n// select is rendered with `showDescription={false}` — passing an empty string\n// keeps the type happy and is invisible at the render layer.\ninterface DecisionOption { name: string, description: string, value: ApprovalDecision }\n\n/**\n * Inline approval picker — replaces the chat input while a tool call is\n * pending. Three options:\n * - **accept once** — let this call execute, don't persist anything.\n * - **accept + remember** — execute + add a `projects.json` entry so the\n * same shape doesn't prompt again in this directory.\n * - **deny** — refuse the call. The model gets `Blocked: …` and adapts.\n *\n * Esc aborts the whole run via the parent keyboard handler; per-call\n * accept/deny only happens through the select below.\n *\n * Layout is fully pinned so the picker never overlaps with itself or the\n * transcript above:\n *\n * - Outer `<box>` has an explicit `height`. The slot below the transcript\n * adapts (the chat container is column-flex), so we control exactly how\n * many rows we occupy.\n * - Summary row is a `<box height: 1, overflow: hidden>` wrapping a\n * `<text wrapMode=\"none\">` — a 500-char tool-call preview can never\n * wrap to row 2 and push the select off-screen.\n * - `<select showDescription={false}>` keeps each option to exactly one\n * row. Hints live in the `name` string after a `·` separator. Without\n * this, the default `showDescription: true` makes every option take 2\n * rows, and a `height: options.length` select would overdraw into the\n * summary above (the original bug).\n */\nfunction ApprovalBlock({\n request,\n onPick,\n}: {\n request: ApprovalRequest\n onPick: (decision: ApprovalDecision) => void\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n\n // File-edit tools (edit / multi_edit / write_file) are routed to the\n // inline `FileEditApprovalModal` in `ChatScreen`'s transcript slot\n // before reaching this block — see the `pending && !fileEditPending`\n // guard above. This component only handles the generic shell-call /\n // bash-style approval prompt.\n\n const summary = useMemo(\n () => `${request.tool}(${formatApprovalArgs(request.input)})`,\n [request.tool, request.input],\n )\n\n const options = useMemo<DecisionOption[]>(() => {\n const safelistEntry = suggestSafelistEntry(request.tool, request.input)\n return [\n { name: 'accept once · allow this call only', description: '', value: 'accept-once' },\n { name: 'accept for session · auto-approve matching calls until you quit', description: '', value: 'accept-session' },\n { name: `accept + remember · add \"${safelistEntry}\" to projects.json`, description: '', value: 'accept-safelist' },\n { name: 'deny · refuse — the model will see Blocked', description: '', value: 'deny' },\n ]\n }, [request.tool, request.input])\n\n // border (2) + summary (1) + one row per option = total outer height.\n const height = 2 + 1 + options.length\n\n return (\n <box\n title=\" approve tool call · esc to abort run \"\n style={{\n border: true,\n borderColor: COLOR.warn,\n paddingLeft: 1,\n paddingRight: 1,\n paddingTop: 0,\n paddingBottom: 0,\n height,\n flexDirection: 'column',\n flexShrink: 0,\n }}\n >\n <box style={{ height: 1, overflow: 'hidden', flexShrink: 0 }}>\n <text fg={COLOR.model} wrapMode=\"none\">\n <span fg={COLOR.warn}>↳ </span>\n {summary}\n </text>\n </box>\n <select\n {...SELECT_THEME}\n focused={focused}\n options={options}\n showDescription={false}\n wrapSelection\n onSelect={(_idx, option) => {\n if (option)\n onPick(option.value)\n }}\n style={{ height: options.length, flexShrink: 0 }}\n />\n </box>\n )\n}\n\n/**\n * One entry in the queued-messages preview list shown above the prompt\n * input. Only the displayable text + the user-provided ref spans are\n * surfaced — the `references` carry-through happens entirely in the\n * App layer (see `messageQueueRef`); this view doesn't need to know\n * about them.\n */\nexport interface QueuedPreview {\n /** Raw prompt text the user typed (no `❯` prefix). */\n text: string\n /**\n * Optional chip spans from the original prompt (`@files`, `/skills`)\n * — passed through so the queued entry highlights skill / file names\n * the same way the echoed user-prompt event does once it lands in\n * the transcript.\n */\n refs?: readonly { start: number, end: number, providerId: string }[]\n}\n\n/**\n * Resolved keybinding specs the queue UI surfaces to the user. Each\n * field is the literal string from `keybindings.json` (\"ctrl+return\",\n * \"backspace\" — or whatever the user has configured) formatted for\n * display via {@link formatBindingForDisplay} at render time so any\n * customization (e.g. `\"cmd+up\"`, `\"delete\"`) shows the actual key.\n */\nexport interface QueueShortcuts {\n enter: string\n push: string\n drop: string\n}\n\n/**\n * Maximum row count the queue box renders at once. A heavy queue\n * (think 18+ entries) would otherwise sprawl up the screen and crowd\n * the transcript; capping at 5 keeps the block proportional and\n * everything beyond scrolls behind the scrollbar. The user navigates\n * older entries with `↑` (selection mode) and the focused row is\n * auto-scrolled into view via `scrollChildIntoView`.\n */\nconst QUEUE_VISIBLE_ROWS = 5\n\n/** Stable anchor id per queue row — drives `scrollChildIntoView`. */\nfunction queueRowId(index: number): string {\n return `queued-msg-${index}`\n}\n\n/**\n * Stacked preview of prompts the user typed while a previous run was in\n * flight. Sits between the transcript and the prompt input; the drain\n * loop in the App pops each entry FIFO and echoes it into the transcript\n * via `runSingleMessage`, at which point it disappears from this block.\n *\n * When the user enters queue-selection mode (`selectionIndex != null`):\n * - the focused row gets a `▶ ` marker + selection background, and\n * - inline action hints (\"ctrl+↵ push · ⌫ drop\") sit right-aligned on\n * that row only — every other row stays clean so the eye lands on\n * the actionable affordance for the focused message.\n *\n * Wraps the rows in a `<scrollbox>` capped at {@link QUEUE_VISIBLE_ROWS}\n * so heavy queues don't push the prompt off the screen; the focused\n * entry is brought into view on every selection change so navigation\n * feels continuous even when the cursor moves past the viewport.\n *\n * Rendered with a thin top border + a \"queued\" left-title so the box\n * reads as a discrete waiting area — distinct from both the transcript\n * (where committed messages live) and the prompt (where the user is\n * typing the next one). Dropped entirely when the queue is empty.\n */\nfunction QueuedMessagesBlock({\n messages,\n selectionIndex,\n shortcuts,\n}: {\n messages: readonly QueuedPreview[]\n selectionIndex: number | null\n shortcuts: QueueShortcuts | undefined\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n // Discoverability hint on the top-right corner. When the user hasn't\n // bound a dedicated `enter queue` shortcut (the default) we surface\n // the natural path — `↑ select` — instead of an empty hint. Once\n // selection is active, swap to `esc exit` since the enter shortcut\n // would be a no-op anyway.\n const enterHintKey = shortcuts?.enter\n ? formatBindingForDisplay(shortcuts.enter)\n : '↑'\n // Auto-scroll the focused row into view on every selection change.\n // Deferred a frame because OpenTUI computes scroll geometry during the\n // post-commit layout pass — synchronous reads after a React commit\n // would see stale measurements. Same dance the settings modal +\n // question wizard do.\n useEffect(() => {\n if (selectionIndex == null)\n return\n const sb = scrollboxRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollChildIntoView(queueRowId(selectionIndex))\n })\n return () => cancelAnimationFrame(handle)\n }, [selectionIndex])\n // Number of rows the scrollbox renders. Caps at QUEUE_VISIBLE_ROWS so\n // the box reaches a steady-state height fast; shorter queues only\n // claim the height they need.\n const visibleRows = Math.min(messages.length, QUEUE_VISIBLE_ROWS)\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n <box\n style={{\n flexDirection: 'column',\n flexShrink: 0,\n border: ['top'],\n borderColor: COLOR.border,\n paddingLeft: 1,\n paddingRight: 1,\n paddingTop: 0,\n }}\n title={` queued · ${messages.length} `}\n titleAlignment=\"left\"\n >\n <scrollbox\n ref={scrollboxRef}\n // Never grab focus — the parent owns navigation via\n // `useKeyboard`. Mouse-wheel scroll still works as a fallback.\n focusable={false}\n // Pin the viewport to the bottom by default so the newest\n // queued entry (closest to the prompt) is always on screen.\n // `scrollChildIntoView` overrides this when the user is\n // actively navigating an older entry.\n stickyScroll\n stickyStart=\"bottom\"\n style={{ height: visibleRows, flexShrink: 0 }}\n >\n {messages.map((msg, i) => {\n const focused = selectionIndex === i\n const bg = focused ? SURFACE.selection : undefined\n return (\n <box\n key={i}\n id={queueRowId(i)}\n style={{\n flexDirection: 'row',\n flexShrink: 0,\n paddingLeft: 1,\n paddingRight: 1,\n backgroundColor: bg,\n }}\n >\n <text wrapMode=\"none\" style={{ flexGrow: 1 }}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={COLOR.mute}>{'❯ '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{previewText(msg.text)}</span>\n </text>\n {focused && shortcuts && (\n <text wrapMode=\"none\" fg={COLOR.dim}>\n <span fg={COLOR.warn}>{formatBindingForDisplay(shortcuts.push)}</span>\n <span fg={COLOR.dim}>{' push'}</span>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.warn}>{formatBindingForDisplay(shortcuts.drop)}</span>\n <span fg={COLOR.dim}>{' drop'}</span>\n </text>\n )}\n </box>\n )\n })}\n </scrollbox>\n </box>\n <text style={{ position: 'absolute', top: 0, right: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n {selectionIndex == null\n ? (\n <>\n <span fg={COLOR.warn}>{enterHintKey}</span>\n <span fg={COLOR.dim}>{' select'}</span>\n </>\n )\n : (\n <>\n <span fg={COLOR.warn}>esc</span>\n <span fg={COLOR.dim}>{' exit'}</span>\n </>\n )}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n </box>\n )\n}\n\n/**\n * Squash a multi-line prompt into a single line for the queued-preview\n * row — newlines become `↵` markers so the structure is still\n * discoverable but the row stays exactly 1 cell tall, regardless of how\n * the user composed the message. No truncation: OpenTUI's `wrapMode=\"none\"`\n * clips at the box's right edge already.\n */\nfunction previewText(text: string): string {\n return text.replace(/\\n+/g, ' ↵ ')\n}\n\n/** Stable empty providers reference — avoids `useCompletion` rerun on every render when no providers are wired. */\nconst EMPTY_PROVIDERS: readonly CompletionProvider<unknown>[] = []\n\nfunction PromptBlock({\n userPrompts,\n onSubmit,\n completionProviders,\n onPopupOpenChange,\n selectMode = null,\n triggerHints,\n busy = false,\n onEnterQueueFromEmpty,\n}: {\n userPrompts: string[]\n onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => void\n completionProviders?: readonly CompletionProvider<unknown>[]\n onPopupOpenChange?: (open: boolean) => void\n /**\n * Drives both the textarea-focus gate and the prompt-hint label set:\n *\n * - `null` → textarea focused, normal / busy hints depending on\n * the agent's run state.\n * - `'turn'` → user is navigating the transcript via ctrl+s. Hints\n * show \"↑↓ navigate · ↵ open · esc exit\".\n * - `'queue'` → user is navigating the type-ahead queue box above\n * the prompt. Hints show \"↑↓ navigate · esc exit\"\n * (the per-row push / drop hints live ON the queue\n * rows themselves, so this set stays minimal).\n */\n selectMode?: PromptSelectMode | null\n /** Optional trigger hints appended to the right overlay when there's room. */\n triggerHints?: readonly Hint[]\n /**\n * `true` while the agent is mid-run. The textarea stays interactive so\n * the user can type-ahead; the submit-hint label swaps to \"queue\" so\n * the user knows their `↵` will defer instead of fire immediately. The\n * actual queue depth is rendered by the `QueuedMessagesBlock` above\n * the prompt, so we don't duplicate it on the hint row.\n */\n busy?: boolean\n /**\n * Optional handler invoked when the user presses `↑` on an empty\n * prompt buffer. Returning `true` means \"queue selection has taken\n * over, suppress history cycling for this keypress\"; `false` falls\n * through to the regular history navigation. Lets the queue claim\n * the most natural up-key without owning a global shortcut.\n */\n onEnterQueueFromEmpty?: () => boolean\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const textareaRef = useRef<TextareaRenderable | null>(null)\n /** Auto-grow: visible content rows the textarea currently occupies (clamped 1..5). */\n const [contentLines, setContentLines] = useState(MIN_CONTENT_LINES)\n // Files captured by the textarea's paste pipeline (image-path pastes,\n // multiline pastes folded into `paste.txt`). Cleared on submit so the\n // chip strip only ever reflects the in-progress prompt.\n const [attachments, setAttachments] = useState<Attachment[]>([])\n /**\n * Mirror of the textarea buffer + cursor, updated on every `onContentChange`.\n * Drives `useCompletion` — the textarea stays uncontrolled (we don't push\n * React state back into it on every keystroke), this is a read-only view\n * for the engine.\n */\n const [bufferState, setBufferState] = useState<{ text: string, cursor: number }>({ text: '', cursor: 0 })\n /**\n * History navigation state. `null` = not navigating (textarea owns its content).\n * Once the user enters history (up at top), we snapshot the draft and cycle.\n */\n const historyRef = useRef<{ idx: number, draft: string } | null>(null)\n\n const providers = completionProviders ?? EMPTY_PROVIDERS\n const completion = useCompletion(bufferState, providers)\n const popupOpen = completion.active != null && completion.items.length > 0\n\n useEffect(() => {\n onPopupOpenChange?.(popupOpen)\n }, [popupOpen, onPopupOpenChange])\n\n // Per-provider chip backgrounds on the live textarea. The submitted\n // echo paints chips as flex-row `<text bg=…>` segments; the textarea\n // (one OpenTUI edit buffer, no per-span children possible) gets the\n // same color story via `addHighlightByCharRange`. See `useChipHighlights`\n // for the full contract.\n const chipStyle = useChipStyle()\n useChipHighlights(textareaRef, completion.references, chipStyle)\n\n /**\n * Pull the latest buffer state from the OpenTUI textarea ref. Called from\n * `onContentChange` + `onKeyDown` so cursor moves (without text changes)\n * also re-evaluate the active trigger.\n */\n const syncBuffer = useCallback(() => {\n const ta = textareaRef.current\n if (!ta)\n return\n setBufferState({ text: ta.plainText, cursor: ta.cursorOffset })\n // Auto-grow tracks the TOTAL wrapped line count, not the logical\n // line count and not `virtualLineCount`. Two gotchas in play:\n // - `lineCount` only counts `\\n`-separated logical lines, so a\n // pasted single-logical-line block (or a wrapped paragraph)\n // would pin the box to 1 row.\n // - `ta.virtualLineCount` is VIEWPORT-relative — it counts the\n // wrapped rows currently visible inside the textarea's height,\n // not the total across the buffer. With a 1-row starting\n // viewport it returns ~1 even after pasting 20 wrapped lines,\n // so `setContentLines` never gets the signal to grow.\n // `editorView.getTotalVirtualLineCount()` is the actual all-buffer\n // wrap count and grows as text comes in, which is what the\n // auto-grow needs.\n const total = ta.editorView.getTotalVirtualLineCount()\n setContentLines(Math.max(MIN_CONTENT_LINES, total))\n }, [])\n\n const submit = useCallback(() => {\n const value = textareaRef.current?.plainText ?? ''\n // Submit is allowed with empty text iff there's at least one\n // attachment — drag-and-drop an image and hit enter should work\n // even if the user typed nothing.\n if (!value.trim() && attachments.length === 0)\n return\n onSubmit(value, completion.references, attachments)\n textareaRef.current?.clear()\n historyRef.current = null\n setBufferState({ text: '', cursor: 0 })\n setContentLines(MIN_CONTENT_LINES)\n setAttachments([])\n }, [onSubmit, completion.references, attachments])\n\n const commitCompletion = useCallback(() => {\n const result = completion.commit()\n if (!result)\n return false\n const ta = textareaRef.current\n if (!ta)\n return false\n ta.setText(result.text)\n ta.cursorOffset = result.cursor\n syncBuffer()\n return true\n }, [completion, syncBuffer])\n\n /**\n * Multi-line paste handler. The textarea's default `handlePaste`\n * forwards the decoded bytes to `insertText` in one call, which the\n * native edit buffer treats as raw characters — embedded `\\n` lands\n * as a literal glyph rather than splitting the line, and bracketed\n * paste from macOS Terminal often substitutes CR for LF on top of\n * that. The result was a multi-line paste collapsing to one giant\n * row.\n *\n * We `preventDefault` to skip the textarea's default path, normalize\n * `\\r\\n` / `\\r` to `\\n`, then drive the edit buffer explicitly:\n * `insertText` for each segment, `newLine()` between segments. The\n * cursor is positioned at the end of the pasted block (the natural\n * place to keep typing).\n */\n const onPaste = useCallback((event: PasteEvent) => {\n event.preventDefault()\n const ta = textareaRef.current\n if (!ta)\n return\n const raw = stripAnsiSequences(decodePasteBytes(event.bytes))\n const normalized = raw.replace(/\\r\\n?/g, '\\n')\n if (normalized.length === 0)\n return\n\n // File-path paste: a single-line paste that resolves to a real file on\n // disk gets attached instead of inserted as text. Terminals deliver\n // dragged files as bracketed-paste with the path; some wrap it in\n // quotes or a file:// URL.\n if (!normalized.includes('\\n')) {\n const trimmed = normalized.trim().replace(/^[\"']|[\"']$/g, '')\n let resolved = trimmed.startsWith('file://') ? decodeURIComponent(trimmed.slice(7)) : trimmed\n if (resolved.startsWith('~/'))\n resolved = (process.env.HOME ?? '') + resolved.slice(1)\n if (resolved.length > 0 && (resolved.startsWith('/') || resolved.startsWith('.'))) {\n try {\n const st = statSync(resolved)\n if (st.isFile()) {\n const buf = readFileSync(resolved)\n const ext = (resolved.split('.').pop() ?? '').toLowerCase()\n const mediaType = IMAGE_MEDIA_TYPES[ext] ?? MIME_BY_EXT[ext] ?? 'application/octet-stream'\n setAttachments(prev => [...prev, { name: basename(resolved), content: buf, mediaType }])\n return\n }\n }\n catch {\n // statSync threw (ENOENT / EACCES / …) — fall through to the\n // normal paste path so the literal text still lands in the\n // textarea. The user pasted something path-shaped that didn't\n // resolve; better to show it than silently swallow.\n }\n }\n }\n\n // Multiline paste → fold into a `paste.txt` attachment so the prompt\n // doesn't balloon vertically. Distinct attachments per paste so\n // pasting twice produces two chips (the second is `paste.txt` again\n // — agent.run receives both document parts in submission order).\n if (normalized.includes('\\n')) {\n setAttachments(prev => [...prev, {\n name: 'paste.txt',\n content: Buffer.from(normalized, 'utf-8'),\n mediaType: 'text/plain',\n }])\n return\n }\n\n // Plain single-line paste — original insert pipeline.\n const parts = normalized.split('\\n')\n for (let i = 0; i < parts.length; i++) {\n if (i > 0)\n ta.newLine()\n const part = parts[i]\n if (part && part.length > 0)\n ta.insertText(part)\n }\n syncBuffer()\n }, [syncBuffer])\n\n const cycleHistory = useCallback((direction: -1 | 1) => {\n if (userPrompts.length === 0 || !textareaRef.current)\n return\n\n if (historyRef.current === null) {\n historyRef.current = {\n idx: userPrompts.length,\n draft: textareaRef.current.plainText,\n }\n }\n\n const nextIdx = historyRef.current.idx + direction\n\n if (nextIdx < 0)\n return // already at oldest\n\n if (nextIdx >= userPrompts.length) {\n textareaRef.current.setText(historyRef.current.draft)\n textareaRef.current.gotoBufferEnd()\n historyRef.current = null\n }\n else {\n textareaRef.current.setText(userPrompts[nextIdx])\n textareaRef.current.gotoBufferEnd()\n historyRef.current.idx = nextIdx\n }\n syncBuffer()\n }, [userPrompts, syncBuffer])\n\n /**\n * Key interception. OpenTUI fires `onKeyDown` BEFORE the textarea's\n * binding table (`Renderable.keypressHandler` invokes the user\n * listener first, then gates `handleKeyPress` on `defaultPrevented`).\n * That means a single `event.preventDefault()` here cleanly skips the\n * textarea's default action — no parallel binding-filter dance, no\n * stray newline when the popup owns Enter.\n *\n * Popup-owned keys (up/down/return/tab/escape): forward to the\n * completion engine and `preventDefault` so the textarea never sees\n * them — committing a selection should land the cursor at end-of-\n * insert + the trailing space from `insertText`, never an extra `\\n`.\n *\n * History keys: up/down at the top/bottom row of an idle buffer cycle\n * the prompt history. `preventDefault` after a successful cycle keeps\n * the cursor at the end of the recalled prompt; non-edge presses fall\n * through so the textarea moves the cursor as usual.\n */\n const onKeyDown = useCallback((event: KeyEvent) => {\n if (popupOpen) {\n if (event.ctrl || event.meta)\n return\n switch (event.name) {\n case 'up':\n completion.selectPrev()\n event.preventDefault()\n return\n case 'down':\n completion.selectNext()\n event.preventDefault()\n return\n case 'return':\n if (event.shift)\n return\n commitCompletion()\n event.preventDefault()\n return\n case 'tab':\n commitCompletion()\n event.preventDefault()\n return\n case 'escape':\n completion.dismiss()\n event.preventDefault()\n return\n default:\n }\n return\n }\n if (event.ctrl || event.shift || event.meta)\n return\n if (event.name !== 'up' && event.name !== 'down')\n return\n const buffer = textareaRef.current\n if (!buffer)\n return\n // History only fires at the very edges of the buffer:\n // - `↑` at offset 0 (first line, first column) cycles backwards.\n // - `↓` at offset == plainText.length (end of buffer) cycles forward.\n // Anywhere in between, the textarea owns the keypress and moves\n // the cursor naturally — including visually up/down across wrapped\n // rows. This is what makes a multi-line paste feel right: pressing\n // `↑` from the middle of a wrapped paragraph moves you up one\n // visual row instead of swapping the whole buffer for a recalled\n // prompt.\n //\n // Bridge step on `↑`: when the cursor is on the FIRST visual row\n // but not yet at column 0, we explicitly jump it to offset 0\n // instead of falling through to the textarea. Without this,\n // OpenTUI's `moveCursorUp` on the top row is a no-op — the user\n // would press `↑`, see nothing happen, then have to remember to\n // hit `home` / `ctrl+a` before `↑` would recall. With it, the\n // story is \"↑ on first line → cursor at start; ↑ again →\n // history\" — a clean two-tap from any first-line cursor position,\n // and a predictable N+2 taps from anywhere in a multi-line buffer.\n if (event.name === 'up') {\n if (buffer.cursorOffset !== 0) {\n const { row } = buffer.editorView.getCursor()\n if (row === 0) {\n event.preventDefault()\n buffer.cursorOffset = 0\n syncBuffer()\n }\n return\n }\n event.preventDefault()\n // Empty buffer hands `↑` off to the queue first — the natural\n // step is INTO the stacked queued messages above the prompt.\n if (buffer.plainText.length === 0 && onEnterQueueFromEmpty?.())\n return\n cycleHistory(-1)\n return\n }\n if (buffer.cursorOffset !== buffer.plainText.length)\n return\n event.preventDefault()\n cycleHistory(1)\n }, [popupOpen, completion, commitCompletion, cycleHistory, onEnterQueueFromEmpty, syncBuffer])\n\n // Content area + 2 lines of border = total box height.\n const contentHeight = Math.min(MAX_CONTENT_LINES, contentLines)\n const boxHeight = contentHeight + 2\n\n return (\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {/*\n Completion popup floats ABOVE the prompt as an absolute overlay\n (`bottom: 100%` anchors its bottom edge at the prompt's top edge)\n so opening/closing it doesn't shift the prompt or the transcript —\n previously the popup lived in-flow above the prompt, and every\n commit visibly jumped the prompt downward by the popup's height\n as it disappeared. The empty wrapper renders nothing when\n `CompletionPopup` returns null, so this only paints when there's\n actually something to show. Render order matters: declared after\n the bordered prompt below so the renderer paints it on top of\n the transcript area it overlaps.\n\n Works thanks to OpenTUI's default `overflow: 'visible'` (no scissor\n rect is pushed unless `overflow` is \"hidden\" or \"scroll\") — the\n popup can extend outside its parent's bounds without being clipped.\n */}\n {/*\n Inner wrapper anchors the absolute hint overlay at `top: 0`\n relative to the prompt box's top border — the bordered box\n itself can't host the overlay as a child because its scissor\n rect excludes the border row and would clip it.\n */}\n <box style={{ flexDirection: 'column', flexShrink: 0 }}>\n {attachments.length > 0 && (\n <box style={{ flexDirection: 'row', flexWrap: 'wrap', paddingLeft: 1, paddingRight: 1, paddingBottom: 0 }}>\n {attachments.map((att, idx) => {\n const sz = att.content.length\n const label = sz < 1024\n ? `${sz}B`\n : sz < 1024 * 1024\n ? `${(sz / 1024).toFixed(1)}KB`\n : `${(sz / (1024 * 1024)).toFixed(1)}MB`\n const icon = att.mediaType.startsWith('image/') ? '🖼' : '📎'\n const chipColor = resolveChipColor(SURFACE.chips, 'file')\n return (\n <text key={`${att.name}-${idx}`}>\n {idx > 0 ? ' ' : ''}\n {icon}\n <span fg={chipColor.fg} bg={chipColor.bg}>\n {' '}\n {att.name}\n {' '}\n (\n {label}\n )\n {' '}\n </span>\n </text>\n )\n })}\n </box>\n )}\n <box\n style={{\n border: true,\n borderColor: selectMode ? COLOR.warn : COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: boxHeight,\n flexDirection: 'column',\n }}\n >\n <textarea\n ref={textareaRef}\n // Unfocus during select mode so up/down/return route to the\n // parent's keyboard handler (which navigates turns) instead\n // of being consumed as cursor movement / newline insertion.\n focused={focused && !selectMode}\n keyBindings={TEXTAREA_BINDINGS}\n // Pasting a long line — the canonical \"I dropped a stack\n // trace into the prompt\" flow — needs to wrap inside the\n // bordered box, not overflow off the right edge. `word` is\n // the natural choice here: prose lines break on spaces\n // (no mid-word ugliness), code lines without spaces still\n // wrap thanks to OpenTUI's char-fallback when no word\n // boundary fits. The textarea's auto-grow tracks\n // `virtualLineCount` so the height matches what the user\n // actually sees.\n wrapMode=\"word\"\n placeholder={\n selectMode === 'turn'\n ? '— turn-select mode — press ⎋ to resume typing —'\n : selectMode === 'queue'\n ? '— queue-select mode — press ⎋ to resume typing —'\n : 'Ask zidane…'\n }\n syntaxStyle={chipStyle}\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={submit}\n onContentChange={syncBuffer}\n onKeyDown={onKeyDown}\n onPaste={onPaste}\n />\n </box>\n <PromptHints selectMode={selectMode} triggerHints={triggerHints} busy={busy} />\n </box>\n {/*\n Suppress the completion popup entirely while the user is in\n select-turn mode — the textarea is locked there, so the buffer\n can't change and any pre-existing popup state (e.g. `/r` typed\n before pressing ctrl+s) would dangle as stale UI overlapping\n the turn-navigation hints. Completion state itself isn't torn\n down: when the user exits select mode the popup re-renders\n from the current buffer if a trigger is still active.\n */}\n {!selectMode && (\n // `zIndex: 10` puts the popup ABOVE any in-flow sibling that\n // happens to sit between the transcript and the prompt — most\n // visibly the queue-messages block, whose row text would\n // otherwise bleed THROUGH the popup at the rows where they\n // overlap. OpenTUI paints by document order without z by\n // default, and the popup's bottom:100% anchor can extend far\n // enough up to land on top of the queue when the catalog has\n // several matches. The modal layer uses the same trick at a\n // higher z (100) so a settings modal still wins.\n <box style={{ position: 'absolute', bottom: '100%', left: 0, right: 0, flexDirection: 'column', zIndex: 10 }}>\n <CompletionPopup state={completion} />\n </box>\n )}\n </box>\n )\n}\n\n/**\n * Modes that swap the prompt-hint set + blur the textarea so global\n * navigation shortcuts reach the App's keyboard handler.\n */\ntype PromptSelectMode = 'turn' | 'queue'\n\n/** Prompt-box shortcuts in normal mode — order matches reading flow. */\nconst PROMPT_HINTS_NORMAL: readonly Hint[] = [\n { key: '↵', label: 'send' },\n { key: 'shift+↵', label: 'newline' },\n { key: '↑↓', label: 'history' },\n { key: 'ctrl+s', label: 'messages' },\n]\n\n/**\n * Same shape as {@link PROMPT_HINTS_NORMAL} but with the submit verb\n * swapped to \"queue\" — a streaming response means `↵` defers the next\n * prompt instead of firing it immediately.\n */\nconst PROMPT_HINTS_BUSY: readonly Hint[] = [\n { key: '↵', label: 'queue' },\n { key: 'shift+↵', label: 'newline' },\n { key: '↑↓', label: 'history' },\n { key: 'esc', label: 'abort' },\n]\n\n/** Prompt-box shortcuts in select-turn mode — only the selection actions are valid. */\nconst PROMPT_HINTS_SELECT_TURN: readonly Hint[] = [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\n { key: 'esc', label: 'exit' },\n]\n\n/**\n * Prompt-box shortcuts in queue-selection mode. Push / drop hints live\n * on the focused queue row itself (see {@link QueuedMessagesBlock}) so\n * this set stays minimal — navigation + exit, no duplicated actions.\n */\nconst PROMPT_HINTS_SELECT_QUEUE: readonly Hint[] = [\n { key: '↑↓', label: 'navigate' },\n { key: 'esc', label: 'exit' },\n]\n\n/**\n * Inline shortcut hints for the prompt box. Drawn as an absolutely-\n * positioned overlay across the box's top border (right-aligned, one\n * cell from the corner) so it reads like a `<box title>` while keeping\n * the warn / dim / mute palette of the bottom bar — native `title` is\n * painted as part of the border with a single color and can't carry\n * per-segment hue.\n *\n * Must be declared AFTER the bordered box in its parent so the\n * renderer paints it on top, and must be a SIBLING (not a child) of\n * the box: bordered boxes clip their own absolute children via a\n * scissor rect that excludes the border row.\n *\n * Leading + trailing spaces overwrite the border characters underneath\n * the title text, mirroring native titles (`── sessions ──`).\n *\n * Swaps the hint set based on `selectMode` so the user sees only the\n * shortcuts that actually do something in the active mode.\n */\nfunction PromptHints({ selectMode, triggerHints, busy = false }: {\n selectMode: PromptSelectMode | null\n triggerHints?: readonly Hint[]\n /** Swap the submit verb to \"queue\" while a run is streaming. */\n busy?: boolean\n}) {\n const COLOR = useColors()\n const { settings } = useSettings()\n const { width: termWidth } = useTerminalDimensions()\n // Minimal UI mode drops the resting `↵ send · shift+↵ newline · ↑↓\n // history · ctrl+s messages` line from the prompt overlay. Only the\n // `@ files` / `/ skills` triggers remain. Select-turn / queue / busy\n // states keep their full hint sets — those are load-bearing\n // affordances (navigate, abort) that the minimal mode is not trying\n // to hide.\n const minimalResting = !selectMode && !busy && settings.uiMode === 'minimal'\n const primary = selectMode === 'turn'\n ? PROMPT_HINTS_SELECT_TURN\n : selectMode === 'queue'\n ? PROMPT_HINTS_SELECT_QUEUE\n : busy\n ? PROMPT_HINTS_BUSY\n : minimalResting\n ? EMPTY_HINTS\n : PROMPT_HINTS_NORMAL\n const hints = useMemo<readonly Hint[]>(() => {\n // Budget = the top-border row's renderable width = AppShell content\n // area (terminal - 2 cells of host padding) - 2 corner glyphs - 1\n // cell of breathing room from the corner. The overlay aligns to the\n // right, so this is the max width the title can occupy before it\n // crosses into the box's interior or the textarea's first row.\n const budget = Math.max(0, termWidth - 5)\n const tooLongCombined = !!triggerHints\n && triggerHints.length > 0\n && !selectMode\n && primary.length > 0\n && hintsLength(primary) + 3 + hintsLength(triggerHints) > budget\n const candidates = selectMode || !triggerHints || triggerHints.length === 0 || tooLongCombined\n ? primary\n : [...primary, ...triggerHints]\n // Drop hints from the right until the rendered row fits the budget.\n // Without this, a narrow terminal lets OpenTUI wrap the absolute\n // `<text>` overlay over the prompt box's border + first textarea\n // row, painting garbled glyphs where the corners would be.\n return clipHintsToWidth(candidates, budget)\n }, [selectMode, primary, triggerHints, termWidth])\n if (hints.length === 0)\n return null\n return (\n <text style={{ position: 'absolute', top: 0, right: 1 }} fg={COLOR.dim}>\n <span fg={COLOR.mute}>{' '}</span>\n {renderHintSpans(hints, COLOR)}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { KeyBindings } from '../chat/keybindings'\nimport type { SessionExportFormat } from '../chat/session-export'\nimport type { SessionData, SessionRun } from '../session'\nimport { useKeyboard, useTerminalDimensions } from '@opentui/react'\nimport { useEffect, useRef, useState } from 'react'\nimport { ageString, compactPath, fmtTokens, shortId } from '../chat/format'\nimport { matchesBinding } from '../chat/keybindings'\nimport { useColors } from '../chat/theme-context'\nimport { errorMessage } from '../errors'\nimport { writeToClipboard } from './clipboard'\nimport { Spinner } from './components'\nimport { Modal, useModal } from './modal'\n\n/** Result handed back by the host's export handler — drives the success footer. */\nexport interface SessionExportResult {\n /** Absolute path of the written file. */\n filepath: string\n /** Format that was written — used to label the success banner. */\n format: SessionExportFormat\n}\n\n// ---------------------------------------------------------------------------\n// SessionDetailsModal — actions and stats for an entire session.\n//\n// Mirrors the layout + interaction pattern of TurnDetailsModal:\n// - Top border carries the session title; bottom border carries the\n// short id + turn count summary.\n// - Body lists metadata (id, age, turn count) and aggregate token\n// usage rolled up from `session.runs`.\n// - Footer row owns the action shortcuts (single-letter, like turns).\n//\n// Keyboard:\n// - `g` — generate a title for the session from its recent turns\n// (shows an inline spinner while running, updates the header\n// on success, surfaces error in the footer on failure)\n// - `e` — export the session as Markdown under `.{prefix}/sessions/`\n// - `j` — export the session as JSON under `.{prefix}/sessions/`\n// - `d` — delete the entire session (two-press confirmation, esc cancels)\n// - `c` — copy the full session id to the clipboard via OSC 52\n// - esc — close\n//\n// Delete is destructive AND non-reversible (the on-disk session row goes\n// away). The two-press flow uses the same affordance as the turn modal\n// so muscle memory carries over.\n// ---------------------------------------------------------------------------\n\nexport interface SessionDetailsModalActions {\n /** Delete the entire session from the store. Parent handles teardown / navigation. */\n onDelete: (sessionId: string) => void | Promise<void>\n /**\n * Generate a title for the session via the active provider/model.\n * Resolves to the new title (already persisted by the parent); reject\n * with an Error to surface a failure message in the modal footer.\n *\n * Omit when title generation isn't wired (no provider picked yet, or\n * the host opted out) — the modal hides the `g` shortcut.\n *\n * `signal` is provided so a parent unmount (modal closed, session\n * switched) can abort the in-flight provider stream. Implementations\n * should forward it to whatever transport they use; an aborted\n * generation should reject (e.g. with the signal's reason).\n */\n onGenerateTitle?: (sessionId: string, signal: AbortSignal) => Promise<string>\n\n /**\n * Export the session to disk in the requested format. The host\n * decides where the file lands (typically `.{prefix}/sessions/`) and\n * returns the absolute path so the modal can show a confirmation\n * footer. Reject with an Error to surface a failure message.\n *\n * Omit when export isn't wired — the modal hides the `e` / `j`\n * shortcuts.\n */\n onExport?: (sessionId: string, format: SessionExportFormat) => Promise<SessionExportResult>\n\n /**\n * Compact older turns via an LLM-generated summary. The host calls\n * `compactConversation` and appends the synthetic `compact-summary`\n * marker turn so older turns stop reaching the provider. The model\n * sees everything from the marker forward verbatim; the user can\n * still scroll back to see the original turns.\n *\n * Resolves to a result envelope used by the modal to flash the\n * \"compacted N turns\" success banner. Reject with an Error to\n * surface a failure message.\n *\n * `signal` is provided so a parent unmount (modal closed, session\n * switched) can abort the in-flight summary call.\n *\n * Omit when compaction isn't wired (no provider picked yet, or the\n * host opted out) — the modal hides the `k` shortcut.\n */\n onCompact?: (sessionId: string, signal: AbortSignal) => Promise<SessionCompactResult>\n}\n\n/** What `onCompact` returns. Drives the modal's success banner. */\nexport interface SessionCompactResult {\n /** Number of turns absorbed into the summary. */\n replacedCount: number\n /**\n * Number of turns the PTL-retry path had to drop without summarizing.\n * Zero on first-try success. When non-zero, the modal warns the user\n * that some context was lost — those turns stay in the conversation\n * history but the new summary doesn't describe them.\n */\n droppedCount: number\n /**\n * Number of files re-injected as synthetic `read_file` results after\n * the compaction marker. Zero when the session had no active reads or\n * the host opted out of restoration.\n */\n restoredFiles: number\n /**\n * Number of skills re-injected as synthetic `skills_use` results\n * after the compaction marker.\n */\n restoredSkills: number\n /** Model used for the summary call. */\n model: string\n /** Total input tokens billed (including cache reads + creations). */\n inputTokens: number\n /** Output tokens of the summary itself. */\n outputTokens: number\n /**\n * Estimated wire-level context size the NEXT turn will start from.\n * Sum of the summary's tokens plus the restoration content's\n * byte-based estimate — captures what the model will actually see on\n * its next call, modulo the system prompt and any preserved tail\n * turns whose precise token count we don't know without re-asking\n * the provider.\n *\n * Drives both the post-compact success banner (\"→ N effective\") and\n * the chat footer's `ctx` indicator — `onCompactSession` writes this\n * value into `lastInputTokens` so the user sees the drop immediately\n * instead of waiting for the next real turn's `usage` update.\n */\n effectiveTokens: number\n}\n\nexport function SessionDetailsModal({\n session,\n title,\n isCurrent,\n actions,\n keybindings,\n}: {\n session: SessionData\n /**\n * Display title for the session. Falls back to the first user turn's\n * preview when omitted — the parent usually has a cached `SessionMeta`\n * title and threads it through so the modal header matches what the\n * sessions list / chat title bar are showing.\n */\n title?: string\n /** True when this is the actively-mounted session — gates the \"active\" tag in the header. */\n isCurrent: boolean\n actions: SessionDetailsModalActions\n /** Effective keybindings — the modal-internal letter actions resolve through this. */\n keybindings: KeyBindings\n}) {\n const COLOR = useColors()\n const modal = useModal()\n // Drives left-truncation of the cwd line so a long absolute path can't\n // blow past the modal's width on a narrow terminal. The modal sits at\n // ~60% of the terminal; we reserve ~10 cells for the `cwd ` prefix +\n // padding and floor at 24 so it stays at least somewhat readable.\n const { width: termWidth } = useTerminalDimensions()\n const cwdMaxWidth = Math.max(24, Math.floor(termWidth * 0.6) - 12)\n\n // Initial header — `metadata.title` wins if previously generated; the\n // host's cached `SessionMeta.title` is the next preference; finally\n // the in-memory fallback for sessions with no recorded title at all.\n const initialTitle = title\n ?? (typeof session.metadata?.title === 'string' ? session.metadata.title : undefined)\n ?? 'untitled'\n // Internalized so a successful `g` action updates the header without\n // round-tripping through the parent (the modal stays open afterward\n // so the user sees the new title in place).\n const [displayTitle, setDisplayTitle] = useState(initialTitle)\n const usage = aggregateUsage(session.runs)\n const turnCount = session.turns.length\n // User-message count = subset of turns where the human spoke. Pairs\n // with the chat title's meta (which counts the same thing) so the\n // user sees the same volume signal in both places.\n const userMessageCount = session.turns.filter(t => t.role === 'user').length\n const hasGenerate = actions.onGenerateTitle != null && turnCount > 0\n\n // Two-press confirmation state, mirrors TurnDetailsModal. `null` = first\n // press, action commits on the second. Esc clears it.\n const [pending, setPending] = useState<'delete' | null>(null)\n // Transient copy feedback. Resets to `idle` on the next action so the\n // success / failure line stays informative without lingering forever.\n const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'failed'>('idle')\n // Title-generation lifecycle. `idle` is the default footer hint; on\n // `g` we flip to `loading` and the spinner row replaces the action\n // hints. `failed` carries the error message into the footer; the user\n // can press `g` again to retry.\n const [titleStatus, setTitleStatus] = useState<'idle' | 'loading' | 'failed'>('idle')\n const [titleError, setTitleError] = useState<string | null>(null)\n // Export lifecycle. `writing` shows a spinner; `success` shows the\n // written path; `failed` shows the error with a retry hint. Both\n // `e` and `j` route through the same state machine since the modal\n // only ever runs one export at a time and the success banner\n // already names the format.\n const [exportStatus, setExportStatus] = useState<'idle' | 'writing' | 'success' | 'failed'>('idle')\n const [exportResult, setExportResult] = useState<SessionExportResult | null>(null)\n const [exportError, setExportError] = useState<string | null>(null)\n const hasExport = actions.onExport != null && turnCount > 0\n\n // Compact lifecycle. `running` shows a spinner; `success` shows the\n // \"compacted N turns\" banner; `failed` shows the error with a retry\n // hint. Gated on at least 2 turns so the modal doesn't offer\n // compaction on conversations that have nothing to compact.\n const [compactStatus, setCompactStatus] = useState<'idle' | 'running' | 'success' | 'failed'>('idle')\n const [compactResult, setCompactResult] = useState<SessionCompactResult | null>(null)\n const [compactError, setCompactError] = useState<string | null>(null)\n const hasCompact = actions.onCompact != null && turnCount >= 2\n\n // Abort handles for the in-flight async actions. Captured in refs so\n // (a) the unmount cleanup can abort whatever's outstanding, and (b) the\n // post-await setters can early-return when the modal already vanished\n // mid-stream (rare — the modal blocks Esc while loading — but still safe).\n const generationAbortRef = useRef<AbortController | null>(null)\n const compactAbortRef = useRef<AbortController | null>(null)\n // `mountedRef` flips false on unmount so the async handlers don't push\n // state into a tree React already tore down. The aborts cover the\n // provider calls; this covers the React side of the dance.\n const mountedRef = useRef(true)\n useEffect(() => () => {\n mountedRef.current = false\n generationAbortRef.current?.abort()\n generationAbortRef.current = null\n compactAbortRef.current?.abort()\n compactAbortRef.current = null\n }, [])\n\n // Resetting peer feedback states whenever a new action starts keeps\n // the action row deterministic: only ONE banner is ever live at a\n // time, and it's the most recent thing the user actually did. Without\n // this, a stale `✓ session id copied` would dangle through a\n // subsequent successful export, etc.\n const resetPeerFeedback = (keep: 'copy' | 'export' | 'title' | 'compact' | 'none' = 'none') => {\n if (keep !== 'copy')\n setCopyStatus('idle')\n if (keep !== 'export') {\n setExportStatus('idle')\n setExportResult(null)\n setExportError(null)\n }\n if (keep !== 'title') {\n // `titleStatus === 'loading'` is locked elsewhere (the keyboard\n // returns early), so we only ever reset from `idle` / `failed`.\n setTitleStatus(prev => prev === 'loading' ? prev : 'idle')\n setTitleError(null)\n }\n if (keep !== 'compact') {\n setCompactStatus(prev => prev === 'running' ? prev : 'idle')\n setCompactResult(null)\n setCompactError(null)\n }\n }\n const commitDelete = () => {\n modal.close()\n void actions.onDelete(session.id)\n }\n const handleCopy = () => {\n resetPeerFeedback('copy')\n setCopyStatus(writeToClipboard(session.id) ? 'copied' : 'failed')\n }\n const handleExport = async (format: SessionExportFormat) => {\n if (!actions.onExport || exportStatus === 'writing')\n return\n resetPeerFeedback('export')\n setExportStatus('writing')\n try {\n const result = await actions.onExport(session.id, format)\n if (!mountedRef.current)\n return\n setExportResult(result)\n setExportStatus('success')\n }\n catch (err) {\n if (!mountedRef.current)\n return\n setExportError(errorMessage(err))\n setExportStatus('failed')\n }\n }\n const handleGenerate = async () => {\n if (!actions.onGenerateTitle || titleStatus === 'loading')\n return\n resetPeerFeedback('title')\n setTitleStatus('loading')\n const ac = new AbortController()\n generationAbortRef.current = ac\n try {\n const next = await actions.onGenerateTitle(session.id, ac.signal)\n if (!mountedRef.current || ac.signal.aborted)\n return\n setDisplayTitle(next)\n setTitleStatus('idle')\n }\n catch (err) {\n if (!mountedRef.current || ac.signal.aborted)\n return\n setTitleError(errorMessage(err))\n setTitleStatus('failed')\n }\n finally {\n if (generationAbortRef.current === ac)\n generationAbortRef.current = null\n }\n }\n const handleCompact = async () => {\n if (!actions.onCompact || compactStatus === 'running')\n return\n resetPeerFeedback('compact')\n setCompactStatus('running')\n const ac = new AbortController()\n compactAbortRef.current = ac\n try {\n const result = await actions.onCompact(session.id, ac.signal)\n if (!mountedRef.current || ac.signal.aborted)\n return\n setCompactResult(result)\n setCompactStatus('success')\n }\n catch (err) {\n if (!mountedRef.current || ac.signal.aborted)\n return\n setCompactError(errorMessage(err))\n setCompactStatus('failed')\n }\n finally {\n if (compactAbortRef.current === ac)\n compactAbortRef.current = null\n }\n }\n\n useKeyboard((key) => {\n // Lock all shortcuts while a long-running action is in flight —\n // pressing `d` or another action mid-stream would race the async\n // update. Title generation, export, and compaction all share this lock.\n if (titleStatus === 'loading' || exportStatus === 'writing' || compactStatus === 'running')\n return\n if (key.name === 'escape' && pending) {\n setPending(null)\n return\n }\n if (matchesBinding(key, keybindings.sessionDelete)) {\n if (pending === 'delete')\n commitDelete()\n else\n setPending('delete')\n return\n }\n if (matchesBinding(key, keybindings.sessionCopyId)) {\n setPending(null)\n handleCopy()\n return\n }\n if (matchesBinding(key, keybindings.sessionGenerateTitle) && hasGenerate) {\n setPending(null)\n void handleGenerate()\n return\n }\n if (matchesBinding(key, keybindings.sessionExportMarkdown) && hasExport) {\n setPending(null)\n void handleExport('markdown')\n return\n }\n if (matchesBinding(key, keybindings.sessionExportJson) && hasExport) {\n setPending(null)\n void handleExport('json')\n return\n }\n if (matchesBinding(key, keybindings.sessionCompact) && hasCompact) {\n setPending(null)\n void handleCompact()\n return\n }\n // Any other key cancels a pending confirmation so the user isn't\n // surprised by a delayed delete after they navigated away.\n if (pending)\n setPending(null)\n })\n\n return (\n <Modal\n title={`session · ${displayTitle}`}\n bottomTitle={`#${shortId(session.id)} · ${turnCount} turn${turnCount === 1 ? '' : 's'}`}\n // Esc is handed to this component's own `useKeyboard` whenever it\n // matters: during loading (so Esc is intentionally inert — the footer\n // says \"generating title, please wait\") and during a pending delete\n // (so Esc clears `pending` instead of closing). Outside those states\n // Modal handles Esc normally.\n disableEscape={\n titleStatus === 'loading'\n || exportStatus === 'writing'\n || compactStatus === 'running'\n || pending !== null\n }\n >\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>id </span>\n <span fg={COLOR.model}>{session.id}</span>\n {isCurrent && (\n <>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.accent}>active</span>\n </>\n )}\n </text>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>created </span>\n <span fg={COLOR.dim}>{ageString(session.createdAt)}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>updated </span>\n <span fg={COLOR.dim}>{ageString(session.updatedAt)}</span>\n </text>\n\n {/*\n cwd line — own row so a long path doesn't push the dense\n `turns · user · runs · status` line off the right edge. Left-\n truncates with an ellipsis (the tail of a path is the\n recognizable part) when the terminal is narrow; untagged\n legacy sessions render the placeholder in `mute` so it reads\n as \"no data\" instead of a real value.\n */}\n <text fg={COLOR.dim} wrapMode=\"none\">\n <span fg={COLOR.mute}>cwd </span>\n {session.projectRoot\n ? <span fg={COLOR.dim}>{compactPath(session.projectRoot, cwdMaxWidth)}</span>\n : <span fg={COLOR.mute}>untagged</span>}\n </text>\n\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>turns </span>\n <span fg={COLOR.warn}>{turnCount}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>user </span>\n <span fg={COLOR.warn}>{userMessageCount}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>runs </span>\n <span fg={COLOR.dim}>{session.runs.length}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>status </span>\n <span fg={statusColor(session.status, COLOR)}>{session.status}</span>\n </text>\n\n {usage.total > 0 && (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>tokens </span>\n <span fg={COLOR.model}>{fmtTokens(usage.total)}</span>\n <span fg={COLOR.mute}> · in </span>\n <span fg={COLOR.dim}>{fmtTokens(usage.input)}</span>\n <span fg={COLOR.mute}> · out </span>\n <span fg={COLOR.dim}>{fmtTokens(usage.output)}</span>\n {usage.cacheRead > 0 && (\n <>\n <span fg={COLOR.mute}> · cached </span>\n <span fg={COLOR.dim}>{fmtTokens(usage.cacheRead)}</span>\n </>\n )}\n {usage.cost > 0 && (\n <>\n <span fg={COLOR.mute}> · cost </span>\n <span fg={COLOR.dim}>{`$${usage.cost.toFixed(usage.cost < 0.01 ? 4 : 2)}`}</span>\n </>\n )}\n </text>\n )}\n\n <ActionRow\n pending={pending}\n copyStatus={copyStatus}\n titleStatus={titleStatus}\n titleError={titleError}\n hasGenerate={hasGenerate}\n exportStatus={exportStatus}\n exportResult={exportResult}\n exportError={exportError}\n hasExport={hasExport}\n compactStatus={compactStatus}\n compactResult={compactResult}\n compactError={compactError}\n hasCompact={hasCompact}\n keys={{\n delete: keybindings.sessionDelete,\n copy: keybindings.sessionCopyId,\n generate: keybindings.sessionGenerateTitle,\n markdown: keybindings.sessionExportMarkdown,\n json: keybindings.sessionExportJson,\n compact: keybindings.sessionCompact,\n }}\n />\n </Modal>\n )\n}\n\n/**\n * Footer action row — mirrors the turn-details modal's pattern. Swaps\n * between the default hint row, a delete-confirm prompt, copy\n * success/failure feedback, an in-flight title-generation spinner, and\n * a title-generation error message. The geometry stays a single row\n * across all states so the modal body never shifts.\n */\nfunction ActionRow({\n pending,\n copyStatus,\n titleStatus,\n titleError,\n hasGenerate,\n exportStatus,\n exportResult,\n exportError,\n hasExport,\n compactStatus,\n compactResult,\n compactError,\n hasCompact,\n keys,\n}: {\n pending: 'delete' | null\n copyStatus: 'idle' | 'copied' | 'failed'\n titleStatus: 'idle' | 'loading' | 'failed'\n titleError: string | null\n hasGenerate: boolean\n exportStatus: 'idle' | 'writing' | 'success' | 'failed'\n exportResult: SessionExportResult | null\n exportError: string | null\n hasExport: boolean\n compactStatus: 'idle' | 'running' | 'success' | 'failed'\n compactResult: SessionCompactResult | null\n compactError: string | null\n hasCompact: boolean\n /** Resolved binding strings for every action this row advertises. */\n keys: {\n delete: string\n copy: string\n generate: string\n markdown: string\n json: string\n compact: string\n }\n}) {\n const COLOR = useColors()\n\n if (titleStatus === 'loading')\n return <Spinner label=\"generating title — please wait\" />\n\n if (exportStatus === 'writing')\n return <Spinner label=\"exporting session — please wait\" />\n\n if (compactStatus === 'running')\n return <Spinner label=\"compacting older turns — please wait\" />\n\n if (titleStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>title generation failed</span>\n {titleError ? <span fg={COLOR.mute}>{` — ${titleError}`}</span> : null}\n {' · '}\n <span fg={COLOR.warn}>{keys.generate}</span>\n {' retry · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (exportStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>export failed</span>\n {exportError ? <span fg={COLOR.mute}>{` — ${exportError}`}</span> : null}\n {' · '}\n <span fg={COLOR.warn}>{keys.markdown}</span>\n {' / '}\n <span fg={COLOR.warn}>{keys.json}</span>\n {' retry · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (compactStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>compaction failed</span>\n {compactError ? <span fg={COLOR.mute}>{` — ${compactError}`}</span> : null}\n {' · '}\n <span fg={COLOR.warn}>{keys.compact}</span>\n {' retry · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (pending === 'delete') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>delete this session?</span>\n {' press '}\n <span fg={COLOR.error}>{keys.delete}</span>\n {' again to confirm · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </text>\n )\n }\n\n if (exportStatus === 'success' && exportResult) {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>{`✓ wrote ${exportResult.format === 'json' ? 'JSON' : 'Markdown'}`}</span>\n <span fg={COLOR.mute}>{' → '}</span>\n <span fg={COLOR.model}>{compactPath(exportResult.filepath)}</span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (compactStatus === 'success' && compactResult) {\n const restoredParts: string[] = []\n if (compactResult.restoredFiles > 0)\n restoredParts.push(`${compactResult.restoredFiles} file${compactResult.restoredFiles === 1 ? '' : 's'}`)\n if (compactResult.restoredSkills > 0)\n restoredParts.push(`${compactResult.restoredSkills} skill${compactResult.restoredSkills === 1 ? '' : 's'}`)\n const restoredLabel = restoredParts.length > 0 ? `restored ${restoredParts.join(' + ')}` : null\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>{`✓ compacted ${compactResult.replacedCount} turn${compactResult.replacedCount === 1 ? '' : 's'}`}</span>\n {compactResult.droppedCount > 0 && (\n <>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.warn}>{`${compactResult.droppedCount} dropped (too large)`}</span>\n </>\n )}\n {restoredLabel && (\n <>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.accent}>{restoredLabel}</span>\n </>\n )}\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.dim}>{fmtTokens(compactResult.inputTokens)}</span>\n <span fg={COLOR.mute}> in / </span>\n <span fg={COLOR.dim}>{fmtTokens(compactResult.outputTokens)}</span>\n <span fg={COLOR.mute}> out</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.accent}>{`→ ${fmtTokens(compactResult.effectiveTokens)}`}</span>\n <span fg={COLOR.mute}> effective</span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (copyStatus === 'copied') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>✓ session id copied</span>\n {' · '}\n <span fg={COLOR.warn}>{keys.delete}</span>\n {' delete · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (copyStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>copy failed (terminal may not support OSC 52)</span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n return (\n <text fg={COLOR.dim}>\n {hasGenerate && (\n <>\n <span fg={COLOR.warn}>{keys.generate}</span>\n {' title · '}\n </>\n )}\n {hasExport && (\n <>\n <span fg={COLOR.warn}>{keys.markdown}</span>\n /\n <span fg={COLOR.warn}>{keys.json}</span>\n {' export · '}\n </>\n )}\n {hasCompact && (\n <>\n <span fg={COLOR.warn}>{keys.compact}</span>\n {' compact · '}\n </>\n )}\n <span fg={COLOR.warn}>{keys.delete}</span>\n {' delete · '}\n <span fg={COLOR.warn}>{keys.copy}</span>\n {' copy · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n}\n\ninterface AggregatedUsage {\n input: number\n output: number\n cacheRead: number\n total: number\n cost: number\n}\n\n/**\n * Sum token + cost figures across every {@link SessionRun}. Preference\n * order for a run's tally is `totalUsage` → per-`turnUsage[]` sum.\n * `turnUsage` exists on every completed run; `totalUsage` is provider-\n * specific (set by some pi-ai adapters when the API surfaces it).\n *\n * Returns an aggregate of zeroes when no usage data is available — the\n * caller decides whether to render the tokens row at all.\n */\nfunction aggregateUsage(runs: readonly SessionRun[]): AggregatedUsage {\n const acc = { input: 0, output: 0, cacheRead: 0, cost: 0 }\n for (const run of runs) {\n if (run.totalUsage) {\n acc.input += run.totalUsage.input ?? 0\n acc.output += run.totalUsage.output ?? 0\n acc.cacheRead += run.totalUsage.cacheRead ?? 0\n }\n else if (run.turnUsage) {\n for (const u of run.turnUsage) {\n acc.input += u.input ?? 0\n acc.output += u.output ?? 0\n acc.cacheRead += u.cacheRead ?? 0\n }\n }\n if (run.cost)\n acc.cost += run.cost\n }\n return { ...acc, total: acc.input + acc.output }\n}\n\n/**\n * Color a `SessionData.status` for the metadata row — `idle` reads\n * normal, `running` warm (something's in flight), `error` red. Falls\n * back to dim for any future-added status the palette doesn't know.\n */\nfunction statusColor(status: SessionData['status'], COLOR: { dim: string, warn: string, error: string, accent: string }): string {\n switch (status) {\n case 'completed': return COLOR.accent\n case 'running': return COLOR.warn\n case 'error': return COLOR.error\n default: return COLOR.dim\n }\n}\n","/** @jsxImportSource @opentui/react */\nimport type { InputRenderable, ScrollBoxRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type { ProviderAuth } from '../chat/auth'\nimport type { KeyBindings } from '../chat/keybindings'\nimport type { McpAuthStatus } from '../chat/mcp-auth-state'\nimport type { DiscoveredMcp, DiscoveryError } from '../chat/mcps-discovery'\nimport type { BooleanSettingKey, SettingsCategory } from '../chat/settings-context'\nimport type { Settings } from '../chat/types'\nimport type { SkillConfig } from '../skills'\nimport { homedir } from 'node:os'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { useDiscoveryOptional } from '../chat/discovery-context'\nimport { useEnabledToggleSet } from '../chat/enabled-toggle-set'\nimport { groupBindings } from '../chat/keybindings'\nimport { useMcpAuthState } from '../chat/mcp-auth-context'\nimport { getMcpAuthStatus } from '../chat/mcp-auth-state'\nimport { SETTINGS_CATEGORIES, SETTINGS_CHOICES, SETTINGS_TOGGLES, useSettings } from '../chat/settings-context'\nimport { useColors, useSurfaces } from '../chat/theme-context'\nimport { KeybindingsCatalog, KeybindingsEditFileButton } from './keybindings-modal'\nimport { Modal } from './modal'\nimport { McpAuthorizingPanel } from './oauth-auth-block'\n\n// ---------------------------------------------------------------------------\n// SettingsModal — tabbed: Agent / UI / Keybindings / Authentication /\n// Skills / MCP servers.\n//\n// The Agent / UI split is data-driven by `SETTINGS_CATEGORIES`\n// from `chat/settings-context` — every toggle + choice declares its\n// `category` there, so the same metadata is available to any\n// downstream GUI consumer (web settings panel, …) without\n// re-hardcoding the bucketing.\n//\n// The Keybindings + Authentication tabs are CONDITIONAL — they only\n// appear when the host passes the corresponding data props (the\n// `keybindings` map for the former, the `authentication` view for the\n// latter). Embedders that don't wire them still get the rest of the\n// modal unchanged. When the data IS provided, the modal also drops\n// the corresponding \"open keybindings file\" / \"re-authenticate\"\n// action rows from the Agent / UI tabs — they'd be redundant.\n//\n// Geometry:\n// - Top: tab strip (active highlighted on selection background).\n// - Search input — single-line, focused on mount; one shared query\n// filters whichever tab is active (skills, MCPs, and category\n// rows all carry pre-lowercased corpora so the filter is\n// `term.every → corpus.includes`).\n// - Content area: windowed list of the active tab's filtered rows.\n// - Hints: dynamic depending on tab + focused row state.\n//\n// Keys (per tab — global ones first):\n// - ←/→ cycle tabs (preventDefault so the input cursor stays put).\n// - esc close (also cancels an in-flight MCP login when\n// focused on an `authorizing` row).\n// Agent / UI / Skills / MCPs / Authentication — selectable rows:\n// - ↑/↓ move the per-tab cursor (also ctrl+P / ctrl+N).\n// - ↵ toggle / cycle / pick a row.\n// Keybindings — informational catalog (no row cursor):\n// - ↑/↓ scroll the catalog by 2 lines.\n// - pageup/dn scroll by half a viewport (also ctrl+B / ctrl+F).\n// - home/end jump to top / bottom.\n// - ↵ open `keybindings.json` in `$EDITOR`.\n// Authentication — provider list:\n// - ↵ switch to the focused provider (or pop the AuthScreen\n// wizard on unavailable rows / the trailing +add row).\n// MCPs only:\n// - ctrl+L login (needs-auth or error rows).\n// - ctrl+O logout (authed / authorizing / error rows).\n// Skills + MCPs:\n// - ctrl+R force a discovery refresh.\n// ---------------------------------------------------------------------------\n\ninterface ToggleItem {\n kind: 'toggle'\n key: BooleanSettingKey\n label: string\n description: string\n category: SettingsCategory\n}\ninterface ChoiceItem {\n kind: 'choice'\n key: keyof Settings\n label: string\n description: string\n options: readonly { value: unknown, label: string }[]\n category: SettingsCategory\n}\ninterface ActionItem {\n kind: 'action'\n id: string\n label: string\n description: string\n onPick: () => void\n category: SettingsCategory\n}\ntype GeneralItem = ActionItem | ChoiceItem | ToggleItem\n\nexport interface SettingsActions {\n /** Re-open the auth screen (full-screen) so the user can run the setup wizard. */\n onReauth?: () => void\n /**\n * Switch the active provider to one of the detected entries. Wired\n * by the Authentication tab's `↵` on a provider row — `↵` on an\n * unavailable row falls back to `onReauth` (which pops the wizard).\n * Closes the modal as a side effect.\n */\n onPickProvider?: (provider: ProviderAuth) => void\n /** Open `~/.zidane/keybindings.json` in `$EDITOR`. */\n onOpenKeybindings?: () => void\n /** Start an MCP OAuth flow. Resolves once tokens land or the flow aborts. */\n onLoginMcp?: (name: string) => Promise<void> | void\n /** Wipe stored MCP tokens. */\n onLogoutMcp?: (name: string) => void\n /** Abort an in-flight MCP OAuth flow. */\n onCancelLoginMcp?: (name: string) => void\n /**\n * Force a fresh skills rescan, bypassing the background SWR throttle.\n * Fired by `ctrl+R` on the Skills tab. When wired the host should\n * call `discoverProjectSkills` directly and replace the catalog —\n * an in-flight refresh promise resolves into the modal automatically\n * via the catalog prop.\n */\n onRefreshSkills?: () => Promise<void> | void\n /**\n * Re-parse the project's `mcps.json` files. Fired by `ctrl+R` on\n * the MCPs tab. Sync-shaped under the hood but typed as async so\n * future discovery paths (registry lookups, etc.) can stay\n * non-blocking without changing the contract.\n */\n onRefreshMcps?: () => Promise<void> | void\n}\n\n// Tab ids = settings-context category ids + the four enumerated views.\n// Category-tab order matches `SETTINGS_CATEGORIES` so flipping a row's\n// category in `settings-context` reorders both surfaces (TUI + GUI)\n// in lockstep; the trailing four are stable.\ntype TabId\n = | 'authentication'\n | 'keybindings'\n | 'mcps'\n | 'skills'\n | SettingsCategory\n\nconst TAB_LABELS: Record<TabId, string> = {\n ...Object.fromEntries(SETTINGS_CATEGORIES.map(c => [c.id, c.label])) as Record<SettingsCategory, string>,\n keybindings: 'Keybindings',\n authentication: 'Authentication',\n skills: 'Skills',\n mcps: 'MCP servers',\n}\n\nfunction isCategoryTab(id: TabId): id is SettingsCategory {\n return id !== 'skills' && id !== 'mcps' && id !== 'keybindings' && id !== 'authentication'\n}\n\n// Stable scroll-anchor for the focused row inside the modal's scrollbox.\n// One id per visible row index — the `scrollChildIntoView` call below\n// re-uses these whenever the cursor moves or the user switches tab.\nfunction anchorIdFor(index: number): string {\n return `settings-row-${index}`\n}\n\n// Title column alignment — EVERY row's title starts at column 6. Toggle /\n// skill / MCP rows fill columns 2-5 with their `[ ] ` checkbox; choice /\n// action rows pad the same span with whitespace so titles line up in a\n// single column down the list. Reads as a clean grid: the eye doesn't have\n// to jump between indent levels when scanning across kinds.\nconst COL_TITLE = ' ' // 6 spaces (marker [2] + checkbox/spacer [4])\nconst SPACER_CHECKBOX_WIDTH = ' ' // 4 spaces — same visual width as `[ ] `\n\n// ---------------------------------------------------------------------------\n\n/**\n * Snapshot of the host's auth state for the Authentication tab.\n * Drives the \"Current\" header + the selectable list of detected\n * providers. The actual provider-switch is delegated to\n * `actions.onPickProvider`; the trailing \"+ add\" row + every\n * unavailable provider fall back to `actions.onReauth` (which pops\n * the full AuthScreen — the wizard flow is too multi-step to embed).\n */\nexport interface AuthenticationView {\n /** Active provider's display label (e.g. \"Anthropic\"). */\n currentProviderLabel?: string\n /** Active provider's machine key (e.g. \"anthropic\"). */\n currentProviderKey?: string\n /** Active model id (e.g. \"claude-sonnet-4-5\"). */\n currentModel?: string\n /** Detected providers from `detectAuth` — used to render the list with credential badges. */\n providers?: readonly ProviderAuth[]\n}\n\nexport function SettingsModal({\n skillsCatalog: skillsCatalogProp,\n mcpsCatalog: mcpsCatalogProp,\n mcpsErrors: mcpsErrorsProp,\n keybindings,\n keybindingsPath,\n authentication,\n actions,\n}: {\n /**\n * Embedder fallback — discovered skills when no `<DiscoveryProvider>`\n * is mounted. In the in-app flow this is ignored; the modal reads\n * live state from `useDiscovery()` so a `ctrl+R` rescan surfaces\n * immediately even with the modal already open.\n */\n skillsCatalog?: readonly SkillConfig[]\n /** Embedder fallback — discovered MCP servers; superseded by context when present. */\n mcpsCatalog?: readonly DiscoveredMcp[]\n /** Embedder fallback — `mcps.json` parse errors; superseded by context when present. */\n mcpsErrors?: readonly DiscoveryError[]\n /**\n * Merged keybinding map. When provided, a \"Keybindings\" tab appears\n * with the full action catalog; `↵` opens `keybindings.json` via\n * `actions.onOpenKeybindings`. The UI-tab action row for the same\n * affordance is dropped when this is set (no duplicate entry points).\n */\n keybindings?: KeyBindings\n /** Absolute path to `keybindings.json`. Surfaced on the pinned edit-file button. */\n keybindingsPath?: string\n /**\n * Auth state snapshot. When provided, an \"Authentication\" tab\n * appears with the current provider + a navigable list of detected\n * ones. `↵` calls `actions.onPickProvider` for available providers\n * and falls back to `actions.onReauth` (wizard) for unavailable\n * ones + the trailing \"+ add or re-configure\" row. The Agent-tab\n * action row is dropped when this is set.\n */\n authentication?: AuthenticationView\n actions?: SettingsActions\n} = {}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n const { settings, toggle: toggleBoolean, setSetting } = useSettings()\n const authState = useMcpAuthState()\n const inputRef = useRef<InputRenderable | null>(null)\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Prefer the live discovery context when the host has wired one\n // (the standard TUI shell). Falling back to the props keeps the\n // standalone modal usable for embedders without `<DiscoveryProvider>`.\n const discovery = useDiscoveryOptional()\n const skillsCatalog = discovery?.skillsCatalog ?? skillsCatalogProp ?? []\n const mcpsCatalog = discovery?.mcpsCatalog ?? mcpsCatalogProp ?? []\n const mcpsErrors = discovery?.mcpsErrors ?? mcpsErrorsProp\n\n const skillsToggle = useEnabledToggleSet<SkillConfig>({\n catalog: skillsCatalog,\n keyOf: s => s.name,\n settingKey: 'enabledSkills',\n })\n const mcpsToggle = useEnabledToggleSet<DiscoveredMcp>({\n catalog: mcpsCatalog,\n keyOf: m => m.config.name,\n settingKey: 'enabledMcps',\n })\n\n // Tab order is computed: category tabs first (stable), then the four\n // enumerated views in the order that makes sense for navigation\n // (configuration → discovery). Keybindings + Authentication only\n // appear when the host wired the corresponding data props.\n const tabOrder = useMemo<readonly TabId[]>(() => {\n const tabs: TabId[] = [...SETTINGS_CATEGORIES.map(c => c.id)]\n if (keybindings)\n tabs.push('keybindings')\n if (authentication)\n tabs.push('authentication')\n tabs.push('skills', 'mcps')\n return tabs\n }, [keybindings, authentication])\n\n const [activeTab, setActiveTab] = useState<TabId>(tabOrder[0])\n const [cursorByTab, setCursorByTab] = useState<Record<TabId, number>>(\n () => Object.fromEntries(tabOrder.map(id => [id, 0])) as Record<TabId, number>,\n )\n const [query, setQuery] = useState('')\n\n // Guard against the active tab disappearing — if the host stops\n // providing `keybindings` / `authentication` while the modal is open\n // (rare; mostly defensive against a future SWR refresh of these\n // slots), snap back to the first category tab so the user still has\n // a working view.\n useEffect(() => {\n if (!tabOrder.includes(activeTab))\n setActiveTab(tabOrder[0])\n }, [tabOrder, activeTab])\n\n // Grab focus on mount — same reasoning as the model picker: the\n // background renderable owned focus before the modal opened, and\n // OpenTUI's prop diff doesn't always re-route focus to a freshly\n // mounted `focused` input. Force it once.\n useEffect(() => {\n inputRef.current?.focus()\n }, [])\n\n // ---- category rows ----------------------------------------------------\n // Toggle / choice rows are static; action rows depend on which\n // callbacks the host wired AND whether a dedicated tab is showing\n // the same affordance (Keybindings tab takes over `onOpenKeybindings`;\n // Authentication tab takes over `onReauth`). Skills + MCPs used to\n // live here as \"open sub-modal\" actions; they're now dedicated tabs\n // so the rows are gone too.\n //\n // Each item carries its `category` (from `settings-context`) so the\n // per-tab filter below is a simple `it.category === activeTab` check.\n const generalItems = useMemo<GeneralItem[]>(() => {\n const toggles: GeneralItem[] = SETTINGS_TOGGLES.map(t => ({ kind: 'toggle' as const, ...t }))\n const choices: GeneralItem[] = SETTINGS_CHOICES.map(c => ({ kind: 'choice' as const, ...c }))\n const acts: ActionItem[] = []\n // Fallback action rows — only emitted when the dedicated tab ISN'T\n // wired, so embedders that don't pass `keybindings` / `authentication`\n // still get the legacy \"open from a row\" affordance.\n if (actions?.onOpenKeybindings && !keybindings) {\n acts.push({\n kind: 'action',\n category: 'ui',\n id: 'keybindings',\n label: 'Keybindings',\n description: 'open ~/.zidane/keybindings.json (restart to apply)',\n onPick: actions.onOpenKeybindings,\n })\n }\n if (actions?.onReauth && !authentication) {\n acts.push({\n kind: 'action',\n category: 'agent',\n id: 'reauth',\n label: 'Authentication',\n description: 'switch provider, add another, or re-authenticate',\n onPick: actions.onReauth,\n })\n }\n return [...toggles, ...choices, ...acts]\n }, [actions, keybindings, authentication])\n\n // ---- filtering --------------------------------------------------------\n // One shared query — predictable across tabs and lets the user pivot to\n // another tab without retyping. Each item carries its own corpus so the\n // filter cost stays linear in catalog size. Category tabs additionally\n // filter by `category === tab.id` BEFORE the text filter so each tab\n // shows only its own bucket of settings.\n const filteredByCategory = useMemo(() => {\n const buckets = Object.fromEntries(\n SETTINGS_CATEGORIES.map(c => [c.id, [] as GeneralItem[]]),\n ) as Record<SettingsCategory, GeneralItem[]>\n for (const it of generalItems) {\n if (!matchesQuery(generalCorpus(it), query))\n continue\n buckets[it.category].push(it)\n }\n return buckets\n }, [generalItems, query])\n const filteredSkills = useMemo(\n () => skillsCatalog.filter(s => matchesQuery(skillCorpus(s), query)),\n [skillsCatalog, query],\n )\n const filteredMcps = useMemo(\n () => mcpsCatalog.filter(m => matchesQuery(mcpCorpus(m), query)),\n [mcpsCatalog, query],\n )\n // Filtered providers for the Authentication tab — matched against\n // label / key / method source. Without this the search input would\n // do nothing on that tab, which would look broken.\n const filteredProviders = useMemo(\n () => (authentication?.providers ?? []).filter(p => matchesQuery(providerCorpus(p), query)),\n [authentication?.providers, query],\n )\n // Keybindings tab has no cursor — its body is informational, the\n // single action (\"Edit file\") is pinned below the scrollbox. The\n // Authentication tab gets `providers.length + 1`: one row per\n // detected provider plus a final \"+ add / re-configure\" wizard\n // entry. `moveCursor` walks them; `↵` switches to the focused\n // provider (or pops the wizard for the +add row).\n const authRowCount = filteredProviders.length + (authentication ? 1 : 0)\n const filteredSize: Record<TabId, number> = {\n ...Object.fromEntries(\n SETTINGS_CATEGORIES.map(c => [c.id, filteredByCategory[c.id].length]),\n ) as Record<SettingsCategory, number>,\n skills: filteredSkills.length,\n mcps: filteredMcps.length,\n keybindings: 0,\n authentication: authRowCount,\n }\n\n // Clamp on read so a query that narrowed the list doesn't leave the\n // cursor pointing past the end — same pattern the old modal used.\n const cursor = Math.min(\n cursorByTab[activeTab],\n Math.max(0, filteredSize[activeTab] - 1),\n )\n\n // Wrap-around on write: ↑ at the top jumps to the last row, ↓ at the\n // bottom comes back to the first. The clamp-on-read above still covers\n // the \"user typed in search and the active row vanished\" case.\n const moveCursor = useCallback(\n (delta: number) =>\n setCursorByTab((prev) => {\n const size = filteredSize[activeTab]\n if (size === 0)\n return prev\n const next = (((prev[activeTab] + delta) % size) + size) % size\n return { ...prev, [activeTab]: next }\n }),\n [activeTab, filteredSize],\n )\n\n const handleQueryChange = useCallback((next: string) => {\n setQuery(next)\n // The cursor probably points at a now-filtered-out row. Anchor at the\n // first match (the natural read position after a search) — only for the\n // active tab. The other tabs keep their cursor; when the user switches,\n // they'll either find their row still in the filtered slice or land at\n // a clamped position.\n setCursorByTab(prev => ({ ...prev, [activeTab]: 0 }))\n }, [activeTab])\n\n // Auto-scroll the focused row into view whenever the cursor moves,\n // the user switches tabs, or filtering changes which row sits at\n // index 0. Deferred a frame because OpenTUI's scrollbox computes its\n // `scrollSize` during the post-commit layout pass — reading row\n // geometry synchronously after a React commit would see stale values\n // and snap to the wrong offset. Same dance the question-wizard does.\n //\n // Skipped for the Keybindings tab — it renders no anchor ids (the\n // catalog is informational, the action sits OUTSIDE the scrollbox),\n // so this would be a silent no-op. The other tabs (including\n // Authentication, where each provider row now carries an anchor)\n // get the standard cursor-snap behavior.\n useEffect(() => {\n if (activeTab === 'keybindings')\n return\n const sb = scrollboxRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollChildIntoView(anchorIdFor(cursor))\n })\n return () => cancelAnimationFrame(handle)\n }, [cursor, activeTab, query])\n\n // Reset scroll to the top whenever the user switches INTO the\n // Keybindings tab. Without this, opening Keybindings after scrolling\n // around in MCPs would leave the scrollbox at the previous offset\n // (the scrollbox instance is shared across tabs).\n useEffect(() => {\n if (activeTab !== 'keybindings')\n return\n const sb = scrollboxRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => { sb.scrollTo(0) })\n return () => cancelAnimationFrame(handle)\n }, [activeTab])\n\n // ---- focused-row context (MCP login / cancel) -------------------------\n const focusedMcp = filteredMcps[cursor]\n const focusedMcpStatus = focusedMcp\n ? getMcpAuthStatus(authState, focusedMcp.config.name)\n : undefined\n\n // While the focused MCP row is authorizing with a URL on screen, the\n // paste-back input on the right grabs focus from the search bar so\n // typing flows into it. Picker shortcuts (tab switch, arrows, etc.)\n // also hand off — except the global `escape` cancel, which still\n // reaches the parent so one keystroke cancels the in-flight login.\n const oauthPasteActive = activeTab === 'mcps'\n && focusedMcpStatus?.kind === 'authorizing'\n && !!focusedMcpStatus.url\n\n // ---- keyboard ---------------------------------------------------------\n useKeyboard((key) => {\n // Escape during in-flight OAuth: cancel + dismiss in one keystroke\n // (Modal's own esc-close fires as a sibling listener).\n if (key.name === 'escape' && oauthPasteActive && focusedMcp) {\n actions?.onCancelLoginMcp?.(focusedMcp.config.name)\n return\n }\n // While the paste input is grabbing keys, the picker's keyboard\n // affordances stay out of the way — the user is typing a URL.\n if (oauthPasteActive)\n return\n // Tab navigation — preventDefault so the focused input's cursor doesn't\n // jump alongside the tab switch. Plain ←/→ only (no modifiers) so the\n // user can still use shift-arrow for in-input selection if they need to.\n if (!key.ctrl && !key.meta && !key.shift && (key.name === 'left' || key.name === 'right')) {\n key.preventDefault()\n const idx = tabOrder.indexOf(activeTab)\n const next = key.name === 'left'\n ? tabOrder[(idx - 1 + tabOrder.length) % tabOrder.length]\n : tabOrder[(idx + 1) % tabOrder.length]\n setActiveTab(next)\n return\n }\n // In-list navigation — single-line input treats up/down as no-ops, so\n // no preventDefault needed. ctrl+P / ctrl+N kept as aliases for parity\n // with the prior modals + the other pickers in this app.\n //\n // Keybindings tab has no selectable list rows — the catalog is\n // informational, and the single action (\"Edit file\") is pinned\n // below the scrollbox. So ↑/↓ scrolls the catalog directly.\n if (key.name === 'up' || (key.ctrl && key.name === 'p')) {\n if (activeTab === 'keybindings')\n scrollboxRef.current?.scrollBy(-2)\n else\n moveCursor(-1)\n return\n }\n if (key.name === 'down' || (key.ctrl && key.name === 'n')) {\n if (activeTab === 'keybindings')\n scrollboxRef.current?.scrollBy(2)\n else\n moveCursor(1)\n return\n }\n // Page up / down — half a viewport per press. Only meaningful on\n // the Keybindings tab (every other tab leans on ↑/↓ row\n // navigation, which already auto-scrolls into view).\n if ((key.name === 'pageup' || (key.ctrl && key.name === 'b'))\n && activeTab === 'keybindings') {\n scrollboxRef.current?.scrollBy(-0.5, 'viewport')\n return\n }\n if ((key.name === 'pagedown' || (key.ctrl && key.name === 'f'))\n && activeTab === 'keybindings') {\n scrollboxRef.current?.scrollBy(0.5, 'viewport')\n return\n }\n if (key.name === 'home' && activeTab === 'keybindings') {\n scrollboxRef.current?.scrollTo(0)\n return\n }\n if (key.name === 'end' && activeTab === 'keybindings') {\n const sb = scrollboxRef.current\n if (sb)\n sb.scrollTo({ x: 0, y: sb.scrollHeight })\n return\n }\n\n if (key.name === 'return') {\n if (isCategoryTab(activeTab)) {\n const it = filteredByCategory[activeTab][cursor]\n if (!it)\n return\n if (it.kind === 'toggle') {\n toggleBoolean(it.key)\n }\n else if (it.kind === 'choice') {\n const current = settings[it.key]\n const idx = it.options.findIndex(o => o.value === current)\n const next = it.options[(idx + 1) % it.options.length]\n if (next)\n setSetting(it.key, next.value as Settings[typeof it.key])\n }\n else {\n it.onPick()\n }\n return\n }\n if (activeTab === 'skills') {\n const s = filteredSkills[cursor]\n if (s)\n skillsToggle.toggle(s.name)\n return\n }\n if (activeTab === 'mcps') {\n const m = filteredMcps[cursor]\n if (m)\n mcpsToggle.toggle(m.config.name)\n return\n }\n if (activeTab === 'keybindings') {\n // ↵ on the Keybindings tab opens `keybindings.json` in\n // `$EDITOR`. The catalog is informational; the action button\n // sits pinned below the scrollbox so the affordance is always\n // visible regardless of scroll offset.\n actions?.onOpenKeybindings?.()\n return\n }\n if (activeTab === 'authentication') {\n // Last row is the \"+ add or re-configure\" wizard entry —\n // unconditionally pops the full AuthScreen flow. Provider\n // rows above it call `onPickProvider` for available creds;\n // unavailable rows fall back to the wizard too, since\n // selecting them would land the user on a session with no\n // working credentials.\n const isAddRow = cursor === filteredProviders.length\n if (isAddRow || !filteredProviders[cursor]) {\n actions?.onReauth?.()\n return\n }\n const provider = filteredProviders[cursor]\n if (provider.available && actions?.onPickProvider) {\n actions.onPickProvider(provider)\n return\n }\n actions?.onReauth?.()\n return\n }\n return\n }\n\n // MCP-specific actions. Plain `l` / `o` (used by the old modal) would\n // be swallowed by the search input here, so we promote them to ctrl-\n // variants which the input ignores entirely.\n if (activeTab === 'mcps' && focusedMcp && focusedMcpStatus) {\n if (key.ctrl && key.name === 'l' && canLogin(focusedMcp, focusedMcpStatus)) {\n void actions?.onLoginMcp?.(focusedMcp.config.name)\n return\n }\n if (key.ctrl && key.name === 'o' && canLogout(focusedMcpStatus)) {\n actions?.onLogoutMcp?.(focusedMcp.config.name)\n return\n }\n // Cancel an in-flight authorize on esc — Modal's own esc-close fires\n // alongside ours (sibling listeners, no propagation chain), so this\n // single keystroke both aborts the login AND dismisses the modal.\n // Same UX as the prior standalone MCP modal.\n if (key.name === 'escape' && focusedMcpStatus.kind === 'authorizing') {\n actions?.onCancelLoginMcp?.(focusedMcp.config.name)\n }\n }\n\n // Catalog refresh — Skills + MCPs tabs only. `ctrl+R` because plain\n // `r` would be swallowed by the search input. The host promise\n // resolves into the modal automatically through the catalog prop;\n // no in-flight state needed — discovery is sub-100ms in practice.\n if (key.ctrl && key.name === 'r') {\n if (activeTab === 'skills' && actions?.onRefreshSkills) {\n void actions.onRefreshSkills()\n return\n }\n if (activeTab === 'mcps' && actions?.onRefreshMcps) {\n void actions.onRefreshMcps()\n }\n }\n })\n\n // ---- render -----------------------------------------------------------\n // Per-category totals are the unfiltered count for that bucket — same\n // semantics as the skills/MCPs `total` (catalog size, not catalog\n // after filtering). Lets the tab badge show `M/N` when a query\n // narrows the visible list.\n const totalsByCategory = useMemo(() => {\n const t = Object.fromEntries(SETTINGS_CATEGORIES.map(c => [c.id, 0])) as Record<SettingsCategory, number>\n for (const it of generalItems)\n t[it.category]++\n return t\n }, [generalItems])\n // Keybindings + Authentication tabs are intentionally absent from\n // `tabCounts` — they're read-mostly views without \"M of N matched\n // the search\" semantics, so showing a number next to their label\n // would be misleading. TabStrip omits the badge when the entry is\n // missing.\n const tabCounts: Partial<Record<TabId, { current: number, total: number }>> = {\n ...Object.fromEntries(SETTINGS_CATEGORIES.map(c => [\n c.id,\n { current: filteredByCategory[c.id].length, total: totalsByCategory[c.id] },\n ])) as Record<SettingsCategory, { current: number, total: number }>,\n skills: { current: filteredSkills.length, total: skillsCatalog.length },\n mcps: { current: filteredMcps.length, total: mcpsCatalog.length },\n }\n\n return (\n <Modal\n title=\"settings\"\n // Sized a notch wider + taller than the legacy modal so the\n // Keybindings catalog (longer per-row labels) + Authentication\n // status block sit comfortably. Same `minWidth` as before so\n // narrow terminals (≥ 76 cols) still get a usable layout.\n maxWidth={160}\n minWidth={76}\n maxHeight={50}\n horizontalMargin={2}\n verticalMargin={1}\n >\n {/*\n Fixed-height chrome (tab strip + search input + footer hints) sets\n `flexShrink: 0` so the scrollbox is the only thing that gives\n ground when the terminal is short — the layout never breaks past\n the modal's bottom border, the list just gets shorter and scrolls.\n */}\n <box style={{ flexShrink: 0 }}>\n <TabStrip order={tabOrder} active={activeTab} counts={tabCounts} />\n </box>\n\n <box\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n flexShrink: 0,\n }}\n >\n <input\n ref={inputRef}\n focused={!oauthPasteActive}\n placeholder={searchPlaceholder(activeTab)}\n onInput={handleQueryChange}\n onSubmit={() => {}}\n style={{ flexGrow: 1 }}\n />\n </box>\n\n <scrollbox\n ref={scrollboxRef}\n // Never grabs focus — our top-level `useKeyboard` owns navigation.\n // The mouse wheel still scrolls the viewport for manual review.\n focusable={false}\n style={{ flexGrow: 1, flexShrink: 1, minHeight: 4 }}\n // No sticky-to-bottom — settings lists are anchored top-to-bottom\n // and the user expects ↑/↓ to drive position, not \"follow new\n // entries\" semantics.\n stickyScroll={false}\n >\n {isCategoryTab(activeTab) && (\n <GeneralList\n items={filteredByCategory[activeTab]}\n cursor={cursor}\n highlightBg={SURFACE.selection}\n query={query}\n />\n )}\n {activeTab === 'skills' && (\n <SkillsList\n items={filteredSkills}\n enabledSet={skillsToggle.enabledSet}\n totalCount={skillsCatalog.length}\n cursor={cursor}\n highlightBg={SURFACE.selection}\n query={query}\n />\n )}\n {activeTab === 'mcps' && (\n <McpsList\n items={filteredMcps}\n enabledSet={mcpsToggle.enabledSet}\n totalCount={mcpsCatalog.length}\n cursor={cursor}\n highlightBg={SURFACE.selection}\n query={query}\n errors={mcpsErrors}\n authState={authState}\n />\n )}\n {activeTab === 'keybindings' && keybindings && (\n <KeybindingsList bindings={keybindings} query={query} />\n )}\n {activeTab === 'authentication' && authentication && (\n <AuthenticationList\n view={authentication}\n providers={filteredProviders}\n cursor={cursor}\n highlightBg={SURFACE.selection}\n query={query}\n />\n )}\n </scrollbox>\n\n {activeTab === 'keybindings' && (\n <box style={{ flexShrink: 0 }}>\n <KeybindingsEditFileButton filePath={keybindingsPath} highlightBg={SURFACE.selection} />\n </box>\n )}\n\n {activeTab === 'mcps' && focusedMcp && focusedMcpStatus && (\n <box style={{ flexShrink: 0 }}>\n {renderMcpDetailPanel(focusedMcp, focusedMcpStatus, COLOR)}\n </box>\n )}\n\n <box style={{ flexShrink: 0 }}>\n <Hints\n activeTab={activeTab}\n focusedMcp={focusedMcp}\n focusedMcpStatus={focusedMcpStatus}\n canRefreshSkills={!!actions?.onRefreshSkills}\n canRefreshMcps={!!actions?.onRefreshMcps}\n />\n </box>\n </Modal>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Tab strip — one row, brand-highlighted active tab on a selection-color\n// background. Inactive tabs use the mute palette so the active one wins\n// the user's attention without any glyphs competing.\n// ---------------------------------------------------------------------------\n\nfunction TabStrip({\n order,\n active,\n counts,\n}: {\n order: readonly TabId[]\n active: TabId\n counts: Partial<Record<TabId, { current: number, total: number }>>\n}) {\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n return (\n <box style={{ flexDirection: 'row', height: 1 }}>\n {order.map((id, i) => {\n const isActive = id === active\n const label = TAB_LABELS[id]\n const tabCounts = counts[id]\n // Read-only tabs (Keybindings) don't carry \"current/total\"\n // semantics — skip the badge entirely there so the strip\n // doesn't show a misleading `0` next to it.\n const showBadge = !!tabCounts\n const badge = tabCounts\n ? (tabCounts.current !== tabCounts.total ? `${tabCounts.current}/${tabCounts.total}` : `${tabCounts.total}`)\n : ''\n return (\n <box\n key={id}\n style={{\n paddingLeft: 1,\n paddingRight: 1,\n marginRight: i === order.length - 1 ? 0 : 1,\n backgroundColor: isActive ? SURFACE.selection : undefined,\n }}\n >\n <text wrapMode=\"none\">\n <span fg={isActive ? COLOR.brand : COLOR.dim}>{label}</span>\n {showBadge && (\n <span fg={isActive ? COLOR.brand : COLOR.mute}>{` ${badge}`}</span>\n )}\n </text>\n </box>\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// General tab — toggles + choices + actions (auth, keybindings).\n// Each row renders inside an `<box id=...>` so the modal's scrollbox can\n// `scrollChildIntoView` on cursor moves.\n// ---------------------------------------------------------------------------\n\nfunction GeneralList({\n items,\n cursor,\n highlightBg,\n query,\n}: {\n items: readonly GeneralItem[]\n cursor: number\n highlightBg: string\n query: string\n}) {\n const { settings } = useSettings()\n if (items.length === 0)\n return <EmptyRow label={query ? `no settings match \"${query}\"` : 'no settings'} />\n return (\n <box style={{ flexDirection: 'column' }}>\n {items.map((item, i) => {\n const focused = i === cursor\n const bg = focused ? highlightBg : undefined\n const id = anchorIdFor(i)\n if (item.kind === 'toggle') {\n return (\n <ToggleRow\n key={item.key}\n id={id}\n label={item.label}\n description={item.description}\n enabled={settings[item.key]}\n focused={focused}\n bg={bg}\n />\n )\n }\n if (item.kind === 'choice') {\n const current = settings[item.key]\n const opt = item.options.find(o => o.value === current)\n return (\n <ChoiceRow\n key={item.key}\n id={id}\n label={item.label}\n description={item.description}\n value={opt?.label ?? String(current)}\n cyclable={item.options.length > 1}\n focused={focused}\n bg={bg}\n />\n )\n }\n return (\n <ActionRow\n key={item.id}\n id={id}\n label={item.label}\n description={item.description}\n focused={focused}\n bg={bg}\n />\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Skills tab — checkbox list with name + description.\n// ---------------------------------------------------------------------------\n\nfunction SkillsList({\n items,\n enabledSet,\n totalCount,\n cursor,\n highlightBg,\n query,\n}: {\n items: readonly SkillConfig[]\n enabledSet: ReadonlySet<string>\n totalCount: number\n cursor: number\n highlightBg: string\n query: string\n}) {\n const COLOR = useColors()\n if (totalCount === 0) {\n return (\n <box style={{ flexDirection: 'column' }}>\n <text fg={COLOR.dim}>No skills discovered.</text>\n <text fg={COLOR.mute}>\n Drop a\n <span fg={COLOR.model}>{' SKILL.md '}</span>\n into\n <span fg={COLOR.model}>{' .zidane/skills/<name>/ '}</span>\n or\n <span fg={COLOR.model}>{' .agents/skills/<name>/ '}</span>\n (project or\n <span fg={COLOR.model}>{' ~/'}</span>\n ).\n </text>\n </box>\n )\n }\n if (items.length === 0)\n return <EmptyRow label={`no skills match \"${query}\"`} />\n return (\n <box style={{ flexDirection: 'column' }}>\n {items.map((entry, i) => {\n const focused = i === cursor\n const bg = focused ? highlightBg : undefined\n const enabled = enabledSet.has(entry.name)\n return (\n <box\n key={entry.name}\n id={anchorIdFor(i)}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{entry.name}</span>\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>\n {`${COL_TITLE}${entry.description ?? ''}`}\n </text>\n </box>\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// MCPs tab — checkbox list with name + auth badge on line 1, transport\n// detail on line 2. Errors render as a preamble; per-row status badges\n// come from `useMcpAuthState`.\n// ---------------------------------------------------------------------------\n\nfunction McpsList({\n items,\n enabledSet,\n totalCount,\n cursor,\n highlightBg,\n query,\n errors,\n authState,\n}: {\n items: readonly DiscoveredMcp[]\n enabledSet: ReadonlySet<string>\n totalCount: number\n cursor: number\n highlightBg: string\n query: string\n errors: readonly DiscoveryError[] | undefined\n authState: ReturnType<typeof useMcpAuthState>\n}) {\n const COLOR = useColors()\n const home = homedir()\n if (totalCount === 0) {\n return (\n <box style={{ flexDirection: 'column' }}>\n {renderMcpErrors(errors, home, COLOR.warn)}\n <text fg={COLOR.dim}>No MCP servers discovered.</text>\n <text fg={COLOR.mute}>\n Drop a\n <span fg={COLOR.model}>{' mcps.json '}</span>\n (or\n <span fg={COLOR.model}>{' mcp.json '}</span>\n ) into\n <span fg={COLOR.model}>{' .zidane/ '}</span>\n or\n <span fg={COLOR.model}>{' .agents/ '}</span>\n (project or\n <span fg={COLOR.model}>{' ~/'}</span>\n ).\n </text>\n </box>\n )\n }\n if (items.length === 0) {\n return (\n <box style={{ flexDirection: 'column' }}>\n {renderMcpErrors(errors, home, COLOR.warn)}\n <EmptyRow label={`no servers match \"${query}\"`} />\n </box>\n )\n }\n return (\n <box style={{ flexDirection: 'column' }}>\n {renderMcpErrors(errors, home, COLOR.warn)}\n {items.map((entry, i) => {\n const focused = i === cursor\n const bg = focused ? highlightBg : undefined\n const name = entry.config.name\n const enabled = enabledSet.has(name)\n const status = getMcpAuthStatus(authState, name)\n return (\n <box\n key={name}\n id={anchorIdFor(i)}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{name}</span>\n {renderInlineMcpBadge(status, COLOR)}\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>\n {`${COL_TITLE}${mcpDetail(entry)}`}\n </text>\n </box>\n )\n })}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Keybindings tab — read-only catalog of every action shortcut. No\n// per-row cursor — ↑/↓ scrolls the catalog directly (see the modal's\n// keyboard handler), and the single action (\"Edit file\") lives in\n// `KeybindingsEditFileButton` pinned BELOW the scrollbox so it stays\n// visible regardless of scroll offset. `↵` (top-level handler) opens\n// `keybindings.json` in `$EDITOR`.\n//\n// Mirrors the standalone `KeybindingsModal` so users see the same\n// content from either entry point — the shared `groupBindings` +\n// `KEYBINDING_KEY_COL_WIDTH` helpers come from `chat/keybindings`.\n// ---------------------------------------------------------------------------\n\nfunction KeybindingsList({\n bindings,\n query,\n}: {\n bindings: KeyBindings\n query: string\n}) {\n const sections = useMemo(() => groupBindings(bindings), [bindings])\n // Pre-filter sections by query — drop rows whose `(key, label,\n // description, action)` corpus doesn't contain every search term;\n // drop a section entirely once its rows are empty. The shared\n // `KeybindingsCatalog` component below renders whatever it receives,\n // so the filter lives here.\n const filteredSections = useMemo(() => {\n if (!query.trim())\n return sections\n return sections\n .map(section => ({\n group: section.group,\n rows: section.rows.filter(row => matchesQuery(keybindingCorpus(row), query)),\n }))\n .filter(s => s.rows.length > 0)\n }, [sections, query])\n if (filteredSections.length === 0)\n return <EmptyRow label={`no keybindings match \"${query}\"`} />\n return <KeybindingsCatalog sections={filteredSections} />\n}\n\n// ---------------------------------------------------------------------------\n// Authentication tab — current provider summary + a navigable list of\n// detected providers (credentials present / missing) + a trailing\n// \"+ add / re-configure\" entry that pops the full AuthScreen wizard.\n//\n// Each provider row is cursor-selectable (auto-scrolled into view via\n// the parent's `scrollChildIntoView` cycle using `anchorIdFor(i)`).\n// `↵` on an available provider switches the active provider; on an\n// unavailable one OR the trailing +add row, falls back to the wizard.\n// ---------------------------------------------------------------------------\n\nfunction AuthenticationList({\n view,\n providers,\n cursor,\n highlightBg,\n query,\n}: {\n view: AuthenticationView\n providers: readonly ProviderAuth[]\n cursor: number\n highlightBg: string\n query: string\n}) {\n const COLOR = useColors()\n const home = homedir()\n const totalProviders = view.providers?.length ?? 0\n // Cursor index that selects the trailing \"+ add\" row — providers\n // come first, the +add row sits one past the last provider.\n const addRowIndex = providers.length\n return (\n <box style={{ flexDirection: 'column' }}>\n <box style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1 }}>\n <text wrapMode=\"none\">\n <span fg={COLOR.brand}>Current</span>\n </text>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' provider '}</span>\n <span fg={view.currentProviderLabel ? COLOR.accent : COLOR.mute}>\n {view.currentProviderLabel ?? '— not selected —'}\n </span>\n {view.currentProviderKey\n && view.currentProviderLabel\n && view.currentProviderKey !== view.currentProviderLabel.toLowerCase() && (\n <span fg={COLOR.dim}>{` (${view.currentProviderKey})`}</span>\n )}\n </text>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' model '}</span>\n <span fg={view.currentModel ? COLOR.model : COLOR.mute}>\n {view.currentModel ?? '— not selected —'}\n </span>\n </text>\n </box>\n <box style={{ flexDirection: 'column', flexShrink: 0, marginTop: 1, paddingLeft: 1, paddingRight: 1 }}>\n <text wrapMode=\"none\">\n <span fg={COLOR.brand}>Providers</span>\n <span fg={COLOR.mute}>{totalProviders > 0 ? ` ${providers.length}/${totalProviders}` : ''}</span>\n </text>\n </box>\n {totalProviders === 0 && (\n <box style={{ paddingLeft: 3, paddingRight: 1 }}>\n <text fg={COLOR.mute}>No providers registered.</text>\n </box>\n )}\n {totalProviders > 0 && providers.length === 0 && (\n <EmptyRow label={`no providers match \"${query}\"`} />\n )}\n {providers.map((p, i) => {\n const isCurrent = view.currentProviderKey === p.key\n const focused = i === cursor\n const bg = focused ? highlightBg : undefined\n // `▶` (focus) wins over `▸` (current) when both apply — the\n // cursor is the more transient of the two and needs the\n // stronger visual hit.\n const marker = focused ? '▶ ' : isCurrent ? '▸ ' : ' '\n // Cursor focus + active-provider both highlight brand; an\n // unfocused row falls back to dim (available) or mute\n // (missing credentials).\n const labelColor = focused || isCurrent\n ? COLOR.brand\n : p.available\n ? COLOR.dim\n : COLOR.mute\n return (\n <box\n key={p.key}\n id={anchorIdFor(i)}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{marker}</span>\n <span fg={labelColor}>{p.label}</span>\n <span fg={p.available ? COLOR.accent : COLOR.mute}>\n {p.available ? ' ✓ configured' : ' · not configured'}\n </span>\n {isCurrent && (\n <span fg={COLOR.mute}>{' (active)'}</span>\n )}\n </text>\n {p.methods.length > 0 && (\n <text wrapMode=\"none\" fg={COLOR.mute}>\n {` ${p.methods.map(m => `${m.source}: ${displayPath(m.detail, home)}`).join(' · ')}`}\n </text>\n )}\n </box>\n )\n })}\n {/*\n Trailing wizard entry — ALWAYS rendered. It's the escape\n hatch (\"re-run setup\") even when every provider is already\n configured AND it stays visible when the query filters every\n provider out (so ↵ at `cursor = filteredProviders.length`\n targets a row the user can see, not a hidden one).\n */}\n <AuthAddRow\n anchorIndex={addRowIndex}\n focused={cursor === addRowIndex}\n highlightBg={highlightBg}\n />\n </box>\n )\n}\n\nfunction AuthAddRow({ anchorIndex, focused, highlightBg }: { anchorIndex: number, focused: boolean, highlightBg: string }) {\n const COLOR = useColors()\n const bg = focused ? highlightBg : undefined\n return (\n <box\n id={anchorIdFor(anchorIndex)}\n style={{ flexDirection: 'column', flexShrink: 0, marginTop: 1, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>+ add or re-configure a provider</span>\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>\n {' launch the setup wizard'}\n </text>\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Row primitives (general tab)\n// ---------------------------------------------------------------------------\n\n// All row variants render on TWO lines: title (+ selection / value /\n// trailing affordance) on line 1, description on line 2 indented to\n// align with the title column. Keeping the description on its own\n// row stops the rows from competing for horizontal space on narrow\n// terminals (where the prior single-line layout truncated descriptions\n// at the visible edge) and gives every entry the same vertical rhythm.\n\n// `flexShrink: 0` on every row keeps the 2-line geometry intact inside\n// the scrollbox — without it Yoga would compress rows to a single line\n// when the modal's height is tight, and the description would vanish.\n\nfunction ToggleRow({\n id,\n label,\n description,\n enabled,\n focused,\n bg,\n}: {\n id: string\n label: string\n description: string\n enabled: boolean\n focused: boolean\n bg: string | undefined\n}) {\n const COLOR = useColors()\n return (\n <box\n id={id}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{label}</span>\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>{`${COL_TITLE}${description}`}</text>\n </box>\n )\n}\n\n// Choice + action rows pad the checkbox slot with whitespace (instead of\n// `[ ]`) so the title text still lands at column 6 — same column the\n// toggle / skill / MCP rows put their labels in. Without the spacer the\n// \"Auto-compact threshold\", \"Theme\", \"Keybindings\", \"Authentication\"\n// rows would sit at column 2 and the list would read as two staggered\n// columns instead of one.\n\nfunction ChoiceRow({\n id,\n label,\n description,\n value,\n cyclable,\n focused,\n bg,\n}: {\n id: string\n label: string\n description: string\n value: string\n cyclable: boolean\n focused: boolean\n bg: string | undefined\n}) {\n const COLOR = useColors()\n return (\n <box\n id={id}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={COLOR.mute}>{SPACER_CHECKBOX_WIDTH}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{label}</span>\n <span fg={COLOR.mute}>: </span>\n <span fg={focused ? COLOR.brand : COLOR.accent}>{value}</span>\n {focused && cyclable && <span fg={COLOR.brand}>{' ↻'}</span>}\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>{`${COL_TITLE}${description}`}</text>\n </box>\n )\n}\n\nfunction ActionRow({\n id,\n label,\n description,\n focused,\n bg,\n}: {\n id: string\n label: string\n description: string\n focused: boolean\n bg: string | undefined\n}) {\n const COLOR = useColors()\n return (\n <box\n id={id}\n style={{ flexDirection: 'column', flexShrink: 0, paddingLeft: 1, paddingRight: 1, backgroundColor: bg }}\n >\n <text wrapMode=\"none\">\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={COLOR.mute}>{SPACER_CHECKBOX_WIDTH}</span>\n <span fg={focused ? COLOR.brand : COLOR.accent}>{label}</span>\n {focused && <span fg={COLOR.brand}>{' ›'}</span>}\n </text>\n <text wrapMode=\"none\" fg={COLOR.mute}>{`${COL_TITLE}${description}`}</text>\n </box>\n )\n}\n\nfunction EmptyRow({ label }: { label: string }) {\n const COLOR = useColors()\n return <text fg={COLOR.mute}>{` ${label}`}</text>\n}\n\n// ---------------------------------------------------------------------------\n// MCP detail panel — only renders when there's verbose info that doesn't\n// fit on the row (authorizing URL, error message). Same content the prior\n// standalone McpsSettingsModal showed below its list.\n// ---------------------------------------------------------------------------\n\nfunction renderMcpDetailPanel(\n entry: DiscoveredMcp,\n status: McpAuthStatus,\n COLOR: ReturnType<typeof useColors>,\n): ReactNode {\n if (status.kind === 'authorizing') {\n if (!status.url) {\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.brand}>{`Authorizing ${entry.config.name}`}</text>\n <text fg={COLOR.dim}>Starting login flow…</text>\n </box>\n )\n }\n // `McpAuthorizingPanel` owns the paste-back input + handler.\n // SettingsModal maxWidth=160, padding=2 each side, border=1 each\n // side → content area ≤ 154 cols.\n return (\n <McpAuthorizingPanel\n serverName={entry.config.name}\n authUrl={status.url}\n maxLineWidth={154}\n inputFocused\n />\n )\n }\n if (status.kind === 'error') {\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.error}>{`Login failed: ${entry.config.name}`}</text>\n <text fg={COLOR.dim}>{status.error}</text>\n <text fg={COLOR.mute}>\n Press\n {' '}\n <span fg={COLOR.warn}>ctrl+L</span>\n {' '}\n to retry.\n </text>\n </box>\n )\n }\n return null\n}\n\nfunction renderInlineMcpBadge(status: McpAuthStatus, COLOR: ReturnType<typeof useColors>): ReactNode {\n switch (status.kind) {\n case 'idle':\n return null\n case 'authed':\n return (\n <span fg={COLOR.accent}>\n {' '}\n ✓ authed\n </span>\n )\n case 'needs-auth':\n return (\n <span fg={COLOR.warn}>\n {' '}\n ! needs login\n </span>\n )\n case 'authorizing':\n return (\n <span fg={COLOR.warn}>\n {' '}\n … authorizing\n </span>\n )\n case 'error':\n return (\n <span fg={COLOR.error}>\n {' '}\n ✗ login failed\n </span>\n )\n }\n}\n\nfunction renderMcpErrors(\n errors: readonly DiscoveryError[] | undefined,\n home: string,\n warnColor: string,\n): ReactNode {\n if (!errors || errors.length === 0)\n return null\n return (\n <box style={{ flexDirection: 'column' }}>\n {errors.map(err => (\n <text key={err.path} fg={warnColor}>\n {`! ${displayPath(err.path, home)}: ${err.message}`}\n </text>\n ))}\n </box>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Footer hints — dynamic per tab + MCP focus state.\n// ---------------------------------------------------------------------------\n\nfunction Hints({\n activeTab,\n focusedMcp,\n focusedMcpStatus,\n canRefreshSkills,\n canRefreshMcps,\n}: {\n activeTab: TabId\n focusedMcp: DiscoveredMcp | undefined\n focusedMcpStatus: McpAuthStatus | undefined\n canRefreshSkills: boolean\n canRefreshMcps: boolean\n}) {\n const COLOR = useColors()\n const showLogin = activeTab === 'mcps'\n && !!focusedMcp\n && !!focusedMcpStatus\n && canLogin(focusedMcp, focusedMcpStatus)\n const showLogout = activeTab === 'mcps'\n && !!focusedMcpStatus\n && canLogout(focusedMcpStatus)\n const showCancel = activeTab === 'mcps' && focusedMcpStatus?.kind === 'authorizing'\n const showRefresh = (activeTab === 'skills' && canRefreshSkills)\n || (activeTab === 'mcps' && canRefreshMcps)\n return (\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>←→</span>\n {' tabs · '}\n <span fg={COLOR.warn}>↑↓</span>\n {activeTab === 'keybindings' ? ' scroll · ' : ' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {isCategoryTab(activeTab)\n ? ' toggle/cycle/select'\n : activeTab === 'keybindings'\n ? ' edit file'\n : activeTab === 'authentication'\n ? ' switch / re-authenticate'\n : ' toggle'}\n {showLogin && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>ctrl+L</span>\n {' login'}\n </span>\n )}\n {showLogout && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>ctrl+O</span>\n {' logout'}\n </span>\n )}\n {showRefresh && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>ctrl+R</span>\n {' refresh'}\n </span>\n )}\n {showCancel\n ? (\n <span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </span>\n )\n : (\n <span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </span>\n )}\n </text>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Helpers — pure\n// ---------------------------------------------------------------------------\n\nfunction matchesQuery(corpus: string, query: string): boolean {\n const trimmed = query.trim().toLowerCase()\n if (!trimmed)\n return true\n return trimmed.split(/\\s+/).every(term => corpus.includes(term))\n}\n\nfunction generalCorpus(item: GeneralItem): string {\n const parts: string[] = [item.label, item.description]\n if (item.kind === 'choice')\n parts.push(...item.options.map(o => o.label))\n return parts.join(' ').toLowerCase()\n}\n\nfunction skillCorpus(s: SkillConfig): string {\n return `${s.name} ${s.description ?? ''}`.toLowerCase()\n}\n\nfunction mcpCorpus(m: DiscoveredMcp): string {\n return [\n m.config.name,\n m.config.transport,\n m.config.command ?? '',\n m.config.url ?? '',\n (m.config.args ?? []).join(' '),\n ].join(' ').toLowerCase()\n}\n\nfunction keybindingCorpus(row: { def: { label: string, description: string, action: string }, spec: string }): string {\n return `${row.spec} ${row.def.label} ${row.def.description} ${row.def.action}`.toLowerCase()\n}\n\nfunction providerCorpus(p: ProviderAuth): string {\n return `${p.label} ${p.key} ${p.methods.map(m => `${m.source} ${m.detail}`).join(' ')}`.toLowerCase()\n}\n\nfunction mcpDetail(entry: DiscoveredMcp): string {\n const transport = entry.config.transport\n const detail = transport === 'stdio'\n ? entry.config.command ?? ''\n : entry.config.url ?? ''\n return `${transport} · ${detail}`\n}\n\nfunction canLogin(entry: DiscoveredMcp, status: McpAuthStatus): boolean {\n // OAuth only applies to HTTP-style transports; stdio servers can't speak it.\n // `idle` is excluded too — we'd be guessing before bootstrap has had a\n // chance to flip the row to `needs-auth` itself.\n if (entry.config.transport === 'stdio')\n return false\n return status.kind === 'needs-auth' || status.kind === 'error'\n}\n\nfunction canLogout(status: McpAuthStatus): boolean {\n return status.kind === 'authed' || status.kind === 'error' || status.kind === 'authorizing'\n}\n\nfunction searchPlaceholder(tab: TabId): string {\n if (tab === 'skills')\n return 'search skills — name, description…'\n if (tab === 'mcps')\n return 'search MCP servers — name, transport, command/URL…'\n if (tab === 'keybindings')\n return 'search keybindings — action, key, description…'\n if (tab === 'authentication')\n return 'search providers — name, method…'\n // Category tabs (agent / ui): same lexical UX, just a hint about\n // which bucket is currently visible so a search that returns nothing\n // doesn't look broken.\n const label = TAB_LABELS[tab].toLowerCase()\n return `search ${label} settings — name, description…`\n}\n\nfunction displayPath(path: string, home: string): string {\n if (home && path.startsWith(`${home}/`))\n return `~/${path.slice(home.length + 1)}`\n return path\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ScrollBoxRenderable } from '@opentui/core'\nimport type { Agent } from '../agent'\nimport type { TodoItem, TodoStatus } from '../chat/todos'\nimport type { Session } from '../session'\nimport { useTerminalDimensions } from '@opentui/react'\nimport { useEffect, useRef, useState } from 'react'\nimport { useColors } from '../chat/theme-context'\nimport { TODO_STATUS_GLYPHS, TODOWRITE_TOOL, useActiveTodos } from '../chat/todos'\nimport { Modal } from './modal'\n\n// ---------------------------------------------------------------------------\n// TodosModal — read-only viewer for the active run's `todowrite` list.\n//\n// Layout:\n// ┌─ todos ─────────────────── 1 in progress · 3 completed ─┐\n// │ ☑ first completed task │\n// │ ☑ second completed task │\n// │ ☑ third completed task │\n// │ ◐ currently working on this one │\n// │ ☐ next pending task │\n// │ ☐ another pending task │\n// └──────────────────────────────── 6 items · esc close ────┘\n//\n// The body is purely the list — the title + right-title + bottom-title\n// carry all the meta (status, totals, dismissal hint). When the live\n// slot is empty but an `archive` snapshot exists (auto-clear after a\n// run of all-completed items), the body renders the archived list with\n// no extra banner — the right-title's `N completed` already signals\n// what the user is looking at.\n//\n// Read-only by design — no toggles, no reorder, no delete. The model\n// drives the list via `todowrite`; this surface is purely \"what does\n// the agent think it's doing right now?\".\n// ---------------------------------------------------------------------------\n\ninterface TodosModalProps {\n /** Active session — null on the auth / sessions screens. */\n session: Session | null\n /**\n * Active agent. Used to subscribe to `tool:after` for live updates —\n * `<ModalRoot>` sits ABOVE `<AppShell>` in the React tree, so it\n * doesn't re-render on the streaming-buffer cascade; without an\n * explicit subscription the modal would freeze at the snapshot taken\n * when `ctrl+t` was pressed. Optional so unit tests / non-live hosts\n * can render the modal against a static session.\n */\n agent?: Agent | null\n}\n\n/** Floor on the modal height — keeps the header + at least a few rows visible on tiny terminals. */\nconst MIN_MODAL_HEIGHT = 12\n/** Ceiling on the modal height — prevents a giant list from blanket-filling a tall window. */\nconst MAX_MODAL_HEIGHT = 36\n\n/**\n * Per-status palette for a row — the glyph and the content text pick\n * different tones so a quick scan reads the status from the glyph color\n * while the row content stays in a calm reading tone:\n *\n * pending glyph `mute` · text `dim` — queued / not yet started\n * in_progress glyph `warn` · text `brand` — currently working (loudest)\n * completed glyph `accent` · text `dim` — done (success glyph, calm text)\n * cancelled glyph `error` · text `mute` — explicitly dropped\n *\n * All tokens come from the active theme — a runtime theme switch\n * repaints without touching this component.\n */\nfunction statusColors(\n status: TodoStatus,\n COLOR: ReturnType<typeof useColors>,\n): { glyph: string, text: string } {\n switch (status) {\n case 'in_progress':\n return { glyph: COLOR.warn, text: COLOR.brand }\n case 'completed':\n return { glyph: COLOR.accent, text: COLOR.dim }\n case 'cancelled':\n return { glyph: COLOR.error, text: COLOR.mute }\n case 'pending':\n default:\n return { glyph: COLOR.mute, text: COLOR.dim }\n }\n}\n\nfunction TodoRow({ item, rowId }: { item: TodoItem, rowId?: string }) {\n const COLOR = useColors()\n const colors = statusColors(item.status, COLOR)\n return (\n <box id={rowId} style={{ flexShrink: 0, flexDirection: 'row' }}>\n <text wrapMode=\"word\">\n <span fg={colors.glyph}>{TODO_STATUS_GLYPHS[item.status]}</span>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={colors.text}>{item.content}</span>\n </text>\n </box>\n )\n}\n\nexport function TodosModal({ session, agent }: TodosModalProps) {\n const COLOR = useColors()\n const { height: termHeight } = useTerminalDimensions()\n // Force a re-render on every `todowrite` so the modal reflects the\n // model's latest checkpoint while it's open. `selectActiveTodos`\n // reads `session.metadata.todos` directly — outside React state —\n // so without this tick the modal would stay frozen at its open-time\n // snapshot. Listening for `todowrite` specifically (not every tool)\n // keeps the cost trivial.\n const [, setTick] = useState(0)\n useEffect(() => {\n if (!agent)\n return\n const unregister = agent.hooks.hook('tool:after', (ctx) => {\n if (ctx.name === TODOWRITE_TOOL)\n setTick(t => t + 1)\n })\n return unregister\n }, [agent])\n const state = useActiveTodos(session)\n const scrollRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Auto-scroll to the first in-progress row when the modal opens so a\n // long list doesn't bury the live item below the visible window.\n // Deferred a frame for the same reason `MultiEditApprovalModal` does\n // — `scrollChildIntoView` needs the scrollbox's measured size, which\n // is only available post-commit.\n const inProgressId = state.inProgress?.id ?? null\n useEffect(() => {\n if (!inProgressId)\n return\n const sb = scrollRef.current\n if (!sb)\n return\n const handle = requestAnimationFrame(() => {\n sb.scrollChildIntoView(`todo-row-${inProgressId}`)\n })\n return () => cancelAnimationFrame(handle)\n }, [inProgressId])\n\n // Cap the modal at roughly two-thirds of the terminal — enough for a\n // long list to scroll without overpowering the chat behind it. The\n // floor keeps the header + at least a few rows visible on tiny\n // terminals; the cap prevents a giant list from blanket-filling a\n // tall window.\n const idealHeight = Math.floor((termHeight - 4) * 0.66)\n const maxHeight = Math.max(MIN_MODAL_HEIGHT, Math.min(MAX_MODAL_HEIGHT, idealHeight))\n\n // What the modal actually renders: the live list when present, or\n // the archived snapshot when the live slot is empty (after the\n // auto-clear that fires when every item went `completed`). The\n // right-title badge counts whatever list is on screen, so a viewer\n // of the archive still sees \"N completed\" — no separate banner.\n const display = state.todos.length > 0 ? state.todos : state.archive\n const total = display.length\n\n const bottomTitle = total > 0\n ? `${total} item${total === 1 ? '' : 's'} · esc close`\n : 'esc close'\n\n // Right-aligned top-border overlay — at-a-glance status next to the\n // title slot. Painted via the new `Modal.rightTitle` sibling-overlay\n // hook (sites OpenTUI's scissor rect would otherwise clip).\n const rightTitle = display.length > 0 ? <CountsBadge items={display} /> : null\n\n return (\n <Modal title=\"todos\" bottomTitle={bottomTitle} rightTitle={rightTitle} maxWidth={100} maxHeight={maxHeight}>\n {total === 0\n ? (\n <text>\n <span fg={COLOR.mute}>{'(no todos yet — the agent hasn\\'t called '}</span>\n <span fg={COLOR.warn}>todowrite</span>\n <span fg={COLOR.mute}>)</span>\n </text>\n )\n : (\n // Scrollable list — `flexGrow: 1` claims the remaining\n // modal body height; `flexShrink: 1` plus the `Modal`\n // panel's own height cap keeps the action footer in view\n // on tall lists. Same containment contract as the\n // file-edit modal (see `test/tui/modal-overflow-\n // containment.test.tsx`).\n <box\n style={{\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 1,\n overflow: 'hidden',\n }}\n >\n <scrollbox\n ref={scrollRef}\n focusable={false}\n stickyScroll={false}\n style={{ flexGrow: 1, flexShrink: 1 }}\n >\n {display.map(item => (\n <TodoRow key={item.id} item={item} rowId={`todo-row-${item.id}`} />\n ))}\n </scrollbox>\n </box>\n )}\n </Modal>\n )\n}\n\n/**\n * Right-aligned top-border badge — \"{in-progress} in progress ·\n * {completed} completed\". Empty buckets are dropped so a homogeneous\n * list reads cleanly. Status order is fixed (in-progress first, then\n * completed) regardless of which buckets are populated — the badge's\n * shape stays predictable so the eye lands on the live count instantly.\n */\nfunction CountsBadge({ items }: { items: readonly TodoItem[] }) {\n const COLOR = useColors()\n const counts: Record<TodoStatus, number> = { pending: 0, in_progress: 0, completed: 0, cancelled: 0 }\n for (const item of items)\n counts[item.status] += 1\n const parts: Array<{ count: number, label: string, color: string }> = []\n if (counts.in_progress)\n parts.push({ count: counts.in_progress, label: 'in progress', color: COLOR.warn })\n if (counts.completed)\n parts.push({ count: counts.completed, label: 'completed', color: COLOR.accent })\n if (counts.pending)\n parts.push({ count: counts.pending, label: 'pending', color: COLOR.dim })\n if (counts.cancelled)\n parts.push({ count: counts.cancelled, label: 'cancelled', color: COLOR.error })\n if (parts.length === 0)\n return null\n return (\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>{' '}</span>\n {parts.map((p, i) => (\n <span key={p.label}>\n {i > 0 && <span fg={COLOR.mute}>{' · '}</span>}\n <span fg={p.color}>{String(p.count)}</span>\n <span fg={COLOR.mute}>{` ${p.label}`}</span>\n </span>\n ))}\n <span fg={COLOR.mute}>{' '}</span>\n </text>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { TextareaRenderable } from '@opentui/core'\nimport type { KeyBindings } from '../chat/keybindings'\nimport type { SessionContentBlock, SessionTurn } from '../types'\nimport { defaultTextareaKeyBindings } from '@opentui/core'\nimport { useKeyboard } from '@opentui/react'\nimport { useRef, useState } from 'react'\nimport { ageString, shortId } from '../chat/format'\nimport { matchesBinding } from '../chat/keybindings'\nimport { useColors } from '../chat/theme-context'\nimport { turnAsText } from '../chat/turn-operations'\nimport { writeToClipboard } from './clipboard'\nimport { Modal, useModal } from './modal'\n\n// ---------------------------------------------------------------------------\n// TurnDetailsModal — actions for the currently-selected turn.\n//\n// Geometry: pinned `maxHeight` with a scrollable preview pane so a long\n// assistant reply doesn't overflow the viewport and bury the action hint\n// row. Header sits in the modal's top border, before/after counter on the\n// bottom border (right-aligned), preview in the middle, action row at the\n// bottom.\n//\n// Keyboard:\n// - `f` — fork session from this turn (truncate, branch off)\n// - `d` — delete this turn (with orphan tool-pair cleanup)\n// - `c` — copy turn content to clipboard via OSC 52\n// - `e` — edit the turn's text content\n// - esc — close\n//\n// `f`/`d` are destructive of conversation flow; both confirm via a brief\n// transition state showing what's about to happen, then commit on a\n// second press. Copy is non-destructive and runs immediately.\n// ---------------------------------------------------------------------------\n\n/** Max chars surfaced in the scrollable preview pane. Long enough that almost everything fits without truncation. */\nconst PREVIEW_CHAR_MAX = 8000\n\n/**\n * Visible rows allocated to the modal. Smaller terminals shrink down via\n * the Modal's own clamp; this is the cap on wide terminals so the modal\n * keeps a comfortable shape rather than stretching to the full height.\n */\nconst MAX_MODAL_HEIGHT = 28\n\nconst EDIT_TEXTAREA_BINDINGS = (() => {\n const base = defaultTextareaKeyBindings.filter(\n b => b.name !== 'return' && !(b.name === 'a' && b.ctrl && !b.shift && !b.meta),\n )\n return [\n ...base,\n { name: 'a' as const, ctrl: true, action: 'select-all' as const },\n { name: 'return' as const, action: 'submit' as const },\n { name: 'return' as const, shift: true, action: 'newline' as const },\n ]\n})()\n\nexport interface TurnDetailsModalActions {\n /** Fork the session at this turn — new session keeps history up to here. */\n onFork: (turnId: string) => void\n /** Delete this turn (with cleanup of orphan tool blocks in neighbors). */\n onDelete: (turnId: string) => void\n /** Edit the text content of this turn. */\n onEdit: (turnId: string, newText: string) => void\n}\n\n/**\n * Extract the editable text from a turn — joins all `text` blocks.\n * Non-text blocks (tool_call, tool_result, thinking, etc.) are structural\n * and not included in the editable surface.\n */\nfunction editableText(turn: SessionTurn): string {\n return turn.content\n .filter((b): b is Extract<SessionContentBlock, { type: 'text' }> => b.type === 'text')\n .map(b => b.text)\n .join('\\n\\n')\n}\n\nexport function TurnDetailsModal({\n turn,\n index,\n total,\n actions,\n keybindings,\n}: {\n turn: SessionTurn\n /** 1-based position of this turn in the session, for the header. */\n index: number\n /** Total turn count, for the `n / N` header. */\n total: number\n actions: TurnDetailsModalActions\n /** Effective keybindings — the modal-internal letter actions resolve through this. */\n keybindings: KeyBindings\n}) {\n const COLOR = useColors()\n const modal = useModal()\n\n const fullText = turnAsText(turn)\n const preview = fullText.length > PREVIEW_CHAR_MAX\n ? `${fullText.slice(0, PREVIEW_CHAR_MAX)}\\n\\n…(${fullText.length - PREVIEW_CHAR_MAX} more chars)`\n : fullText\n const summary = blockSummary(turn)\n const before = index - 1\n const after = total - index\n const bottomTitle = `${before} before · ${after} after`\n\n // Pending confirmation state: `null` = first press, action committed on\n // second press. Esc clears the pending state without committing.\n const [pending, setPending] = useState<'fork' | 'delete' | null>(null)\n // Transient copy feedback. Flips back automatically — the modal itself\n // doesn't tick; the success message stays until the user presses\n // another key or closes the modal.\n const [copyStatus, setCopyStatus] = useState<'idle' | 'copied' | 'failed'>('idle')\n // Edit mode: when true, the preview pane is replaced by a textarea.\n const [editing, setEditing] = useState(false)\n const textareaRef = useRef<TextareaRenderable | null>(null)\n\n const hasEditableText = turn.content.some(b => b.type === 'text')\n\n const commitFork = () => {\n modal.close()\n actions.onFork(turn.id)\n }\n const commitDelete = () => {\n modal.close()\n actions.onDelete(turn.id)\n }\n const handleCopy = () => {\n if (!fullText) {\n setCopyStatus('failed')\n return\n }\n setCopyStatus(writeToClipboard(fullText) ? 'copied' : 'failed')\n }\n const commitEdit = () => {\n const newText = textareaRef.current?.plainText ?? ''\n modal.close()\n actions.onEdit(turn.id, newText)\n }\n\n useKeyboard((key) => {\n // In edit mode, only handle esc (cancel) — everything else goes to\n // the textarea. Submit is handled via the textarea's onSubmit.\n if (editing) {\n if (key.name === 'escape') {\n setEditing(false)\n }\n return\n }\n\n // `<Modal disableEscape={pending !== null}>` suppresses Modal's default\n // Esc handler whenever a confirmation is pending, so the handler below\n // is the sole responder for that keystroke. When no confirmation is\n // pending, Modal handles Esc normally (close).\n if (key.name === 'escape' && pending) {\n setPending(null)\n return\n }\n if (matchesBinding(key, keybindings.turnFork)) {\n setCopyStatus('idle')\n if (pending === 'fork')\n commitFork()\n else\n setPending('fork')\n return\n }\n if (matchesBinding(key, keybindings.turnDelete)) {\n setCopyStatus('idle')\n if (pending === 'delete')\n commitDelete()\n else\n setPending('delete')\n return\n }\n if (matchesBinding(key, keybindings.turnCopy)) {\n setPending(null)\n handleCopy()\n return\n }\n if (matchesBinding(key, keybindings.turnEdit)) {\n if (!hasEditableText)\n return\n setPending(null)\n setCopyStatus('idle')\n setEditing(true)\n return\n }\n // Any other key cancels a pending confirmation so the user isn't\n // surprised by a delayed fork/delete after they navigated away.\n if (pending)\n setPending(null)\n })\n\n return (\n <Modal\n title={editing ? `edit turn ${index} / ${total} · ${turn.role}` : `turn ${index} / ${total} · ${turn.role}`}\n bottomTitle={editing ? undefined : bottomTitle}\n maxHeight={MAX_MODAL_HEIGHT}\n disableEscape={editing || pending !== null}\n >\n {!editing && (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>id </span>\n <span fg={COLOR.model}>{shortId(turn.id)}</span>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>created </span>\n <span fg={COLOR.dim}>{ageString(turn.createdAt)}</span>\n {turn.runId && (\n <>\n <span fg={COLOR.mute}> · </span>\n <span fg={COLOR.mute}>run </span>\n <span fg={COLOR.dim}>{turn.runId}</span>\n </>\n )}\n </text>\n )}\n\n {!editing && (\n <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>blocks </span>\n <span fg={COLOR.dim}>{summary}</span>\n </text>\n )}\n\n {editing\n ? (\n <box\n title=\" edit \"\n style={{\n border: true,\n borderColor: COLOR.borderActive,\n paddingLeft: 1,\n paddingRight: 1,\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 1,\n minHeight: 5,\n }}\n >\n <textarea\n ref={textareaRef}\n focused\n keyBindings={EDIT_TEXTAREA_BINDINGS}\n initialValue={editableText(turn)}\n placeholder=\"enter text…\"\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={commitEdit}\n />\n </box>\n )\n : (\n <box\n title=\" preview \"\n style={{\n border: true,\n borderColor: COLOR.mute,\n paddingLeft: 1,\n paddingRight: 1,\n flexDirection: 'column',\n flexGrow: 1,\n flexShrink: 1,\n minHeight: 5,\n }}\n >\n {preview\n ? (\n <scrollbox\n focusable={false}\n style={{ flexGrow: 1 }}\n stickyScroll={false}\n >\n <text fg={COLOR.dim}>{preview}</text>\n </scrollbox>\n )\n : <text fg={COLOR.mute}>— no text content —</text>}\n </box>\n )}\n\n {editing\n ? (\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>↵</span>\n {' save · '}\n <span fg={COLOR.warn}>shift+↵</span>\n {' newline · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </text>\n )\n : (\n <ActionRow\n pending={pending}\n copyStatus={copyStatus}\n canCopy={fullText.length > 0}\n canEdit={hasEditableText}\n forkKey={keybindings.turnFork}\n deleteKey={keybindings.turnDelete}\n copyKey={keybindings.turnCopy}\n editKey={keybindings.turnEdit}\n />\n )}\n </Modal>\n )\n}\n\n/**\n * Footer row showing the action shortcuts. When a destructive action\n * (fork / delete) is pending confirmation, the row swaps to a clear\n * \"press <key> again to confirm\" prompt so the user can't trigger it\n * by accident. The copy result rides the same row when present — same\n * geometry, no layout shift.\n */\nfunction ActionRow({\n pending,\n copyStatus,\n canCopy,\n canEdit,\n forkKey,\n deleteKey,\n copyKey,\n editKey,\n}: {\n pending: 'fork' | 'delete' | null\n copyStatus: 'idle' | 'copied' | 'failed'\n canCopy: boolean\n canEdit: boolean\n forkKey: string\n deleteKey: string\n copyKey: string\n editKey: string\n}) {\n const COLOR = useColors()\n\n if (pending === 'fork') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>fork from here?</span>\n {' press '}\n <span fg={COLOR.warn}>{forkKey}</span>\n {' again to confirm · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </text>\n )\n }\n\n if (pending === 'delete') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>delete this turn?</span>\n {' press '}\n <span fg={COLOR.error}>{deleteKey}</span>\n {' again to confirm · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </text>\n )\n }\n\n // Copy feedback overrides the default hint row while it's relevant —\n // resets to idle on any other action keypress (see modal keyboard).\n if (copyStatus === 'copied') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>✓ copied</span>\n {' · '}\n <span fg={COLOR.warn}>{forkKey}</span>\n {' fork · '}\n <span fg={COLOR.warn}>{deleteKey}</span>\n {' delete · '}\n <span fg={canEdit ? COLOR.warn : COLOR.mute}>{editKey}</span>\n {canEdit ? ' edit · ' : ' (no text) · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n if (copyStatus === 'failed') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.error}>copy failed (terminal may not support OSC 52)</span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n }\n\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.warn}>{forkKey}</span>\n {' fork · '}\n <span fg={COLOR.warn}>{deleteKey}</span>\n {' delete · '}\n <span fg={canCopy ? COLOR.warn : COLOR.mute}>{copyKey}</span>\n {canCopy ? ' copy · ' : ' (nothing to copy) · '}\n <span fg={canEdit ? COLOR.warn : COLOR.mute}>{editKey}</span>\n {canEdit ? ' edit · ' : ' (no text) · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n )\n}\n\n/**\n * Human-readable per-kind block tally — e.g. `1 text · 2 tool_call`. Skips\n * zero-count kinds so the line stays scannable; uses canonical block-type\n * names so they line up with what the LLM and persistence layer see.\n */\nfunction blockSummary(turn: SessionTurn): string {\n const counts: Record<string, number> = {}\n for (const block of turn.content)\n counts[block.type] = (counts[block.type] ?? 0) + 1\n const parts: string[] = []\n for (const [type, n] of Object.entries(counts))\n parts.push(`${n} ${type}`)\n return parts.length === 0 ? '(empty)' : parts.join(' · ')\n}\n","/** @jsxImportSource @opentui/react */\nimport type { Agent } from '../agent'\nimport type { AgentProfile } from '../chat/agents'\nimport type { ProviderAuth, ProviderKey } from '../chat/auth'\nimport type { CompletionReference } from '../chat/completion'\nimport type { ResolvedConfig } from '../chat/config'\nimport type { Hint } from '../chat/hints'\nimport type { InteractionRequest, InteractionResponse, PendingInteractionEntry } from '../chat/interactions'\nimport type { ProviderDescriptor } from '../chat/providers'\nimport type { ApprovalOriginator } from '../chat/safe-mode-context'\nimport type { SessionExportFormat } from '../chat/session-export'\nimport type { EditOutcome, EditPayload, Picked, Screen, SessionMeta, Settings, StreamEvent } from '../chat/types'\nimport type { Session, SessionData } from '../session'\nimport type { ToolDef } from '../tools/types'\nimport type { PromptPart, SessionContentBlock, SessionTurn, ThinkingLevel, ToolResultContent } from '../types'\nimport type { InFlightToolCall } from './cancel-tool-modal'\nimport type { ContextUsage } from './components'\nimport type { PickedModel } from './model-picker'\nimport type { Attachment } from './screens'\nimport type { SessionCompactResult } from './session-details-modal'\nimport { spawn } from 'node:child_process'\nimport { useKeyboard, useRenderer, useSelectionHandler } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { createAgent } from '../agent'\nimport { buildBuildSystem, buildPlanSystem, envSection } from '../chat/agent-prompt'\nimport { discoverAgentsMd } from '../chat/agents-md'\nimport { detectAuth } from '../chat/auth'\nimport { AUTO_COMPACT_MIN_GROWTH_FRACTION, shouldAutoCompact } from '../chat/auto-compact'\nimport { buildUpdateHint, useUpdateCheck } from '../chat/auto-update-hook'\nimport { bootTick } from '../chat/boot-profiler'\nimport { tryOpenBrowser } from '../chat/browser'\nimport { createFilesCompletionProvider } from '../chat/completion-files'\nimport { createSkillsCompletionProvider, uniqueSkillNamesFromReferences } from '../chat/completion-skills'\nimport { ConfigProvider, useConfig } from '../chat/config-context'\nimport { useDiscovery } from '../chat/discovery-context'\nimport {\n buildEditOutcomesAnnotation,\n mergeApprovalAndBodyOutcomes,\n parseEditOutcomesFromResult,\n resolveApprovalForPayload,\n rewriteMultiEditHeader,\n stripEditOutcomesAnnotation,\n} from '../chat/edit-approval'\nimport { extractEditPayload } from '../chat/edit-diff'\nimport { buildHints } from '../chat/footer-hints'\nimport { formatTaskSummary, previewLine } from '../chat/format'\nimport { generateSessionTitle } from '../chat/generate-title'\nimport {\n buildResumedToolResultsTurn,\n createInteractionTools,\n InteractionsProvider,\n makeRequestInteraction,\n pendingInteractionsFromTurns,\n useInteractionsActions,\n useInteractionsQueue,\n} from '../chat/interactions'\nimport { ensureKeybindingsFile, keybindingsPath, matchesBinding } from '../chat/keybindings'\nimport { McpAuthProvider, useMcpAuthDispatch } from '../chat/mcp-auth-context'\nimport { createFileMcpCredentialStore } from '../chat/mcp-credentials'\nimport { buildMcpServers } from '../chat/mcps-discovery'\nimport { formatPathForCwd } from '../chat/path-display'\nimport { findGitRoot } from '../chat/project-root'\nimport { getContextWindow, modelSupportsReasoning, piIdOf } from '../chat/providers'\nimport { addToSafelist, getSafelist, isOnSafelist, suggestSafelistEntry } from '../chat/safe-mode'\nimport { SafeModeProvider, useSafeModeActions, useSafeModeQueue } from '../chat/safe-mode-context'\nimport { writeSessionExport } from '../chat/session-export'\nimport { clampFps, DEFAULT_SETTINGS, SettingsProvider, useSettings } from '../chat/settings-context'\nimport { buildSkillsConfig, defaultSkillScanPaths } from '../chat/skills-discovery'\nimport {\n deriveSessionTitle,\n EDIT_TOOL_NAMES,\n eventsFromTurns,\n lastContextSizeFromTurns,\n listSessionMeta,\n selectableTurnIds,\n stripSpawnTokensLine,\n sumRunCosts,\n toolCallPreview,\n toolResultText,\n updateToolEventOutcomes,\n} from '../chat/store'\nimport {\n finalizeStreamingMarkdown,\n finalizeStreamingMarkdownForOwner,\n turnContextSize,\n useStreamBuffer,\n} from '../chat/streaming'\nimport { resolveChipColor, resolveTheme } from '../chat/theme'\nimport { ThemeProvider, useColors, useSurfaces } from '../chat/theme-context'\nimport { deleteTurnSafely, truncateTurnsAt } from '../chat/turn-operations'\nimport {\n buildPostCompactAttachments,\n compactConversation,\n selectFilesFromSession,\n summaryToTurn,\n} from '../compact'\nimport { createProcessContext } from '../contexts'\nimport { errorMessage } from '../errors'\nimport { cleanupPersistedSession, resolvePersistDir, resolveTasksDir } from '../loop-persistence'\nimport { connectMcpServers } from '../mcp'\nimport { loginMcpServer } from '../mcp/login'\nimport { McpOAuthProvider } from '../mcp/oauth-provider'\nimport { createSession, loadSession } from '../session'\nimport { formatTokenUsage } from '../stats'\nimport { accentColor } from './agent-picker'\nimport { CancelToolModal } from './cancel-tool-modal'\nimport { writeToClipboard } from './clipboard'\nimport { Footer } from './components'\nimport { CwdPickerModal } from './cwd-picker'\nimport { DiscoveryShell } from './discovery-shell'\nimport { EffortPickerModal } from './effort-picker'\nimport { KeybindingsModal } from './keybindings-modal'\nimport { ModalRoot, useModal } from './modal'\nimport { ModelPickerModal } from './model-picker'\nimport { AuthScreen, ChatScreen, isSessionRowId, SessionsScreen } from './screens'\nimport { SessionDetailsModal } from './session-details-modal'\nimport { SettingsModal } from './settings-modal'\nimport { ChipStyleProvider, MdStyleProvider } from './theme'\nimport { TodosModal } from './todos-modal'\nimport { TurnDetailsModal } from './turn-details-modal'\n\n/**\n * One entry in the user-prompt queue. Captured at submit time alongside\n * the original prompt's resolved `CompletionReference`s so the drain loop\n * can re-run the same skill-activation / chip-highlight flow it would\n * have run for an immediately-submitted prompt.\n */\ninterface QueuedMessage {\n prompt: string\n references: readonly CompletionReference<unknown>[]\n attachments: readonly Attachment[]\n}\n\n/**\n * Surface failures that are normally silenced (teardown / save) when the\n * `ZIDANE_DEBUG` env var is set. Logging via `console.error` would otherwise\n * trigger OpenTUI's error console overlay and clutter the UI for end users.\n */\nconst debugLog: (label: string, err: unknown) => void\n = process.env.ZIDANE_DEBUG\n ? (label, err) => console.error(`[zidane/tui] ${label}:`, err)\n : () => {}\n\n/**\n * Launch the user's preferred editor on `path`. Tries `$EDITOR` /\n * `$VISUAL` first (the standard *nix convention for \"open this\n * config file\"); falls back to the platform's default opener\n * (`open` on macOS, `xdg-open` on Linux, `start` on Windows). The\n * spawned process is detached + unref'd so closing the TUI doesn't\n * kill the editor (and vice versa).\n *\n * Resolves once the spawn is dispatched — we don't wait for the\n * editor to exit. The user is expected to save and either restart\n * the TUI (for changes that need to be picked up at launch, like\n * keybindings) or simply continue working.\n */\nasync function launchEditor(path: string): Promise<void> {\n const envEditor = (process.env.VISUAL ?? process.env.EDITOR ?? '').trim()\n const [cmd, args] = (() => {\n if (envEditor)\n return [envEditor, [path]] as const\n if (process.platform === 'darwin')\n return ['open', [path]] as const\n if (process.platform === 'win32')\n return ['cmd', ['/c', 'start', '\"\"', path]] as const\n return ['xdg-open', [path]] as const\n })()\n spawn(cmd, args, {\n detached: true,\n stdio: 'ignore',\n shell: process.platform === 'win32',\n }).unref()\n}\n\n/**\n * Session-metadata key under which the TUI persists the user's \"pinned\"\n * active skills — the set the user toggled on via `/skill-name` and\n * expects to stay active across run boundaries (and TUI restarts).\n *\n * Separate from the agent's `skillActivationState` for two reasons:\n * 1. The framework's run-end pass deactivates everything; the pinned\n * set survives that pass so the next prompt can re-activate.\n * 2. The agent's session-resume rehydrator only sees `skills_use`\n * `tool_call` blocks in history. Slash-command activations bypass\n * that path; this metadata key is the system-of-record for them.\n */\nconst ACTIVE_SKILLS_META_KEY = 'zidane.activeSkills'\n\n/**\n * Read the pinned-skills set out of session metadata. Tolerant by\n * design — older sessions, manually-edited metadata, or a future\n * type drift all degrade to \"no pins\", never throw. Returns a fresh\n * Set so callers can mutate-and-store without aliasing the input.\n */\nfunction readPinnedSkills(raw: unknown): ReadonlySet<string> {\n if (!Array.isArray(raw))\n return new Set<string>()\n return new Set(raw.filter((v): v is string => typeof v === 'string' && v.length > 0))\n}\n\n/**\n * Mirror the pinned-skills set into session metadata. Sorted for\n * stable on-disk ordering (diffs / debugging). `session.setMeta`\n * already routes through `session:meta` hooks, so observers see a\n * single normalized payload regardless of insertion order.\n *\n * Imported lazily via the session ref — calling sites already\n * captured `session` in scope, so we accept it as an argument\n * rather than re-fetching from a ref.\n */\nfunction persistPinnedSkills(\n session: { setMeta: (key: string, value: unknown) => void },\n pins: ReadonlySet<string>,\n): void {\n session.setMeta(ACTIVE_SKILLS_META_KEY, Array.from(pins).sort())\n}\n\n/**\n * Filter a `multi_edit` (or single `edit` / `write_file`) input's hunk\n * list down to the approved subset, in original order. Used by the\n * approval gate to rebind `ctx.input.edits` after a partial decision —\n * the tool body then runs its legacy atomic semantics against the\n * smaller batch. Non-array inputs short-circuit to an empty list; the\n * caller treats that as \"nothing to apply\".\n */\nfunction reduceEditsByOutcomes(\n edits: unknown,\n outcomes: readonly EditOutcome[],\n): unknown[] {\n if (!Array.isArray(edits))\n return []\n const out: unknown[] = []\n for (let i = 0; i < edits.length; i++) {\n const outcome = outcomes[i]\n if (!outcome || outcome.kind === 'applied')\n out.push(edits[i])\n }\n return out\n}\n\n/**\n * Top-level TUI component. Accepts a fully-resolved `ResolvedConfig` and wires\n * everything (settings, modal layer, screens, footer) underneath it.\n *\n * Hosts can either drive this via `runTui()` for the standard bootstrap or\n * mount `<App config={resolveConfig(...)} />` themselves inside a renderer\n * they already own.\n */\nexport function App({ config }: { config: ResolvedConfig }) {\n const initialSettings = useMemo(\n () => ({ ...DEFAULT_SETTINGS, ...config.initialSettings }),\n [config.initialSettings],\n )\n\n const onSettingsChange = useCallback(\n (settings: Settings) => config.stateStore.save({ ...config.stateStore.load(), settings }),\n [config.stateStore],\n )\n\n return (\n <ConfigProvider config={config}>\n <SettingsProvider initial={initialSettings} onChange={onSettingsChange}>\n <ThemedShell />\n </SettingsProvider>\n </ConfigProvider>\n )\n}\n\n/**\n * Reads `settings.theme` from the surrounding `SettingsProvider`, resolves\n * it to a `Theme`, and mounts everything else underneath a `ThemeProvider`.\n * Split out so `App` doesn't need to call `useSettings` (which would force\n * it to live inside its own provider — invalid).\n *\n * `resolveTheme` falls back to `DEFAULT_THEME` on unknown ids, so an\n * out-of-date `state.json` (theme renamed / removed) never breaks rendering.\n */\nfunction ThemedShell() {\n const { settings } = useSettings()\n const theme = useMemo(() => resolveTheme(settings.theme), [settings.theme])\n return (\n <ThemeProvider theme={theme}>\n <MdStyleProvider>\n <ChipStyleProvider>\n <SafeModeProvider>\n <InteractionsProvider>\n <McpAuthProvider>\n <DiscoveryShell>\n <ModalRoot>\n <AppShell />\n </ModalRoot>\n </DiscoveryShell>\n </McpAuthProvider>\n </InteractionsProvider>\n </SafeModeProvider>\n </ChipStyleProvider>\n </MdStyleProvider>\n </ThemeProvider>\n )\n}\n\nfunction AppShell() {\n // Boot profiler: AppShell evaluating means React's first reconciliation\n // pass has reached us. The `useEffect` further down emits another tick\n // once that pass is committed (i.e. the first frame is on screen).\n bootTick('AppShell:render-enter')\n\n const renderer = useRenderer()\n const modal = useModal()\n const config = useConfig()\n const { settings } = useSettings()\n const COLOR = useColors()\n const SURFACE = useSurfaces()\n\n // Fires once after React commits the very first render. The boot\n // profiler turns this into the \"time to first paint\" marker.\n useEffect(() => {\n bootTick('AppShell:first-effect (post-first-paint)')\n }, [])\n // `useSafeModeQueue` re-renders on every push/pop; `useSafeModeActions`\n // hands back a stable object, so anything memoizing over the actions\n // (gate handlers, abort callback) keeps a single identity across queue\n // churn. See safe-mode-context.tsx.\n const queue = useSafeModeQueue()\n const { requestApproval, resolveHead, denyAll } = useSafeModeActions()\n // Head of the safe-mode approval queue. `null` means \"nothing\n // pending\" and the chat screen shows the prompt input as usual.\n // Hoisted here (above every callback that reads it) so React doesn't\n // hit a temporal-deadzone when the model-picker / details callbacks\n // — declared earlier in source order — depend on it.\n const pendingApproval = queue[0] ?? null\n\n // Interactions queue — `present_plan` / `ask_user` tool calls awaiting\n // a user reply. Same two-context pattern as safe-mode. The head drives\n // the chat screen's prompt-slot UI; the actions stay stable so the\n // gate / resumed-flow callbacks don't rebind on every queue change.\n const interactionsQueue = useInteractionsQueue()\n const interactions = useInteractionsActions()\n const pendingInteractionEntry = interactionsQueue[0] ?? null\n const pendingInteraction = pendingInteractionEntry?.request ?? null\n\n // Destructure stable identities up-front so callbacks/effects can depend on\n // them directly (avoids the \"whole config in deps\" footgun — see S1 review).\n const {\n providers: providerRegistry,\n agents: agentRegistry,\n initialAgentId,\n store,\n stateStore,\n modelsFor,\n resumeProvider,\n initialPicked,\n initialState,\n keybindings,\n autoUpdate: autoUpdateConfig,\n } = config\n const lastResumedSessionId = initialState.lastSessionId\n // `resumeLastSession` is snapshotted at mount — toggling the setting\n // mid-session shouldn't replay the resume-on-launch effect against a\n // session that's already torn down. Reads from `initialState.settings`\n // (what was on disk when we started); defaults to `true` so users\n // who pre-date this setting get the historical resume behavior.\n const resumeLastSessionOnLaunch = initialState.settings?.resumeLastSession ?? true\n // User-scoped config root — credentials + safelist + MCP tokens live\n // here regardless of whether project mode is on, so they're never\n // committed alongside a project's checked-in `.{prefix}/` directory.\n // Aliased to `userDir` for back-compat; in XDG mode this is\n // `$XDG_CONFIG_HOME/zidane`, in legacy mode it collapses to\n // `<storageDir>/<prefix>` (e.g. `~/.zidane/`).\n const dataDir = config.paths.userDir\n // Cache root — persisted tool-result blobs, background-task logs,\n // auto-update registry cache. Regenerable, safe to wipe. In XDG\n // mode this is `$XDG_CACHE_HOME/zidane`; in legacy mode it\n // collapses to the same dir as `dataDir`.\n const cacheDir = config.paths.cacheDir\n\n // Active agent profile. Held in a ref alongside state so `buildAgent` —\n // which the resume effect depends on — stays referentially stable across\n // profile switches (otherwise the effect would re-fire and re-activate\n // the originally-resumed session, clobbering any session the user\n // navigated to in between). State drives the picker's UI; the ref carries\n // the latest value into `buildAgent` synchronously.\n const [pickedAgent, setPickedAgent] = useState<AgentProfile>(\n () => agentRegistry[initialAgentId] ?? Object.values(agentRegistry)[0],\n )\n const pickedAgentRef = useRef(pickedAgent)\n\n // -------------------------------------------------------------------------\n // Safe-mode plumbing.\n //\n // Hook handlers run inside the agent loop — they need stable references to\n // the latest \"is safe-mode on?\" flag and to the cwd (the project key in\n // `projects.json`). Refs decouple registration from React re-renders so we\n // don't re-register on every settings flip.\n // -------------------------------------------------------------------------\n\n const safeModeEnabledRef = useRef(settings.safeMode)\n useEffect(() => { safeModeEnabledRef.current = settings.safeMode }, [settings.safeMode])\n\n // Mirror the user's `targetFps` setting onto the live renderer. The\n // boot value was already seeded inside `runTui`; this effect handles\n // every subsequent flip via the Settings modal (30 / 60 / 120). We\n // pin both `targetFps` and `maxFps` to the same value so an idle\n // frame doesn't try to render faster than the configured target.\n useEffect(() => {\n const fps = clampFps(settings.targetFps)\n if (renderer.targetFps !== fps)\n renderer.targetFps = fps\n if (renderer.maxFps !== fps)\n renderer.maxFps = fps\n }, [renderer, settings.targetFps])\n\n // Mirror finalized in-app selections to the OS clipboard. Terminal\n // apps eat `cmd+c` at the OS keymap level so we can't intercept it,\n // but `writeToClipboard` (OSC 52 + native pbcopy / wl-copy / xclip /\n // clip) populates the clipboard on drag-end — so the user just hits\n // `cmd+v` in any other app and the dragged text is there.\n //\n // `lastCopiedRef` debounces redundant copies — the selection handler\n // fires on every state change (drag-update, finalize, clear) and\n // each invocation forks `pbcopy` on macOS; we keep that to one fork\n // per distinct selection.\n const lastCopiedRef = useRef('')\n useSelectionHandler((selection) => {\n if (selection.isDragging)\n return\n const text = selection.getSelectedText()\n if (!text || text === lastCopiedRef.current)\n return\n lastCopiedRef.current = text\n writeToClipboard(text)\n })\n\n // Persistence toggle — same pattern: ref keeps `buildAgent` independent\n // of `settings` for dependency-array purposes. A flip takes effect on\n // the NEXT session activation; an in-flight stream isn't torn down.\n const persistToolResultsRef = useRef(settings.persistToolResults)\n useEffect(() => { persistToolResultsRef.current = settings.persistToolResults }, [settings.persistToolResults])\n // Interactive-tools gating — same NEXT-activation semantics as\n // `persistToolResults`. Read at `buildAgent` time; flipping mid-stream\n // doesn't tear down an in-flight `ask_user` / `present_plan`.\n const allowInteractionRef = useRef(settings.allowInteraction)\n useEffect(() => { allowInteractionRef.current = settings.allowInteraction }, [settings.allowInteraction])\n // AGENTS.md / CLAUDE.md scope — same NEXT-activation semantics. Flipping\n // mid-session doesn't re-inject the prompt; the user has to start a\n // fresh session (or a `/compact` / model swap that re-fires the\n // `system:transform` hook) for the new scope to take effect.\n const userInstructionsScopeRef = useRef(settings.userInstructionsScope)\n useEffect(() => { userInstructionsScopeRef.current = settings.userInstructionsScope }, [settings.userInstructionsScope])\n\n // Auto-compaction toggle + threshold — refs so the post-turn trigger\n // check inside `onSubmitPrompt` reads the latest values without forcing\n // the callback to depend on `settings` (which would re-bind it on every\n // unrelated setting flip).\n const autoCompactRef = useRef(settings.autoCompact)\n useEffect(() => { autoCompactRef.current = settings.autoCompact }, [settings.autoCompact])\n const autoCompactThresholdRef = useRef(settings.autoCompactThreshold)\n useEffect(() => { autoCompactThresholdRef.current = settings.autoCompactThreshold }, [settings.autoCompactThreshold])\n /**\n * Post-compaction baseline for the hysteresis check in\n * {@link shouldAutoCompact}. Latched to the effective post-compact token\n * count whenever a compaction lands (see {@link onCompactSession}), reset\n * to `undefined` on session change so the first compaction in a fresh\n * session fires off the absolute threshold without any growth gating.\n * Pairs with {@link AUTO_COMPACT_MIN_GROWTH_FRACTION} in the predicate.\n */\n const lastCompactedInputTokensRef = useRef<number | undefined>(undefined)\n\n // Smooth-streaming toggle — read on every tick by the stream buffer, so\n // flipping the setting mid-stream takes effect on the next drain.\n const smoothStreamingRef = useRef(settings.smoothStreaming)\n useEffect(() => { smoothStreamingRef.current = settings.smoothStreaming }, [settings.smoothStreaming])\n\n // Project anchor — used as the key in `projects.json` (safelist),\n // as the `cwd` for skill / MCP / file discovery, as the scope tag\n // on new sessions, and as the export resolver's anchor. Walks up\n // from `process.cwd()` to the enclosing git root so two cwds inside\n // the same repo (e.g. `repo/` and `repo/packages/foo`) share ONE\n // project — one safelist, one session list, one set of discovered\n // skills/MCPs/files. Outside a git repo we fall back to the bare\n // cwd, keeping the historical \"this directory is its own project\"\n // behavior for non-repo work.\n //\n // Captured once per AppShell mount with `useState` lazy init —\n // `useMemo([])` would technically allow React to recompute under\n // memory pressure, `useState` is a hard guarantee.\n const [projectDir, setProjectDir] = useState(() => findGitRoot(process.cwd()) ?? process.cwd())\n\n // Dynamic working directory — the cwd tools resolve paths against.\n // Starts at `process.cwd()` and can be changed at runtime (e.g.\n // Ctrl+G). When changed, `process.chdir()` is also called so child\n // processes inherit the new cwd. `projectDir` is recomputed to the\n // new cwd's git root (or the cwd itself outside a repo) so safelists,\n // sessions, and discovery follow the user across projects.\n const [cwd, setCwdRaw] = useState(process.cwd)\n const cwdRef = useRef(cwd)\n cwdRef.current = cwd\n const setCwd = useCallback((next: string) => {\n process.chdir(next)\n setCwdRaw(next)\n setProjectDir(findGitRoot(next) ?? next)\n }, [])\n\n // In-memory cache for the project's safelist so `gateDecision` doesn't\n // re-read `projects.json` from disk on every tool call (matters when a\n // parallel batch fires dozens of gate hooks in the same microtask). The\n // ref is seeded lazily and explicitly refreshed when we persist an entry\n // — the TUI is the only writer, so external invalidation isn't needed.\n const safelistRef = useRef<readonly string[] | null>(null)\n const readSafelist = useCallback((): readonly string[] => {\n if (safelistRef.current === null)\n safelistRef.current = getSafelist(dataDir, projectDir)\n return safelistRef.current\n }, [dataDir, projectDir])\n // Drop the cache on a project switch (only happens across mounts today,\n // but cheap insurance for future code that swaps `projectDir`).\n useEffect(() => { safelistRef.current = null }, [dataDir, projectDir])\n\n // Session-scoped safelist — tool entries approved via \"accept for session\"\n // live here until the session tears down or the TUI exits. Checked in\n // `gateDecision` after the disk safelist, before prompting.\n const sessionSafelistRef = useRef<Set<string>>(new Set())\n\n // Forward ref to the stream buffer — populated below where the buffer\n // is created. The gate-side handlers (`gateDecision`, `applyGate`) need\n // to emit synthetic `tool` events for denied edits but are declared\n // before `useStreamBuffer` runs; reading via the ref breaks the cycle\n // without re-ordering the hook block.\n const streamRef = useRef<ReturnType<typeof useStreamBuffer> | null>(null)\n\n // Per-edit annotation pending map — populated on partial approvals\n // (keyed by `callId`), consumed by the `tool:transform` /\n // `child:tool:transform` hooks to append the `<edit-outcomes>` block\n // onto the tool's result text. Lives at AppShell scope so the map\n // outlives any individual `buildAgent` call (a session switch tears\n // down the agent but the next agent gets a fresh map by construction\n // — entries from a previous run never match a new run's callIds).\n //\n // Entries are normally deleted by the matching `tool:transform` /\n // `child:tool:transform` handler. Validation reject (no transform\n // fires) and abort/steer short-circuits inside `executeToolBatch`\n // (synthesize a result without firing transform) can strand entries\n // for the same callId. We catch both via the run-end `agent:done`\n // sweep registered in `buildAgent`, and the teardown path clears\n // the map outright as a belt-and-suspenders for partial destroys.\n const pendingAnnotationsRef = useRef<Map<string, readonly EditOutcome[]>>(new Map())\n\n // Discovery state — catalogs (skills, mcps, mcpsErrors, files) and\n // their ensure/refresh thunks live in <DiscoveryShell>, mounted ABOVE\n // <ModalRoot>. That layering is load-bearing: modals are rendered as\n // siblings of <AppShell> by <ModalRoot>, so a `setSkillsCatalog`\n // inside AppShell wouldn't reach an open modal. Hoisting the state\n // above ModalRoot lets context propagation drive live updates into\n // the active modal's subtree (same path as MCP auth state).\n //\n // AppShell still owns:\n // - `enabledSkills` / `enabledMcps` (user toggle allowlists, lives in `Settings`)\n // - `mcpCredentialStore` (used by `buildAgent`, `onLoginMcp`, `onLogoutMcp`)\n // - `dispatchAuth` (for login/logout flows)\n // `mcpsErrors` is consumed directly by `<SettingsModal>` via the\n // same context — AppShell doesn't read it, so it's not destructured\n // here. Same for `refreshFiles` (no UI affordance yet).\n const {\n skillsCatalog,\n mcpsCatalog,\n filesCatalog,\n ensureFiles: ensureFilesCatalog,\n ensureSkills: ensureSkillsCatalog,\n refreshSkills: onRefreshSkills,\n refreshMcps: onRefreshMcps,\n } = useDiscovery()\n\n // Memoized credential store — single file-backed store reused across both\n // bootstrap (`buildAuthProvider`) and the interactive login flow. Both\n // paths need to share so a login save is immediately visible to the next\n // bootstrap, and tokens refreshed by the SDK on a tool call are visible\n // to the modal's \"authed\" badge on the next reconciliation.\n const mcpCredentialStore = useMemo(() => createFileMcpCredentialStore(dataDir), [dataDir])\n\n // Dispatch comes from <McpAuthProvider> (mounted above <DiscoveryShell>);\n // the modal layer reads the same context, so login state propagates live\n // through the open settings modal as well. The ref pattern keeps long-\n // lived agent hooks (`mcp:auth:url`, …) firing the LATEST dispatcher\n // without re-binding the hook on every state update.\n const dispatchAuth = useMcpAuthDispatch()\n const dispatchAuthRef = useRef(dispatchAuth)\n dispatchAuthRef.current = dispatchAuth\n\n // Refs mirror the discovery + enabled state so closures (completion\n // provider, buildAgent) don't capture stale snapshots. The provider\n // re-reads on each `suggest` / `parseReferences` call; `buildAgent`\n // re-reads at session activation. Toggling a skill or MCP server takes\n // effect on the next session activation — by design, so a stream\n // already in flight isn't torn down.\n const skillsCatalogRef = useRef(skillsCatalog)\n skillsCatalogRef.current = skillsCatalog\n const enabledSkillsRef = useRef(settings.enabledSkills)\n enabledSkillsRef.current = settings.enabledSkills\n const mcpsCatalogRef = useRef(mcpsCatalog)\n mcpsCatalogRef.current = mcpsCatalog\n const enabledMcpsRef = useRef(settings.enabledMcps)\n enabledMcpsRef.current = settings.enabledMcps\n const filesCatalogRef = useRef(filesCatalog)\n filesCatalogRef.current = filesCatalog\n\n // `ensureFilesCatalog` / `ensureSkillsCatalog` close over project\n // state via refs (the thunks themselves are stable across renders),\n // so the providers can be memoized once and survive project swaps —\n // the thunks just re-target the new slot internally.\n const ensureFilesCatalogRef = useRef(ensureFilesCatalog)\n ensureFilesCatalogRef.current = ensureFilesCatalog\n const ensureSkillsCatalogRef = useRef(ensureSkillsCatalog)\n ensureSkillsCatalogRef.current = ensureSkillsCatalog\n\n // `@`-completion path formatter — rewrites discovery's\n // project-root-relative paths into the form the agent's\n // CWD-resolving tools will actually find. Captures `process.cwd()`\n // at mount; the TUI doesn't chdir during its lifetime so a stable\n // closure is correct.\n const formatFilePathForCwd = useMemo(() => {\n return (entry: { path: string }) => formatPathForCwd(entry.path, projectDir, cwd)\n }, [projectDir, cwd])\n\n // Building the array reference against the catalog identities — not\n // []! — is the cheap way to force `useCompletion` to rerun `suggest`\n // when a background rescan lands fresh entries while the popover is\n // already open. The provider OBJECTS still read live state via refs,\n // so this is just an identity bump; the engine's `useMemo` chain\n // (rawTrigger → active → suggest effect) cascades from it.\n const completionProviders = useMemo(\n () => [\n createSkillsCompletionProvider({\n getCatalog: () => skillsCatalogRef.current,\n getEnabled: () => enabledSkillsRef.current,\n ensureCatalog: () => ensureSkillsCatalogRef.current(),\n }),\n createFilesCompletionProvider({\n getCatalog: () => filesCatalogRef.current,\n ensureCatalog: () => ensureFilesCatalogRef.current(),\n formatPath: formatFilePathForCwd,\n }),\n ] as const,\n [filesCatalog, skillsCatalog, formatFilePathForCwd],\n )\n\n /**\n * Outcome of the approval gate.\n *\n * - `'allow'` — the call proceeds untouched.\n * - `'deny'` — the call is refused. `outcomes` (when set, for an\n * edit-family tool) drive the synthetic transcript event so the\n * user can see which hunks would have been applied vs denied.\n * - `'partial'` — a subset of an edit-family call. The caller MUST\n * rebind the tool ctx's `input.edits` to the approved subset; the\n * `outcomes` array is 1:1 with the ORIGINAL hunks (so the renderer\n * shows the full picture). The TUI's `tool:transform` hook reads\n * `outcomes` back out of a pending-annotation map and appends the\n * `<edit-outcomes>` block onto the tool's result text — this is\n * what carries the per-hunk view through to the wire / persisted\n * tool_result on the next agent turn.\n */\n type GateOutcome\n = | { kind: 'allow' }\n | { kind: 'deny', outcomes?: readonly EditOutcome[], editPayload?: EditPayload }\n | { kind: 'partial', outcomes: readonly EditOutcome[], editPayload: EditPayload, reducedEdits: readonly unknown[] }\n\n /**\n * Single source of truth for \"should this call execute?\". Three short-circuits:\n *\n * - Safe-mode globally off → always allow.\n * - Call covered by the project safelist or the implicit read-only set\n * → always allow without prompting.\n * - Otherwise → prompt the user and act on their decision (including\n * persisting a new safelist entry on \"accept + safelist\", or\n * injecting per-edit outcomes on a partial multi_edit approval).\n *\n * Wired into the parent agent via `tool:gate` / `mcp:tool:gate`, and to\n * every subagent (transitively, for free) via `child:tool:gate` /\n * `child:mcp:tool:gate` — see the bubble in `src/tools/spawn.ts`.\n */\n const gateDecision = useCallback(\n async (\n tool: string,\n input: Record<string, unknown>,\n turnId: string | undefined,\n callId: string,\n originator: ApprovalOriginator,\n ): Promise<GateOutcome> => {\n if (!safeModeEnabledRef.current)\n return { kind: 'allow' }\n if (isOnSafelist(readSafelist(), tool, input))\n return { kind: 'allow' }\n if (isOnSafelist([...sessionSafelistRef.current], tool, input))\n return { kind: 'allow' }\n\n const decision = await requestApproval(tool, input, originator)\n\n // Safelist side effects fire only on the explicit accept-* paths.\n // `partial` collapses to a one-off mixed decision — no entry written.\n if (decision === 'accept-session') {\n sessionSafelistRef.current.add(suggestSafelistEntry(tool, input))\n }\n else if (decision === 'accept-safelist') {\n const entry = suggestSafelistEntry(tool, input)\n addToSafelist(dataDir, projectDir, entry)\n safelistRef.current = null\n }\n\n // File-edit tools get the per-edit outcome treatment. For everything\n // else, `partial` is meaningless — collapse to `allow`.\n const editPayload = extractEditPayload(tool, input)\n if (editPayload) {\n const resolved = resolveApprovalForPayload(decision, editPayload)\n if (resolved.shouldBlock) {\n // Always emit a synthetic `tool` event for denied edits so the\n // transcript shows the intended diff (with denied badges) instead\n // of silently swallowing the call.\n if (resolved.syntheticEvent) {\n streamRef.current?.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(tool, input),\n tool,\n input,\n edit: resolved.syntheticEvent,\n callId,\n ...(turnId ? { turnId } : {}),\n })\n }\n return {\n kind: 'deny',\n outcomes: resolved.outcomes,\n ...(resolved.syntheticEvent ? { editPayload: resolved.syntheticEvent } : {}),\n }\n }\n // Partial — emit the synthetic event with the FULL hunks + outcomes\n // so the live transcript shows every hunk badged (applied / denied),\n // then return so `applyGate` reduces `ctx.input.edits` to the\n // approved subset. The tool body sees the smaller batch and runs\n // legacy single-mode atomic semantics on it; `tool:transform`\n // appends the `<edit-outcomes>` annotation to the result so the\n // wire / persisted history carries the per-hunk decisions through\n // to replay. The original `ctx.input` reference (still on the\n // assistant turn's `tool_call` block in `ctx.turns`) stays clean\n // since we rebind to a fresh shallow-clone in `applyGate`.\n if (resolved.syntheticEvent) {\n const reducedEdits = reduceEditsByOutcomes(input.edits, resolved.outcomes)\n streamRef.current?.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(tool, input),\n tool,\n input,\n edit: resolved.syntheticEvent,\n callId,\n ...(turnId ? { turnId } : {}),\n })\n return {\n kind: 'partial',\n outcomes: resolved.outcomes,\n editPayload: resolved.syntheticEvent,\n reducedEdits,\n }\n }\n // accept-* (or partial that resolved to all-applied) → allow.\n return { kind: 'allow' }\n }\n\n if (decision === 'deny')\n return { kind: 'deny' }\n return { kind: 'allow' }\n },\n [dataDir, projectDir, requestApproval, readSafelist],\n )\n\n // Initial screen + picked seed from the resolved config so a returning user\n // lands straight on chat (or sessions) without an auth-screen flash.\n const [screen, setScreen] = useState<Screen>(() => {\n if (!resumeProvider)\n return 'auth'\n return lastResumedSessionId ? 'chat' : 'sessions'\n })\n const [picked, setPicked] = useState<Picked | null>(() => initialPicked)\n // Mirror of `picked` for callbacks (resumed-interaction resolver) that\n // are captured before a model / effort swap and would otherwise carry\n // a stale snapshot — the picker can change mid-pause so the next run\n // should use whatever the user has most-recently chosen.\n const pickedRef = useRef(picked)\n pickedRef.current = picked\n const [sessions, setSessions] = useState<SessionMeta[]>([])\n const [currentSession, setCurrentSession] = useState<SessionMeta | null>(null)\n const [events, setEvents] = useState<StreamEvent[]>([])\n const [busy, setBusy] = useState(false)\n /**\n * React-state mirror of {@link autoCompactInFlightRef}. The ref drives\n * the gate logic (sync read during submits); this state drives the UI\n * indicator — the small spinner painted over the session title. Without\n * it the title wouldn't re-render when compaction starts / ends.\n */\n const [compacting, setCompacting] = useState(false)\n /**\n * FIFO of user prompts the user typed while a previous run was still in\n * flight. The transcript echoes each one immediately (so the user sees\n * \"I sent that\"), but only the head of the queue is fed to `agent.run()`\n * at any given moment — see {@link drainMessageQueue}. Mirrored via a\n * ref so the drain loop reads the freshest value synchronously and the\n * UI re-renders only when the visible length changes.\n *\n * Esc / abort / teardown clears the queue — pending submits are dropped\n * along with the in-flight run (same \"esc = stop everything\" rule the\n * approval queue follows).\n */\n const messageQueueRef = useRef<QueuedMessage[]>([])\n const [messageQueue, setMessageQueue] = useState<readonly QueuedMessage[]>([])\n /**\n * `true` while {@link drainMessageQueue} is iterating. Gates re-entry —\n * a second user submit during a run pushes onto {@link messageQueueRef}\n * and returns; the loop picks the new entry up on its next iteration\n * instead of spawning a parallel `agent.run()` (which `createAgent`\n * would refuse with \"Agent is already running\").\n *\n * A ref (not state) because the gate must be synchronous: two submits\n * in the same tick would both observe the same stale `busy` boolean\n * otherwise.\n */\n const runningRef = useRef(false)\n /** Token count from the most recent assistant turn (caching-aware). */\n const [lastInputTokens, setLastInputTokens] = useState(0)\n /**\n * Cumulative USD cost across every run in the active session — seeded\n * from `session.runs` on activation and topped up by each `turn:after`'s\n * `usage.cost`. Only providers that report cost (currently OpenRouter)\n * contribute; everywhere else this stays at 0 and the footer hides it.\n */\n const [sessionCost, setSessionCost] = useState(0)\n /**\n * Synchronous mirror of {@link lastInputTokens} for callbacks that need\n * to read the freshest value after `await agent.run()` resolves. The\n * `turn:after` hook updates both the state (drives the footer) and this\n * ref (drives the auto-compact trigger check) in lock-step.\n */\n const lastInputTokensRef = useRef(0)\n /**\n * Active turn id when the user is in \"select turn\" mode (ctrl+s on the\n * chat screen). `null` means normal mode — typing is enabled, transcript\n * has no highlight. When set, the prompt textarea is unfocused so up/down\n * navigate the turn list, ↵ opens the details modal, ⎋ exits the mode.\n */\n const [selectedTurnId, setSelectedTurnId] = useState<string | null>(null)\n /**\n * Highlighted entry in the type-ahead queue box. `null` means the user\n * is in normal mode (textarea focused, queue purely informational);\n * `0+` is an index into `messageQueue` and unfocuses the textarea so\n * up/down navigate the queue, `ctrl+return` pushes, `delete` drops, esc\n * exits back to the prompt. Auto-clears when the queue empties.\n *\n * Mirrored in {@link queueSelectionIndexRef} so callbacks with `[]`\n * deps can read the current selection synchronously without forcing\n * either a re-bind on every selection change or a side-effecting\n * state updater (React requires updaters to be pure).\n */\n const [queueSelectionIndex, setQueueSelectionIndex] = useState<number | null>(null)\n const queueSelectionIndexRef = useRef<number | null>(null)\n queueSelectionIndexRef.current = queueSelectionIndex\n\n const agentRef = useRef<Agent | null>(null)\n const sessionRef = useRef<Session | null>(null)\n\n // Sync cwd changes to the live agent's execution handle so the\n // current session's tools resolve paths against the new directory\n // immediately — no session restart needed. A `system:transform` hook\n // rewrites the `<env>` block on every turn so the model always sees\n // the current cwd and project root.\n useEffect(() => {\n const handle = agentRef.current?.handle\n if (handle)\n handle.cwd = cwd\n }, [cwd])\n\n // Rewrite the system prompt's <env> block with the live cwd/projectDir\n // on every turn via `system:transform`. The hook is re-registered\n // whenever cwd or projectDir changes so the closure captures the\n // latest values. The unregister returned by `hook()` tears down the\n // previous hook before the new one is installed.\n useEffect(() => {\n const agent = agentRef.current\n if (!agent)\n return\n const profile = pickedAgentRef.current\n if (profile.id !== 'build' && profile.id !== 'plan')\n return\n const unregister = agent.hooks.hook('system:transform', (ctx) => {\n const allowInteraction = allowInteractionRef.current !== false\n const envOpts = {\n cwd,\n ...(projectDir !== cwd ? { projectRoot: projectDir } : {}),\n allowInteraction,\n }\n const freshEnv = envSection(envOpts)\n ctx.system = ctx.system.replace(/<env>[\\s\\S]*?<\\/env>/, freshEnv)\n })\n return unregister\n }, [cwd, projectDir])\n\n /**\n * Live registry of in-flight tool calls — populated by `tool:before`\n * (and `child:tool:before`), drained by `tool:after` / `tool:error` /\n * `tool:cancelled` (and their `child:*` siblings). Drives the\n * \"cancel tool call\" picker (see {@link CancelToolModal}).\n *\n * Mirrored in `inFlightToolsRef` for callbacks that read the latest\n * value synchronously without listing `inFlightTools` in their deps —\n * the picker open path needs both: a state snapshot for the modal's\n * rendered rows, and the live ref for any subsequent cancel actions\n * that fire after the snapshot was taken.\n *\n * Excludes `mcp:tool:before` / `mcp:tool:after` deliberately for\n * v1 — `agent.cancelTool(callId)` operates on the unified loop-side\n * callId registry, which MCP tools also live in (they're dispatched\n * through the same `executeSingleTool`), so the cancel works for\n * them too; we just don't surface them in the picker UI yet to\n * avoid drowning the list with high-cardinality MCP transient calls.\n */\n const [inFlightTools, setInFlightTools] = useState<readonly InFlightToolCall[]>([])\n const inFlightToolsRef = useRef<readonly InFlightToolCall[]>([])\n inFlightToolsRef.current = inFlightTools\n\n /**\n * Live registry of running background tasks — populated by\n * `background:start` (and `child:background:start`), drained by\n * `background:exit` (and the child sibling). Same shape as\n * {@link InFlightToolCall} so it merges cleanly into the cancel-tool\n * picker's snapshot; the `kind: 'task'` discriminator routes the\n * cancel callback to `agent.killBackgroundTask(taskId)` instead of\n * `agent.cancelTool(callId)`.\n *\n * Tracked separately from `inFlightTools` because the two have\n * different lifetimes: a tool call is in-flight only while\n * `tool:before` and `tool:after` straddle, but a background task\n * survives PAST its spawning tool call — the `shell` body returns\n * the handle and `tool:after` fires while the task keeps running.\n * Without this separate registry, `ctrl+k` would never see a task\n * because the tool entry has already drained.\n */\n const [backgroundTasks, setBackgroundTasks] = useState<readonly InFlightToolCall[]>([])\n const backgroundTasksRef = useRef<readonly InFlightToolCall[]>([])\n backgroundTasksRef.current = backgroundTasks\n\n /**\n * Names of currently-active skills, tracked via `skills:activate` /\n * `skills:deactivate` hooks. Drives the footer's \"✦ N skill(s)\"\n * chip — the user's only passive surface for noticing that a skill\n * (and its `allowed-tools` restrictions) is in effect. Cleared on\n * session teardown alongside the rest of the per-session live state.\n *\n * Stored as a Set rather than an array so dedup is structural (a\n * runaway `skills:activate` for the same name doesn't inflate the\n * count). React state is the snapshot we render against; a fresh\n * Set per update gives React identity-based change detection.\n */\n const [activeSkillNames, setActiveSkillNames] = useState<ReadonlySet<string>>(() => new Set<string>())\n /**\n * Mirror of {@link activeSkillNames} for synchronous reads in\n * `onSubmitPrompt`. The submit path runs outside React's render cycle\n * and needs to pre-activate every user-pinned skill before\n * `agent.run()` — listing the state in `useCallback`'s deps would\n * re-bind the handler on every activation change, which would\n * invalidate the textarea's submit binding on every `/skill` trigger.\n * The ref keeps the binding stable and the read fresh.\n */\n const activeSkillNamesRef = useRef<ReadonlySet<string>>(activeSkillNames)\n activeSkillNamesRef.current = activeSkillNames\n\n // Stable helpers — register/unregister are wired into the agent's\n // tool:* hooks (see `buildAgent` below) and into the cancel modal's\n // close path. Keeping them as `useCallback([])` keeps the hook\n // closures referentially stable across re-renders.\n const registerInFlightTool = useCallback((entry: InFlightToolCall) => {\n setInFlightTools((prev) => {\n // Idempotent: callId is the unique key. A duplicate `tool:before`\n // (shouldn't happen, but defensive) replaces rather than dupes.\n const filtered = prev.filter(e => e.callId !== entry.callId)\n return [...filtered, entry]\n })\n }, [])\n const unregisterInFlightTool = useCallback((callId: string) => {\n setInFlightTools(prev => prev.filter(e => e.callId !== callId))\n }, [])\n /**\n * In-flight auto-compaction promise. Held in a ref so the next\n * `onSubmitPrompt` invocation can `await` it before calling\n * `agent.run()` — this is what prevents a user-submitted prompt from\n * racing the compaction's `appendTurns(summaryTurn, …restored)` and\n * picking up the pre-compaction history mid-write.\n *\n * `null` between compactions. The promise itself resolves on success,\n * rejects on compaction error — callers should always await with\n * `.catch(() => {})` if they don't care to propagate the failure (the\n * compaction's own error path already surfaces it as a transcript\n * event).\n */\n const autoCompactInFlightRef = useRef<Promise<void> | null>(null)\n /**\n * `AbortController` paired with {@link autoCompactInFlightRef}. Aborted\n * by `teardown()` so navigating away from a session mid-compaction\n * cancels the LLM call instead of letting it run to completion against\n * a now-stale view of the session.\n */\n const autoCompactAbortRef = useRef<AbortController | null>(null)\n /**\n * Forward ref to {@link triggerAutoCompactIfNeeded}, hoisted up here so\n * callbacks declared earlier in the file (`continueResumedInteractions`,\n * `onSubmitPrompt`) can fire the trigger without depending on the\n * callback's lexical position. Initialized with a no-op and populated\n * via `useEffect` once `triggerAutoCompactIfNeeded` is defined below.\n */\n const triggerAutoCompactRef = useRef<(sessionId: string) => void>(() => {})\n\n const stream = useStreamBuffer(setEvents, {\n getSmooth: useCallback(() => smoothStreamingRef.current, []),\n })\n // Mirror into the forward ref so callbacks declared earlier (gate\n // handlers) can reach the latest buffer identity without listing it\n // as a dep. `useStreamBuffer` returns a stable object via `useMemo`,\n // so this assignment lands once and never re-fires.\n streamRef.current = stream\n\n const makePicked = useCallback((provider: ProviderAuth, modelId?: string): Picked | null => {\n // If the host narrowed the provider registry we may have a detected auth\n // with no descriptor to back it. Return null and let the caller bail; the\n // AuthScreen already filters these out, so this is a belt-and-braces guard.\n const descriptor = providerRegistry[provider.key]\n if (!descriptor)\n return null\n const remembered = initialState.lastModelByProvider?.[provider.key]\n // Prefer remembered → descriptor default → factory probe. Factories may\n // throw on missing credentials at construction time; by the time we get\n // here we've already detected auth, so the probe is safe, but the\n // descriptor default lets us avoid constructing in the common case.\n const model = modelId ?? remembered ?? descriptor.defaultModel ?? descriptor.factory().meta.defaultModel\n const effort = effortForModel(descriptor, model, initialState.lastEffortByModel)\n return effort ? { provider, model, effort } : { provider, model }\n }, [providerRegistry, initialState])\n\n // Shared abort sequence — flushes every awaiter (approval queue,\n // interactions queue, message queue) and signals the in-flight run to\n // stop. Hoisted above `buildAgent` so the gate handlers wired in there\n // can trigger it on user denial (see `applyGate`). The user-facing esc\n // path (`onAbort` below) wraps this with its popup short-circuit.\n const cancelRunOnDenial = useCallback((reason: string) => {\n denyAll()\n interactions.cancelAll(reason)\n messageQueueRef.current = []\n setMessageQueue([])\n setQueueSelectionIndex(null)\n agentRef.current?.abort()\n }, [denyAll, interactions])\n\n // -------------------------------------------------------------------------\n // Agent lifecycle: build a fresh Agent bound to the active Session and wire\n // streaming hooks. Re-created on every session switch so context never bleeds.\n // -------------------------------------------------------------------------\n\n const buildAgent = useCallback((session: Session, key: ProviderKey): Agent => {\n const descriptor = providerRegistry[key]\n if (!descriptor)\n throw new Error(`No provider registered for key \"${key}\"`)\n // Read the latest profile via the ref so a switch made between renders\n // (onPickAgent → activateSession in the same tick) picks up the new\n // preset without forcing `buildAgent` to depend on `pickedAgent`.\n const profile = pickedAgentRef.current\n // Compose the runtime preset:\n // - `skills`: project + user discovery paths, filtered by the user's\n // `enabledSkills` allowlist. Profile-supplied skills overlay last\n // so a preset that customizes (e.g.) `maxActive` still wins.\n // - `mcpServers`: discovered `.{prefix}/mcps.json` entries filtered\n // by `enabledMcps`. Profile-supplied servers concat after, so a\n // hard-coded \"test-only\" server in the preset isn't masked by a\n // project file that didn't declare it.\n //\n // All inputs read via refs — toggling skills / MCPs takes effect at\n // the NEXT session activation, not mid-stream.\n const skillsScan = defaultSkillScanPaths({ cwd: projectDir, prefix: config.prefix })\n const skillsConfig = buildSkillsConfig({ scan: skillsScan, enabled: enabledSkillsRef.current })\n const projectMcps = buildMcpServers({\n discovered: mcpsCatalogRef.current,\n enabled: enabledMcpsRef.current,\n })\n // `present_plan` + `ask_user` — wired here so the React-side queue\n // resolves the tool's Promise. The profile's own tools win on name\n // collision (a host that registers a custom `ask_user` for testing\n // shouldn't see ours silently shadow it).\n // `Settings.allowInteraction=false` strips both `ask_user` and\n // `present_plan` so the model can't pause the conversation. Gating\n // happens at the host (not inside `createInteractionTools`) because\n // it's a policy decision, not a property of the tools themselves.\n // The system prompt's interaction guidance + plan-mode doctrine\n // adapt to the same flag below.\n const allowInteraction = allowInteractionRef.current !== false\n const interactionTools: Record<string, ToolDef> = allowInteraction\n ? createInteractionTools({ requestInteraction: makeRequestInteraction(interactions) })\n : {}\n // Re-compose the system prompt with the agent's real cwd + project\n // root injected into the env section. ONLY for the two built-in\n // profiles — a host-supplied profile keeps its own `preset.system`\n // verbatim (otherwise we'd silently overwrite it).\n //\n // The TUI's `projectDir` is the git root (or `process.cwd()` outside\n // a repo). The agent's tools resolve paths against `process.cwd()`\n // — which can be a subdirectory of the project. Telling the model\n // both anchors closes the gap that otherwise lets `@`-completion\n // paths (project-root-relative) get misinterpreted as cwd-relative\n // by `read_file` etc. The env block's path-resolution note kicks\n // in only when the two differ; running from the project root keeps\n // the prompt unchanged.\n const actualCwd = cwdRef.current\n // Personal instructions from AGENTS.md / CLAUDE.md (project + user,\n // gated by the `userInstructionsScope` setting). Loaded once per\n // session activation — cheap (a handful of existsSync + small reads)\n // and the file rarely changes mid-session. Re-loading on every turn\n // would churn the prompt cache for no benefit.\n const agentsMd = discoverAgentsMd({\n cwd: actualCwd,\n prefix: config.prefix,\n scope: userInstructionsScopeRef.current,\n })\n const envOpts = {\n cwd: actualCwd,\n ...(projectDir !== actualCwd ? { projectRoot: projectDir } : {}),\n allowInteraction,\n ...(agentsMd.block ? { userInstructions: agentsMd.block } : {}),\n }\n const builtInSystem = profile.id === 'build'\n ? buildBuildSystem(envOpts)\n : profile.id === 'plan'\n ? buildPlanSystem(envOpts)\n : null\n // Disk persistence wiring:\n // - persistDir: per-session subdir under `cacheDir`, always pinned to\n // the user-level cache (not project dir) so blobs never leak into a\n // checked-in `.{prefix}/` directory. In XDG mode that's\n // `$XDG_CACHE_HOME/zidane/<sid>/persisted-tool-results/`; in legacy\n // mode it collapses to `<userDir>/<sid>/...` (same as before).\n // - off-switch: when `Settings.persistToolResults` is false we override\n // `behavior.persistThreshold = 0`, which the loop reads as \"disabled\"\n // (the persistence branch in `emitToolResult` short-circuits before\n // touching disk). The exclude list + dir are still set, so flipping\n // back on takes effect at the next session activation without rewiring.\n const persistDir = resolvePersistDir({ userDir: cacheDir, sessionId: session.id })\n const persistDisabled = persistToolResultsRef.current === false\n const persistBehavior = persistDisabled\n ? { persistThreshold: 0, persistDir }\n : { persistDir }\n // Per-session background-tasks dir — pairs with `persistDir`'s layout\n // but lives under `<cacheDir>/<sessionId>/tasks/` so the session's\n // ephemeral task logs cluster separately from the chat-wide\n // tool-result blobs. Created on first write by the context.\n const tasksDir = resolveTasksDir({ userDir: cacheDir, sessionId: session.id })\n const agent = createAgent({\n ...profile.preset,\n ...(builtInSystem ? { system: builtInSystem } : {}),\n skills: { ...skillsConfig, ...(profile.preset.skills ?? {}) },\n mcpServers: [...projectMcps, ...(profile.preset.mcpServers ?? [])],\n tools: { ...interactionTools, ...(profile.preset.tools ?? {}) },\n behavior: { ...(profile.preset.behavior ?? {}), ...persistBehavior, tasksDir },\n execution: createProcessContext({ cwd: actualCwd }),\n provider: descriptor.factory(),\n session,\n // Inject OAuth-aware connector. The connector builds a non-interactive\n // McpOAuthProvider per OAuth-tagged server — bootstrap uses stored\n // tokens / refresh only. Missing tokens surface via\n // `mcp:auth:required` (see bootstrapServer in src/mcp/index.ts) and\n // the server is skipped. Interactive login lives in `onLoginMcp`\n // below; it writes to the same `mcpCredentialStore` so the next\n // session's bootstrap picks the tokens up.\n //\n // Closure captures `agent` for `agent.hooks` — referenced lazily\n // (first run / warmup), by which point the const is initialized.\n mcpConnector: configs => connectMcpServers(configs, undefined, agent.hooks, {\n buildAuthProvider: cfg => new McpOAuthProvider({\n name: cfg.name,\n store: mcpCredentialStore,\n }),\n }),\n })\n\n // Safe-mode gates -----------------------------------------------------\n // Four hooks: native vs MCP, parent vs subagent. The `child:*:gate`\n // events bubble from every subagent's hook bus via `bubbleHooks` (see\n // `src/tools/spawn.ts`) with the **same** gate ctx, so writes to\n // `block` / `reason` here propagate straight back to the child's loop.\n // A denied call never reaches `tool:before`, so it doesn't pollute the\n // transcript — the model receives `Blocked: …` as the denied tool's\n // result and the turn continues. Each pending approval resolves on\n // its own; the user must hit `esc abort run` (→ `onAbort`) to stop\n // the whole run.\n const emitUpstreamDeniedEvent = (\n name: string,\n input: Record<string, unknown>,\n ctx: { callId: string, reason: string, turnId?: string },\n ): void => {\n const payload = extractEditPayload(name, input)\n if (!payload)\n return\n const reason = ctx.reason || 'denied'\n const enriched: EditPayload = {\n ...payload,\n outcomes: payload.hunks.map(() => ({ kind: 'denied', reason })),\n }\n streamRef.current?.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(name, input),\n tool: name,\n input,\n edit: enriched,\n callId: ctx.callId,\n ...(ctx.turnId ? { turnId: ctx.turnId } : {}),\n })\n }\n\n const applyGate = async (\n name: string,\n input: Record<string, unknown>,\n ctx: {\n callId: string\n block: boolean\n reason: string\n turnId?: string\n // `tool:gate` / `child:tool:gate` / `mcp:tool:gate` ctx all carry\n // a mutable `input` slot (see `loop.ts:executeSingleTool`). The\n // typed declaration is a structural superset so this handler\n // works across all four gate hooks without per-hook overloads.\n input: Record<string, unknown>\n // Set by `bubbleHooks` (see `src/tools/spawn.ts`) on child\n // gate ctxs; absent on parent ctxs. Drives the modal's\n // `· child-N` attribution label and the gate's\n // {@link ApprovalOriginator} payload.\n childId?: string\n },\n ): Promise<void> => {\n // Upstream gate (skills / budgets / dedup) already refused this call.\n // For file-edit tools we still want the transcript to show the\n // intended diff with a \"denied (<reason>)\" tag — otherwise the user\n // is left guessing what the assistant tried to do.\n if (ctx.block) {\n emitUpstreamDeniedEvent(name, input, ctx)\n return\n }\n const originator: ApprovalOriginator = ctx.childId\n ? { kind: 'child', label: ctx.childId }\n : { kind: 'parent' }\n const outcome = await gateDecision(name, input, ctx.turnId, ctx.callId, originator)\n if (outcome.kind === 'deny') {\n // Refuse THIS call only — the harness writes `Blocked: <reason>`\n // as the tool result and the model continues the turn. Crucially\n // we do NOT call `cancelRunOnDenial()` here: when several tool\n // calls fire in parallel, a single user deny would otherwise\n // resolve every queued approval with `deny` (via `denyAll()`)\n // and cancel the run. The user's explicit \"stop everything\"\n // gesture is the `esc abort run` shortcut → `onAbort()`.\n ctx.block = true\n ctx.reason = 'User denied this tool call'\n // For an edit-family tool with hunks, emit a synthetic\n // `tool-result` event carrying the `<edit-outcomes>` block so\n // the live transcript reconstructs the same per-hunk badges\n // a partial-with-all-denied call would produce. Persisted\n // history still shows `Blocked: ...` (the substitute path\n // isn't used here so the wire stays terse) — replay of a\n // denied call from disk will only show the synthetic `tool`\n // event's diff badges, not the `tool-result` annotation.\n if (outcome.outcomes && outcome.outcomes.length > 0) {\n streamRef.current?.appendImmediate({\n kind: 'tool-result',\n text: `[fully denied] ${buildEditOutcomesAnnotation(outcome.outcomes)}`,\n tool: name,\n callId: ctx.callId,\n ...(ctx.turnId ? { turnId: ctx.turnId } : {}),\n })\n }\n return\n }\n if (outcome.kind === 'partial') {\n // Rebind `ctx.input` to a fresh shallow-clone whose `edits`\n // list contains only the approved subset. The tool body sees\n // the smaller batch and runs its single-mode atomic semantics\n // (multi_edit) against it. The model's ORIGINAL `tool_call`\n // block in `ctx.turns` stays untouched because the rebind\n // produces a new object — it never mutates the shared `input`\n // reference. `tool:transform` (registered below) appends the\n // `<edit-outcomes>` annotation to the resulting tool_result\n // so the wire / persisted history carries the per-hunk\n // decisions for replay.\n ctx.input = { ...input, edits: outcome.reducedEdits }\n pendingAnnotationsRef.current.set(ctx.callId, outcome.outcomes)\n }\n // `allow` → call proceeds with the model's original input.\n }\n\n agent.hooks.hook('tool:gate', ctx => applyGate(ctx.name, ctx.input, ctx))\n agent.hooks.hook('child:tool:gate', ctx => applyGate(ctx.name, ctx.input, ctx))\n agent.hooks.hook('mcp:tool:gate', ctx => applyGate(ctx.displayName, ctx.input, ctx))\n agent.hooks.hook('child:mcp:tool:gate', ctx => applyGate(ctx.displayName, ctx.input, ctx))\n\n // Per-edit annotation — runs after the tool body produces a result,\n // before persistence / wire emission. Mutates `ctx.result` in place\n // so the annotation rides the canonical tool_result text into both\n // the live transcript (via `tool:after`) and the persisted history\n // (via the loop's user-turn tool_result block).\n //\n // Three sources of truth, merged in this order:\n // 1. **Approval-side outcomes** (`pendingAnnotationsRef`) — 1:1\n // with the model's ORIGINAL `edits` list. `applied` is a\n // placeholder for hunks the user kept; `denied` / `skipped`\n // reflect the user's gate decision.\n // 2. **Body-side outcomes** — `multi_edit`'s best-effort body\n // emits an `<edit-outcomes>` block when any step failed. Keyed\n // against the APPROVED SUBSET (the rebound `ctx.input.edits`\n // the body actually saw), in subset-position order.\n // 3. **Merged outcomes** — `mergeApprovalAndBodyOutcomes` walks\n // approval and replaces each `applied` placeholder with the\n // body's next outcome, so the canonical block carries denied,\n // skipped, AND failed entries 1:1 with the original edits.\n //\n // The body's block is stripped before re-appending the merged one;\n // otherwise the parser (anchored on the FIRST block) would see the\n // subset-keyed body annotation and lose the gate decisions. The\n // body's header is rewritten via `rewriteMultiEditHeader` so the\n // subset-relative `N of M` count is replaced by the original total\n // (`M = approval.length`).\n //\n // Returns the post-transform result AND the final outcomes (or\n // `null` when no annotation work was needed). The caller uses the\n // outcomes to update the in-flight `tool` event so live diff badges\n // catch up to body-side failures the gate couldn't anticipate.\n const annotateEditResult = (\n toolName: string,\n callId: string,\n result: string | ToolResultContent[],\n input: Record<string, unknown>,\n ): {\n result: string | ToolResultContent[]\n mergedOutcomes: readonly EditOutcome[] | null\n } => {\n const approval = pendingAnnotationsRef.current.get(callId)\n if (approval)\n pendingAnnotationsRef.current.delete(callId)\n\n // Plain-text path — overwhelmingly the common shape for edit\n // tool results. Structured content arrays only show up for\n // tools that return images (read_file with `media: 'image'`);\n // edit-family tools always return strings.\n if (typeof result === 'string') {\n const bodyOutcomes = parseEditOutcomesFromResult(result)\n\n // Pass-through fast path: no approval state AND body didn't\n // emit a block — the legacy \"all applied, summary-only\" shape.\n if (!approval && !bodyOutcomes)\n return { result, mergedOutcomes: null }\n\n // No approval — body's annotation (when present) is already\n // keyed 1:1 with the model's input since no subset rebinding\n // happened. Pass the text through unchanged; surface\n // `bodyOutcomes` so the in-flight `tool` event picks up live\n // badges for body-side failures.\n if (!approval)\n return { result, mergedOutcomes: bodyOutcomes }\n\n // Approval present — merge with body (or just approval if body\n // didn't emit). ALWAYS strip a body-side `<edit-outcomes>`\n // block (well-formed OR malformed-but-textually-present) before\n // re-appending the merged version, so the parser doesn't see\n // duplicates. `stripEditOutcomesAnnotation` is a no-op on input\n // without a properly-anchored block, so this is safe in the\n // \"no body block\" case too.\n const merged = mergeApprovalAndBodyOutcomes(approval, bodyOutcomes)\n const stripped = stripEditOutcomesAnnotation(result)\n // Only multi_edit emits \"applied N of M edits\" headers; edit /\n // write_file headers (`Edited X: replaced N occurrences.` /\n // `Created/Updated X (N bytes).`) don't carry counts the merge\n // could disturb, so the rewrite is a no-op on those by virtue\n // of the regex not matching — but we still gate on `toolName`\n // for clarity.\n const path = typeof input.path === 'string' ? input.path : ''\n const rebuilt = toolName === 'multi_edit' && path\n ? rewriteMultiEditHeader(stripped, merged, path)\n : stripped\n const annotation = buildEditOutcomesAnnotation(merged)\n const out = rebuilt.length === 0 ? annotation : `${rebuilt}\\n\\n${annotation}`\n return { result: out, mergedOutcomes: merged }\n }\n\n // Structured-content path — only hit when an edit tool returns a\n // ToolResultContent[]. None of the built-in edit tools do; the\n // fallback appends an annotation text block so replay can still\n // recover outcomes.\n if (!approval)\n return { result, mergedOutcomes: null }\n const annotation = buildEditOutcomesAnnotation(approval)\n return {\n result: [...result, { type: 'text', text: `\\n${annotation}` }],\n mergedOutcomes: approval,\n }\n }\n\n // Wire-level annotation lands on `ctx.result` (so the persisted\n // tool_result and the LLM-facing wire both carry the merged block).\n // The in-flight `tool` event's `edit.outcomes` is updated in the\n // same tick so the live diff badges reflect the merged truth —\n // without it, a body-side failure on a partial-approval call would\n // paint `applied` badges until the next session reload.\n const applyEditAnnotationsToStream = (\n callId: string,\n outcomes: readonly EditOutcome[],\n ): void => {\n streamRef.current?.flushAndUpdate(events =>\n updateToolEventOutcomes(events, callId, outcomes),\n )\n }\n\n agent.hooks.hook('tool:transform', (ctx) => {\n const { result, mergedOutcomes } = annotateEditResult(ctx.name, ctx.callId, ctx.result, ctx.input)\n ctx.result = result\n if (mergedOutcomes)\n applyEditAnnotationsToStream(ctx.callId, mergedOutcomes)\n })\n agent.hooks.hook('child:tool:transform', (ctx) => {\n const { result, mergedOutcomes } = annotateEditResult(ctx.name, ctx.callId, ctx.result, ctx.input)\n ctx.result = result\n if (mergedOutcomes)\n applyEditAnnotationsToStream(ctx.callId, mergedOutcomes)\n })\n\n // MCP OAuth status — feed the reducer that backs the picker badge.\n // `dispatchAuthRef` so a hook firing from a long-lived agent doesn't\n // capture a stale dispatch from a previous render.\n agent.hooks.hook('mcp:auth:required', ({ name, reason }) => {\n dispatchAuthRef.current({ type: 'auth-required', name, reason })\n })\n agent.hooks.hook('mcp:auth:url', ({ name, url }) => {\n dispatchAuthRef.current({ type: 'auth-url', name, url })\n })\n agent.hooks.hook('mcp:auth:success', ({ name }) => {\n dispatchAuthRef.current({ type: 'auth-success', name })\n })\n agent.hooks.hook('mcp:auth:error', ({ name, error }) => {\n dispatchAuthRef.current({ type: 'auth-error', name, error: error.message })\n })\n agent.hooks.hook('mcp:connect', ({ name }) => {\n // OAuth-backed servers should land on `authed` even when bootstrap\n // succeeded silently (token already stored, no `needs-auth` blip).\n // Without this branch the row would stay `idle` and the picker\n // wouldn't expose the `o` (logout) action — the user couldn't\n // revoke their own stored credentials.\n if (mcpCredentialStore.load(name)?.tokens)\n dispatchAuthRef.current({ type: 'auth-success', name })\n else\n dispatchAuthRef.current({ type: 'connected', name })\n })\n\n // Parent streams ------------------------------------------------------\n // `turnId` rides every event so the TUI's select-turn mode can group\n // all events emitted by one SessionTurn — matches what the historical\n // path stamps via `eventsFromTurns`.\n agent.hooks.hook('stream:thinking', ({ delta, turnId }) => stream.queueStreamDelta('thinking', delta, { turnId }))\n agent.hooks.hook('stream:text', ({ delta, turnId }) => stream.queueStreamDelta('markdown', delta, { turnId }))\n agent.hooks.hook('tool:before', async ({ callId, name, input, turnId }) => {\n // Track the in-flight call so the cancel picker (Ctrl+K) sees it.\n // We register here rather than on `tool:dispatched` because:\n // - the picker reads \"the model is waiting on this\" semantics,\n // not \"the loop accounted for this call\" semantics; gate-blocked\n // and gate-substituted paths never reach `tool:before` and\n // correctly stay out of the picker (there's nothing for the\n // user to cancel).\n // - the matching `tool:after` / `tool:error` / `tool:cancelled`\n // hooks drain the same entry, so the lifecycle is symmetric.\n registerInFlightTool({ callId, tool: name, startedAt: Date.now() })\n\n // Partial-approval already emitted a synthetic `tool` event with\n // the FULL hunks + outcomes from `gateDecision`; suppressing the\n // default emit here avoids painting a second (reduced-input) row\n // for the same call. The pending entry is cleared in\n // `tool:transform` once the result lands.\n if (pendingAnnotationsRef.current.has(callId))\n return\n // Snapshot the file's current bytes BEFORE the tool runs so the\n // diff renderer can paint real-file line numbers in the gutter\n // (via `buildContextualDiff`) and the compact view can anchor each\n // hunk summary to its actual position. Reading is awaited so the\n // snapshot is guaranteed to be the pre-write state — the cost is\n // one extra fs read per edit call (cheap on local disk; matches\n // what the approval modal already does for previews).\n let priorContent: string | undefined\n if (EDIT_TOOL_NAMES.has(name) && agent.handle && typeof input.path === 'string') {\n try {\n priorContent = await agent.execution.readFile(agent.handle, input.path)\n }\n catch {\n // File doesn't exist (fresh create for `write_file`, or\n // model-side mistake for `edit` / `multi_edit`) — fall through\n // with `priorContent: undefined`. For `write_file`,\n // extractEditPayload normalizes to `oldString: ''` so the diff\n // renders as all-add; for the others the snippet diff path\n // takes over and synthetic line numbers stand in.\n }\n }\n const edit = extractEditPayload(name, input, priorContent)\n stream.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(name, input),\n tool: name,\n input,\n ...(edit ? { edit } : {}),\n callId,\n turnId,\n })\n })\n agent.hooks.hook('tool:after', ({ callId, name, result, turnId }) => {\n // Drain the in-flight registry on successful / errored completion.\n // Symmetric with `tool:before`'s register — the cancel picker only\n // shows truly-live calls.\n unregisterInFlightTool(callId)\n\n // Spawn tool-results carry a `Tokens: …` summary that's already shown\n // by the spawn-end marker right above the tool-result — strip it from\n // the displayed string to avoid the duplicate / format-drift block.\n const raw = toolResultText(result)\n const text = name === 'spawn' ? stripSpawnTokensLine(raw) : raw\n stream.appendImmediate({ kind: 'tool-result', text, tool: name, callId, turnId })\n })\n agent.hooks.hook('tool:error', ({ callId }) => {\n // The loop fires `tool:error` BEFORE `tool:after` only on the\n // execute-branch error path — `tool:after` still fires with the\n // synthesized error result and would also drain the registry. We\n // drain here too so the registry can't survive a pathological\n // hook setup where `tool:after` doesn't fire (e.g. a consumer's\n // own `tool:error` throws); the second `unregisterInFlightTool`\n // in `tool:after` is then a no-op (Map.delete on a missing key).\n unregisterInFlightTool(callId)\n })\n agent.hooks.hook('tool:cancelled', ({ callId }) => {\n // User-issued per-call cancel: the loop short-circuited `tool:after`\n // entirely, so this hook is the only opportunity to drain.\n unregisterInFlightTool(callId)\n })\n\n // Background-task lifecycle — LIVE banner rendering.\n //\n // The framework injects a `<task-notification>` text block onto the\n // next user-turn (see `pendingTaskNotifications` in `src/agent.ts`),\n // which `eventsFromTurns` synthesizes back into a `task-notification`\n // StreamEvent on REPLAY. But during a LIVE session the user-turn\n // is built when they submit a prompt — they'd never see the\n // completion until they typed something. That defeats the whole\n // value prop of backgrounding (\"model goes back to work, user sees\n // task done when it lands\").\n //\n // So we mirror the replay path: when `background:exit` fires,\n // synthesize the SAME StreamEvent shape and push it into the\n // stream immediately. The subsequent prompt's notification block\n // still hits the wire (model side), and `eventsFromTurns` still\n // produces the banner on replay — but we don't double-paint\n // because the live banner doesn't carry a `turnId` (no committed\n // user turn yet) while the replay banner does. The two coexist\n // cleanly: live shows the user, replay shows the wire-equivalent.\n // Background-task lifecycle wiring.\n //\n // Two registries, two event streams, three event sources:\n // - `backgroundTasks` state — feeds the `ctrl+k` picker. Pushed on\n // start, dropped on exit. `kind: 'task'` is the cancel-modal\n // discriminator (routes to `agent.killBackgroundTask` instead\n // of `agent.cancelTool`).\n // - `stream` — feeds the transcript. `background:exit` emits a\n // `task-notification` event so the user sees the completion\n // LIVE; on session reload `eventsFromTurns` reconstructs the\n // same event from the persisted `<task-notification>` XML.\n //\n // Three sources of events: parent's direct `background:*` and\n // bubbled `child:background:*` (one per nesting level — the chain\n // bubbler forwards grandchildren too). All converge through the\n // same two helpers below.\n const registerBackgroundTask = (ctx: { taskId: string, command: string, startedAt: number, childId?: string }) => {\n setBackgroundTasks(prev => [\n ...prev,\n {\n kind: 'task' as const,\n callId: ctx.taskId,\n tool: `shell (background): ${previewLine(ctx.command, 60)}`,\n startedAt: ctx.startedAt,\n ...(ctx.childId ? { childId: ctx.childId } : {}),\n },\n ])\n }\n const dropBackgroundTaskAndEmitBanner = (ctx: {\n taskId: string\n status: 'exited' | 'killed'\n exitCode: number\n signal?: NodeJS.Signals\n outputPath: string\n durationMs: number\n command: string\n childId?: string\n depth?: number\n }) => {\n // Drain the ctrl+k entry — the task is no longer cancellable.\n setBackgroundTasks(prev => prev.filter(t => t.callId !== ctx.taskId))\n streamRef.current?.appendImmediate({\n kind: 'task-notification',\n text: formatTaskSummary(ctx),\n task: {\n taskId: ctx.taskId,\n status: ctx.status,\n exitCode: ctx.exitCode,\n outputPath: ctx.outputPath,\n command: ctx.command,\n durationMs: ctx.durationMs,\n },\n ...(ctx.childId ? { childId: ctx.childId } : {}),\n ...(typeof ctx.depth === 'number' ? { depth: ctx.depth } : {}),\n })\n }\n\n agent.hooks.hook('background:start', ctx => registerBackgroundTask(ctx))\n agent.hooks.hook('background:exit', ctx => dropBackgroundTaskAndEmitBanner(ctx))\n // Reassignment — the spawn tool promotes a subagent's still-running\n // tasks up to the parent's handle when the subagent finishes (see\n // `src/tools/spawn.ts`). Drop the `childId` tag on the matching\n // ctrl+k entry so the picker surfaces it as parent-level and\n // `agent.killBackgroundTask(taskId)` actually finds it. Without\n // this hook the entry would stay marked as a subagent's and be\n // filtered out of the picker even though the task is now reachable.\n agent.hooks.hook('background:reassign', (ctx) => {\n setBackgroundTasks(prev => prev.map(entry =>\n entry.callId === ctx.taskId\n ? { kind: 'task' as const, callId: entry.callId, tool: entry.tool, startedAt: entry.startedAt }\n : entry,\n ))\n })\n // Subagent background tasks bubble through with `childId` / `depth`\n // already attached. The ctrl+k picker filters child entries out\n // (the kill primitive walks the parent's map), but the\n // `background:reassign` listener above promotes them to parent-\n // level once the subagent finishes — so the user gets at them via\n // the picker the moment spawn.ts's finally block runs.\n agent.hooks.hook('child:background:start', ctx => registerBackgroundTask(ctx))\n agent.hooks.hook('child:background:exit', ctx => dropBackgroundTaskAndEmitBanner(ctx))\n\n // Skill activation tracking — drives:\n // 1. the footer's `✦ N skill(s)` chip (passive visibility), and\n // 2. the \"re-activate user-pinned skills on every run\" path in\n // `onSubmitPrompt` (see `userActivatedSkillsRef` below).\n //\n // The set must SURVIVE the framework's run-end deactivate-all, so we\n // filter by reason: `'run-end'` (framework-initiated) keeps the\n // chip's view stable across runs, while `'model'` / `'explicit'` /\n // `'reset'` (user / model intent) drain the entry.\n //\n // Without this filter, every successful run would silently undo the\n // user's \"I activated /read-only\" pin: run ends → set empties → next\n // prompt re-runs but `userActivatedSkillsRef` no longer knows to\n // pre-activate `read-only` → the gate is wide open. That's the\n // exact \"skill activated but bash ran\" bug the user reported.\n agent.hooks.hook('skills:activate', ({ skill }) => {\n setActiveSkillNames((prev) => {\n if (prev.has(skill.name))\n return prev\n const next = new Set(prev)\n next.add(skill.name)\n persistPinnedSkills(session, next)\n return next\n })\n })\n agent.hooks.hook('skills:deactivate', ({ skill, reason }) => {\n // `'run-end'` is the framework's per-run cleanup pass — purely\n // mechanical, no user intent. We keep the set populated so the\n // pre-run re-activation in `onSubmitPrompt` can restore them\n // on the next turn. Model-driven `'model'` deactivates and\n // host-driven `'explicit'` / `'reset'` deactivates remove the\n // entry (the user / model is asking for the pin to be released).\n if (reason === 'run-end')\n return\n setActiveSkillNames((prev) => {\n if (!prev.has(skill.name))\n return prev\n const next = new Set(prev)\n next.delete(skill.name)\n persistPinnedSkills(session, next)\n return next\n })\n })\n agent.hooks.hook('mcp:tool:after', ({ callId, displayName, result, turnId }) => {\n stream.appendImmediate({ kind: 'tool-result', text: toolResultText(result), tool: displayName, callId, turnId })\n })\n agent.hooks.hook('turn:after', ({ usage }) => {\n if (usage) {\n const tokens = turnContextSize(usage)\n // Skip zero-context placeholder turns — the loop synthesizes\n // an assistant turn with `usage: { input: 0, output: 0 }` when\n // streaming aborts/errors before any usage is reported. Letting\n // that overwrite the footer would flash `ctx 0` even though the\n // prior real turn's context is still what the next prompt rides\n // on top of. Same rationale as `lastContextSizeFromTurns`.\n if (tokens > 0) {\n setLastInputTokens(tokens)\n // Mirror in the ref so `onSubmitPrompt`'s post-run auto-compact\n // check reads the freshest value — React state hasn't committed\n // yet by the time `await agent.run()` returns to the caller.\n lastInputTokensRef.current = tokens\n }\n const turnCost = usage.cost ?? 0\n if (turnCost > 0)\n setSessionCost(prev => prev + turnCost)\n }\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n })\n\n // Subagent streams ----------------------------------------------------\n agent.hooks.hook('spawn:before', ({ id, task, depth }) => {\n // `previewLine` collapses inline newlines + tabs into single\n // spaces before truncating — without it, a task whose first\n // line is short but has trailing `\\n\\n …` content paints the\n // spawn-start marker across multiple visual rows and the next\n // event lands with leading whitespace that visually drifts to\n // the right. Same shape for the same reason for every other\n // preview path in this file.\n const taskPreview = previewLine(task, 80)\n stream.appendImmediate({\n kind: 'spawn-start',\n text: taskPreview,\n childId: id,\n depth: depth ?? 1,\n })\n })\n agent.hooks.hook('spawn:complete', ({ id, depth, status, stats }) => {\n const tag = status === 'aborted' ? 'aborted' : status === 'error' ? 'error' : 'done'\n stream.appendImmediate({\n kind: 'spawn-end',\n text: `${tag} ${formatTokenUsage(stats)}`,\n childId: id,\n depth: depth ?? 1,\n })\n })\n agent.hooks.hook('spawn:error', ({ id, depth, error }) => {\n stream.appendImmediate({\n kind: 'error',\n text: `[${id}] ${error.message}`,\n childId: id,\n depth: depth ?? 1,\n })\n })\n agent.hooks.hook('child:stream:thinking', ({ delta, childId, depth, turnId }) => {\n stream.queueStreamDelta('thinking', delta, { childId, depth, turnId })\n })\n agent.hooks.hook('child:stream:text', ({ delta, childId, depth, turnId }) => {\n stream.queueStreamDelta('markdown', delta, { childId, depth, turnId })\n })\n agent.hooks.hook('child:tool:before', ({ callId, name, input, childId, depth, turnId, priorContent }) => {\n // Subagent in-flight tracking mirrors the parent's path.\n // `childId` lets the picker label these \"· child-2\" so the user\n // knows which subagent the call belongs to.\n registerInFlightTool({ callId, tool: name, startedAt: Date.now(), childId })\n\n // Mirror of the parent's `tool:before` suppression — the gate\n // already emitted a synthetic `tool` event with the full hunks\n // + outcomes for a partial-approval call.\n if (pendingAnnotationsRef.current.has(callId))\n return\n // `priorContent` is populated by spawn.ts's `tool:before` enricher\n // when the child writes a file — it reads via the child's own\n // execution context (which may be a docker / sandbox handle the\n // parent can't reach), then mutates the bubbled ctx. For\n // `edit` / `multi_edit` the old content rides `input.old_string`\n // / `input.edits[]` directly and the enricher no-ops.\n const edit = extractEditPayload(name, input, priorContent)\n stream.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(name, input),\n tool: name,\n input,\n ...(edit ? { edit } : {}),\n callId,\n childId,\n depth,\n turnId,\n })\n })\n agent.hooks.hook('child:tool:after', ({ callId, name, result, childId, depth, turnId }) => {\n unregisterInFlightTool(callId)\n stream.appendImmediate({\n kind: 'tool-result',\n text: toolResultText(result),\n tool: name,\n callId,\n childId,\n depth,\n turnId,\n })\n })\n agent.hooks.hook('child:tool:error', ({ callId }) => {\n unregisterInFlightTool(callId)\n })\n agent.hooks.hook('child:tool:cancelled', ({ callId }) => {\n unregisterInFlightTool(callId)\n })\n agent.hooks.hook('child:stream:end', ({ childId }) => {\n // Finalize the child's trailing markdown block so the next child event\n // (tool call, spawn-end) doesn't keep appending into it.\n stream.flushAndUpdate(prev => finalizeStreamingMarkdownForOwner(prev, childId))\n })\n\n // Run-end sweep — clear any pending edit-outcome annotations whose\n // matching `tool:transform` never fired. Reachable paths that strand\n // an entry after a partial-approval gate:\n // - `validation:reject` returns from `executeSingleTool` before\n // `tool:transform` runs.\n // - A throwing `tool:before` / `tool:gate` handler unwinds via\n // `executeToolBatch`'s per-call onrejected branch, which\n // synthesizes an error tool_result without firing `tool:transform`.\n // Both are bounded by run lifetime; we sweep here so the map can't\n // accumulate across many runs on the same agent. New runs mint fresh\n // callIds, so wiping at run end never drops a still-needed entry.\n agent.hooks.hook('agent:done', () => {\n pendingAnnotationsRef.current.clear()\n })\n\n return agent\n }, [providerRegistry, stream, gateDecision, cancelRunOnDenial, projectDir, config.prefix, interactions, dataDir, cacheDir, mcpCredentialStore, registerInFlightTool, unregisterInFlightTool])\n\n // -------------------------------------------------------------------------\n // Session activation — load (or create) a Session, rebuild the agent, replay\n // turns into the transcript, and persist the resume hint.\n // -------------------------------------------------------------------------\n\n const refreshSessions = useCallback(async () => {\n // Scope the list to the current project unless the user has opted\n // into the cross-project view via `Settings.showAllProjects`. The\n // store filter does the heavy lifting (SQL `WHERE project_root =\n // ?`), so the cost is one round-trip even on a huge global DB.\n const filter = settings.showAllProjects ? undefined : { projectRoot: projectDir }\n const list = await listSessionMeta(store, filter)\n setSessions(list)\n return list\n }, [store, settings.showAllProjects, projectDir])\n\n const teardown = useCallback(async () => {\n // Abort the active run + drain the safe-mode queue *before* destroying\n // the agent. Without this:\n // - `agent.destroy()` is not an abort — it just closes MCP and the\n // execution handle, leaving any in-flight `run()` to keep firing\n // stream/tool hooks. Those write into the shared `stream` buffer,\n // so deltas from the doomed run land in the new session's\n // transcript after `activateSession` mounts it.\n // - Pending approvals (head-of-queue + tail) stay parked, so the\n // next prompt that opens the chat screen inherits them.\n //\n // Order matters: `denyAll()` first so any gate handler currently\n // awaiting `requestApproval(...)` resolves with `deny` and the loop\n // unwinds cleanly; `abort()` second so the abort signal interrupts the\n // next iteration; `destroy()` last to close handles after the loop\n // unwound. Each step is independently best-effort — exceptions are\n // logged and swallowed so a partial failure doesn't leak state.\n try {\n denyAll()\n }\n catch (err) {\n debugLog('teardown: denyAll failed', err)\n }\n try {\n // Same rationale as `denyAll()` above: any pending `present_plan` /\n // `ask_user` tool whose Promise is parked must unwind before\n // `agent.abort()` so the loop can finish unwinding cleanly.\n // Resumed-flow entries (no live Promise) are also drained — their\n // `cancel` is a no-op resolver on the App-side, so it's safe.\n interactions.cancelAll('session teardown')\n }\n catch (err) {\n debugLog('teardown: interactions.cancelAll failed', err)\n }\n try {\n agentRef.current?.abort()\n }\n catch (err) {\n debugLog('teardown: agent.abort failed', err)\n }\n stream.reset()\n await agentRef.current?.destroy().catch(err => debugLog('agent.destroy failed', err))\n agentRef.current = null\n sessionRef.current = null\n // Cancel any in-flight auto-compaction and drop the references so\n // the next session's first `onSubmitPrompt` doesn't gate itself on\n // the prior session's background compaction. Aborting the LLM call\n // also saves the wasted tokens that would otherwise be spent\n // summarising a session the user has navigated away from. The\n // compaction's own handlers check `sessionRef.current?.id` against\n // the captured sessionId and skip emitting transcript events for a\n // dead session, so this abort is purely about cost.\n autoCompactAbortRef.current?.abort()\n autoCompactAbortRef.current = null\n autoCompactInFlightRef.current = null\n setCompacting(false)\n // Drop the user-prompt queue and clear the running gate so the next\n // session's first submit re-enters the drain loop cleanly. Without\n // resetting `runningRef`, a teardown mid-drain would leave the gate\n // stuck and every subsequent submit would silently enqueue forever.\n // Also drop `queueSelectionIndex` — leaving stale selection state\n // would survive into the next session and put the user in a bogus\n // selection mode with no visible target.\n messageQueueRef.current = []\n setMessageQueue([])\n setQueueSelectionIndex(null)\n runningRef.current = false\n sessionSafelistRef.current.clear()\n // Belt-and-suspenders for the `agent:done` sweep registered in\n // `buildAgent`: a teardown mid-run (abort + destroy) races the\n // run's own `agent:done` emission — `destroy()` doesn't fire it,\n // and the aborted path inside `run()` may not have committed yet\n // when we get here. Wiping the map outright on session swap keeps\n // partial-approval residue from surviving into the next session.\n pendingAnnotationsRef.current.clear()\n // Drop the in-flight tools snapshot — a teardown mid-run leaves\n // entries the next session would otherwise inherit, and the cancel\n // picker would happily try to call `agent.cancelTool(callId)` on\n // an agent that no longer owns those calls.\n setInFlightTools([])\n // Same for background tasks. The execution context's `destroy()`\n // (inside `agent.destroy()`) SIGTERMs every still-running task,\n // so the previous agent's tasks are actually killed by the time\n // we reach this state-clear; this just clears the React mirror.\n setBackgroundTasks([])\n // Same rationale for the active-skills chip: the next session's\n // fresh agent will re-emit `skills:activate` events (resume\n // rehydration or model-driven), so we start empty.\n setActiveSkillNames(new Set<string>())\n }, [stream, denyAll, interactions])\n\n /**\n * Continue a session from where pending interactions left it. Appends\n * a single user turn containing one `tool_result` per pending request\n * (in pending order) and triggers a prompt-less `agent.run()` so the\n * model resumes.\n *\n * The single-turn / multi-`tool_result` shape is load-bearing: if the\n * model emitted parallel interaction calls (e.g. `present_plan` +\n * `ask_user` in the same assistant turn) the agent loop's history\n * contract requires every `tool_use` block to be matched by exactly\n * one `tool_result` in the very next user turn. Writing one user\n * turn per submission would persist a turn missing the sibling's\n * match and the next API call would reject the history — see\n * {@link buildResumedToolResultsTurn} for the protocol notes.\n */\n const continueResumedInteractions = useCallback(async (\n requests: readonly InteractionRequest[],\n responses: ReadonlyMap<string, InteractionResponse>,\n ) => {\n const session = sessionRef.current\n const agent = agentRef.current\n const currentPicked = pickedRef.current\n if (!session || !agent || !currentPicked) {\n debugLog('resume-interaction: missing session/agent/picked', { count: requests.length })\n return\n }\n const turnId = await session.generateTurnId()\n // The run id is shared across all requests in a batch (parallel\n // tool calls live in the same assistant turn → same `runId`). Pick\n // the first request's run id; missing ones fall back to `undefined`\n // which `buildResumedToolResultsTurn` silently drops.\n const runId = requests[0]?.runId\n let turn: SessionTurn\n try {\n turn = buildResumedToolResultsTurn(requests, responses, { turnId, runId })\n }\n catch (err) {\n // Defensive — `activateSession` only flushes when `responses` is\n // complete, so this path is unreachable. Surface in debug logs\n // so a future flush-too-early bug is loud.\n debugLog('resume-interaction: turn build failed', err)\n return\n }\n await session.appendTurns([turn])\n setEvents(eventsFromTurns(session.turns, session.runs))\n setBusy(true)\n try {\n await agent.run({\n model: currentPicked.model,\n ...(currentPicked.effort ? { thinking: currentPicked.effort } : {}),\n })\n await session.save().catch(err => debugLog('resume-interaction: session.save failed', err))\n setCurrentSession(prev => prev\n ? {\n ...prev,\n title: deriveSessionTitle(session.turns, session.metadata),\n turnCount: session.turns.length,\n userMessageCount: session.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n runCount: session.runs.length,\n updatedAt: Date.now(),\n }\n : prev)\n // Post-run auto-compaction check — same hook as `onSubmitPrompt`.\n // Interaction-resolution turns can grow the context just as much\n // as user-prompted turns (the model may execute an approved plan\n // that consumes thousands of tokens), so the trigger has to fire\n // here too or sessions that loop through `present_plan` /\n // `ask_user` would never auto-compact.\n triggerAutoCompactRef.current(session.id)\n }\n catch (err) {\n stream.appendImmediate({ kind: 'error', text: errorMessage(err) })\n }\n finally {\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n setBusy(false)\n }\n }, [stream])\n\n const activateSession = useCallback(async (id: string | null, key: ProviderKey) => {\n await teardown()\n\n const loaded = id ? await loadSession(store, id) : null\n // Stamp the current project root on FRESH sessions only. A loaded\n // session keeps whatever `projectRoot` it was created with — the\n // tag is sticky for the session's lifetime regardless of where\n // the user is when they reopen it.\n const session = loaded\n ?? await createSession({ store, projectRoot: projectDir, ...(id ? { id } : {}) })\n\n sessionRef.current = session\n agentRef.current = buildAgent(session, key)\n\n // Seed the pinned active-skills set from session metadata. Slash-\n // command activations (`/skill-name`) don't produce a `skills_use`\n // tool_call block — they go through `agent.activateSkill()` directly\n // — so the agent's session-resume rehydrator wouldn't restore them\n // on its own. The TUI persists the pinned set to\n // `metadata['zidane.activeSkills']` and reads it back here so a TUI\n // restart picks the user's pins right back up.\n //\n // The hook handlers in `buildAgent` mirror live mutations into the\n // same metadata key, so the persisted value stays in sync without\n // an explicit save step on every `/skill` keystroke. Falls back to\n // an empty Set when the field is missing or shaped wrong (older\n // sessions, hand-edited metadata) — never throws.\n const persistedPins = readPinnedSkills(session.metadata['zidane.activeSkills'])\n setActiveSkillNames(persistedPins)\n\n setEvents(eventsFromTurns(session.turns, session.runs))\n const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs)\n setLastInputTokens(replayedTokens)\n lastInputTokensRef.current = replayedTokens\n // New session activation — drop any prior compaction baseline so the\n // first compaction in this session fires off the absolute threshold.\n // We deliberately do NOT try to re-derive the baseline from persisted\n // turns: the marker turn doesn't record `effectiveTokens` and the\n // per-turn input usage at compaction time is lost to truncation. A\n // fresh hysteresis window after reload is the correct semantic.\n lastCompactedInputTokensRef.current = undefined\n setSessionCost(sumRunCosts(session.runs))\n setCurrentSession({\n id: session.id,\n title: deriveSessionTitle(session.turns, session.metadata),\n turnCount: session.turns.length,\n userMessageCount: session.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n runCount: session.runs.length,\n updatedAt: Date.now(),\n })\n setScreen('chat')\n stateStore.save({\n ...stateStore.load(),\n lastProvider: key,\n lastSessionId: session.id,\n })\n\n // Resumed-interaction enqueue. When a session was killed mid-prompt\n // (`present_plan` / `ask_user` awaiting a reply), the persisted\n // assistant turn carries one or more `tool_call`s without matching\n // `tool_result`s. We surface them in the same picker UI as a live\n // call; the resolver chain below collects each answer and flushes\n // a single batched user turn once they're all in. Graceful aborts\n // persist a `Tool error` result through the live tool path, so\n // they never re-enqueue here.\n //\n // The collected map is shared across every entry's resolver so\n // multi-call batches (the model emitted parallel interaction\n // tools) end up in ONE user turn — the agent loop's history\n // contract requires every `tool_use` to be paired with a\n // `tool_result` in the immediately-following user turn.\n const pending = pendingInteractionsFromTurns(session.turns)\n if (pending.length > 0) {\n const collected = new Map<string, InteractionResponse>()\n for (const request of pending) {\n const entry: PendingInteractionEntry = {\n request,\n resolve: (response) => {\n collected.set(request.id, response)\n if (collected.size >= pending.length)\n void continueResumedInteractions(pending, collected)\n },\n // `cancel` is intentionally a no-op: dropping the entry from\n // the queue (via `cancelAll` on teardown) leaves the\n // session's unresolved `tool_call` intact, so the next\n // activation re-enqueues it — the user can come back to the\n // question. A run-level abort, by contrast, has already\n // persisted a `Tool error` result through the live tool path\n // before this function ever runs.\n cancel: () => {},\n }\n interactions.enqueue(entry)\n }\n }\n }, [teardown, buildAgent, store, stateStore, projectDir, interactions, continueResumedInteractions])\n\n // -------------------------------------------------------------------------\n // Resume on launch — when a previous provider was remembered, jump straight\n // into the last session (or the sessions list if it's been deleted).\n // -------------------------------------------------------------------------\n\n // Narrow the deps to exactly what the effect reads. Depending on `config`\n // as a whole would re-fire if any future refactor made the resolved config\n // non-stable — and re-firing here re-runs `activateSession`, destroying and\n // rebuilding the agent in a loop (this was the original leak).\n useEffect(() => {\n if (!resumeProvider)\n return\n let cancelled = false\n void (async () => {\n // Resume-on-launch gate. Skips the `lastSessionId` lookup\n // entirely when the user has turned off `Start from last session`\n // — they then land on the sessions list (or a fresh session if\n // empty) regardless of what `state.json` remembers. We don't\n // clear `state.lastSessionId` on skip: flipping the setting\n // back on should restore the resume pointer without losing\n // wherever the user last was.\n if (resumeLastSessionOnLaunch && lastResumedSessionId) {\n const data = await store.load(lastResumedSessionId)\n if (cancelled)\n return\n // Project gate — only auto-resume sessions that belong to the\n // current project root (matching the same scope the sessions\n // list applies). Without this, a `lastSessionId` carried over\n // from another cwd would silently reopen here, while the list\n // filtered by the current project would render empty — a\n // confusing mismatch where the user lands inside a \"ghost\"\n // session they have no way to see in the sidebar.\n //\n // Opt-out via `Settings.showAllProjects`: when the user has\n // already chosen the cross-project view, resume follows the\n // same liberal rule the list uses.\n //\n // We deliberately do NOT clear `state.lastSessionId` on a\n // mismatch — when the user cd's back to the original project\n // the resume should re-engage. `activateSession` overwrites\n // `lastSessionId` with whatever they pick next anyway.\n const sessionMatchesProject = settings.showAllProjects\n || (data?.projectRoot != null && data.projectRoot === projectDir)\n if (data && sessionMatchesProject) {\n await activateSession(lastResumedSessionId, resumeProvider.key)\n bootTick(`resume:activate (sessionId=${lastResumedSessionId.slice(-8)})`)\n return\n }\n }\n const list = await refreshSessions()\n if (cancelled)\n return\n if (list.length === 0) {\n await activateSession(null, resumeProvider.key)\n bootTick('resume:fresh-session (empty list)')\n }\n else {\n setScreen('sessions')\n bootTick(`resume:sessions-list (${list.length})`)\n }\n })()\n return () => { cancelled = true }\n }, [activateSession, refreshSessions, resumeProvider, lastResumedSessionId, store, settings.showAllProjects, projectDir, resumeLastSessionOnLaunch])\n\n // -------------------------------------------------------------------------\n // Screen actions.\n // -------------------------------------------------------------------------\n\n // Available providers (with credentials configured) for the\n // cross-provider model picker. Seeded on mount + refreshed whenever\n // the user re-runs the auth wizard (via `onPickProvider`). Stored\n // as state so a credential added mid-session (e.g. setting a key\n // through the wizard while the chat is open) propagates to the\n // model picker on its next open.\n const [availableProviders, setAvailableProviders] = useState<readonly ProviderAuth[]>([])\n const refreshAvailableProviders = useCallback(() => {\n const detected = detectAuth(config.paths.userDir, providerRegistry)\n setAvailableProviders(detected.filter(p => p.available))\n }, [config.paths.userDir, providerRegistry])\n useEffect(() => {\n refreshAvailableProviders()\n }, [refreshAvailableProviders])\n\n const onPickProvider = useCallback(async (p: ProviderAuth) => {\n const next = makePicked(p)\n if (!next)\n return // provider isn't in the registry — guarded by AuthScreen, defensive here\n setPicked(next)\n stateStore.save({ ...stateStore.load(), lastProvider: p.key })\n // Re-detect — the wizard may have just added a credential for a\n // provider that wasn't authed before, and the model picker needs\n // to see it on its next open.\n refreshAvailableProviders()\n const list = await refreshSessions()\n if (list.length === 0)\n await activateSession(null, p.key)\n else\n setScreen('sessions')\n }, [refreshSessions, activateSession, makePicked, stateStore, refreshAvailableProviders])\n\n const onCreateSession = useCallback(async () => {\n if (picked)\n await activateSession(null, picked.provider.key)\n }, [picked, activateSession])\n\n const onSwitchSession = useCallback(async (id: string) => {\n if (picked)\n await activateSession(id, picked.provider.key)\n }, [picked, activateSession])\n\n const onOpenSessions = useCallback(async () => {\n await refreshSessions()\n setScreen('sessions')\n }, [refreshSessions])\n\n // `popupOpenRef` tracks the completion popup's visibility — set by\n // `ChatScreen`'s `onPopupOpenChange` callback. The esc handler short-\n // circuits when the popup is open so esc dismisses the popover instead\n // of aborting the run / cancelling pending approvals. Stored in a ref\n // (not state) so the abort callback stays referentially stable.\n const popupOpenRef = useRef(false)\n\n // Memoize the callback so `<ChatScreen onPopupOpenChange>` keeps a stable\n // identity across `setEvents` flushes. Without this, the inline arrow\n // recreated on every render lands in `PromptBlock`'s `useEffect` dep\n // array, re-firing the effect on every streaming delta and adding work\n // to each paint frame.\n const onPopupOpenChange = useCallback((open: boolean) => {\n popupOpenRef.current = open\n }, [])\n\n const onAbort = useCallback(() => {\n if (popupOpenRef.current)\n return // popup will consume esc via PromptBlock's onKeyDown\n // Flush every awaiter so the run can unwind cleanly; otherwise\n // `agent.abort()` only flips the run's signal and our gate /\n // interaction handlers would still hang in `await requestApproval(...)`\n // / `await requestInteraction(...)`. Esc == \"stop everything\", same rule\n // the approval queue follows; otherwise the drain loop would happily\n // start the next queued message right after the abort unwinds.\n cancelRunOnDenial('user aborted run')\n }, [cancelRunOnDenial])\n\n // Plan rejection aborts the run after the response is forwarded — the\n // model still records the `{ decision: 'reject', … }` tool_result, then\n // the loop unwinds. `revise` and `approve` continue to flow back unchanged.\n const onInteractionResolve = useCallback((response: InteractionResponse) => {\n interactions.resolveHead(response)\n if (response.kind === 'plan' && response.decision === 'reject')\n cancelRunOnDenial('user rejected a plan')\n }, [interactions, cancelRunOnDenial])\n\n // -----------------------------------------------------------------------\n // Message-queue navigation\n //\n // Selection state lives outside the queue box because the keyboard\n // shortcuts (`ctrl+up` enter, `ctrl+return` push, `delete` drop — all\n // bound through `keybindings.json` so a user can swap them) ride the\n // global `useKeyboard` listener and gate other shortcuts (esc abort)\n // on the selection being null. Centralizing it here keeps the\n // ChatScreen / QueuedMessagesBlock as render-only.\n // -----------------------------------------------------------------------\n\n const enterQueueSelection = useCallback(() => {\n if (messageQueueRef.current.length === 0)\n return\n // Land on the most-recently-added entry (the bottom of the stacked\n // list) — that's the one the user has freshest mental contact with,\n // and `↑` reads naturally as \"step up from the prompt\".\n setQueueSelectionIndex(messageQueueRef.current.length - 1)\n }, [])\n\n /**\n * Try to enter queue selection on an empty prompt buffer. Wired into\n * `PromptBlock`'s onKeyDown for `↑` at row 0 so the user moves\n * naturally from \"typing nothing\" into the queue box without needing\n * a dedicated shortcut. Returns `true` when selection was entered so\n * the caller can short-circuit history cycling on the same keypress.\n *\n * Falls back to the regular history-cycle path on:\n * - non-empty buffer (the user might be re-editing a draft and `↑`\n * should still pull a past prompt down for them to modify), or\n * - empty queue (nothing to step into).\n */\n const enterQueueSelectionFromEmptyPrompt = useCallback((): boolean => {\n if (messageQueueRef.current.length === 0)\n return false\n setQueueSelectionIndex(messageQueueRef.current.length - 1)\n return true\n }, [])\n\n const exitQueueSelection = useCallback(() => {\n setQueueSelectionIndex(null)\n }, [])\n\n /**\n * Move the queue cursor by `delta`. `↑` stops at the first entry\n * (clamps); `↓` past the last entry exits back to the prompt — the\n * mirror of \"↑ from the empty prompt enters the queue at the last\n * entry\", so navigation feels continuous across the queue ↔ prompt\n * boundary.\n */\n const moveQueueSelection = useCallback((delta: -1 | 1) => {\n setQueueSelectionIndex((prev) => {\n if (prev == null)\n return prev\n const len = messageQueueRef.current.length\n if (len === 0)\n return null\n const next = prev + delta\n if (next < 0)\n return 0\n if (next >= len)\n return null // step off the bottom → back to the prompt\n return next\n })\n }, [])\n\n const dropSelectedQueuedMessage = useCallback(() => {\n const idx = queueSelectionIndexRef.current\n if (idx == null)\n return\n const queue = messageQueueRef.current\n if (queue.length === 0) {\n setQueueSelectionIndex(null)\n return\n }\n const dropIdx = Math.min(idx, queue.length - 1)\n messageQueueRef.current = queue.filter((_, i) => i !== dropIdx)\n setMessageQueue(messageQueueRef.current.slice())\n // Empty queue → bail back to the prompt. Otherwise stay on the\n // entry that slid up into the dropped slot (or the new last entry\n // when we just removed the tail).\n if (messageQueueRef.current.length === 0)\n setQueueSelectionIndex(null)\n else\n setQueueSelectionIndex(Math.min(dropIdx, messageQueueRef.current.length - 1))\n }, [])\n\n /**\n * Push the selected queued message onto the live run's steering queue —\n * `agent.steer()` delivers it between tool calls in the agent loop, so\n * the model sees it as the next user input once the current turn\n * completes. Skill chips ride the same `activateSkill` path the regular\n * submit uses; the message text itself is what reaches the model.\n *\n * Best-effort: when no run is in flight (rare — the queue is only\n * populated while running) we silently exit selection. Adding the\n * message back to the queue on failure would be more confusing than\n * useful.\n */\n const pushSelectedQueuedMessage = useCallback(() => {\n const agent = agentRef.current\n if (!agent)\n return\n const idx = queueSelectionIndexRef.current\n if (idx == null)\n return\n const queue = messageQueueRef.current\n if (queue.length === 0) {\n setQueueSelectionIndex(null)\n return\n }\n const pushIdx = Math.min(idx, queue.length - 1)\n const entry = queue[pushIdx]\n messageQueueRef.current = queue.filter((_, i) => i !== pushIdx)\n setMessageQueue(messageQueueRef.current.slice())\n // Activate referenced skills BEFORE steering so the catalog has\n // the skill body in the system prompt by the time the model gets\n // to read the steered message. Best-effort — same `debugLog`\n // path as the regular submit; failures don't block the steer.\n const skillNames = uniqueSkillNamesFromReferences(entry.references)\n for (const name of skillNames) {\n agent.activateSkill(name).catch(err => debugLog(`activateSkill(\"${name}\")`, err))\n }\n agent.steer(entry.prompt)\n if (messageQueueRef.current.length === 0)\n setQueueSelectionIndex(null)\n else\n setQueueSelectionIndex(Math.min(pushIdx, messageQueueRef.current.length - 1))\n }, [])\n\n /**\n * Pick a `{ providerKey, modelId }` tuple from the cross-provider\n * model picker. Two branches:\n *\n * - **Same provider, different model** — update `picked.model` +\n * remember it in `lastModelByProvider`. The active agent keeps\n * running; `agent.run({ model })` accepts the model per-call so\n * no session rebuild is needed.\n * - **Different provider** — swap the active `ProviderAuth`,\n * persist BOTH `lastProvider` + `lastModelByProvider`, then\n * re-activate the current session against the new provider's\n * factory. The session id (and conversation history) is\n * preserved; only the bound agent + provider instance change.\n *\n * Either branch re-resolves the reasoning effort: a non-reasoning\n * model loses its `effort`, a reasoning model gets the remembered\n * per-model value (falling back to a sensible default).\n */\n const onPickModel = useCallback(async (next: PickedModel) => {\n const nextProvider = availableProviders.find(p => p.key === next.providerKey)\n if (!nextProvider) {\n // Catalog was built from `availableProviders`, so a stale row\n // should be unreachable — guard anyway in case auth detection\n // races with the picker (provider revoked credentials between\n // catalog assembly and commit).\n debugLog('onPickModel: unknown provider key', next.providerKey)\n modal.close()\n return\n }\n const descriptor = providerRegistry[nextProvider.key]\n const prior = stateStore.load()\n const providerChanged = picked?.provider.key !== nextProvider.key\n stateStore.save({\n ...prior,\n ...(providerChanged ? { lastProvider: nextProvider.key } : {}),\n lastModelByProvider: { ...prior.lastModelByProvider, [nextProvider.key]: next.modelId },\n })\n const nextEffort = descriptor\n ? effortForModel(descriptor, next.modelId, prior.lastEffortByModel)\n : undefined\n const updated: Picked = nextEffort\n ? { provider: nextProvider, model: next.modelId, effort: nextEffort }\n : { provider: nextProvider, model: next.modelId }\n setPicked(updated)\n modal.close()\n // Re-activate the current session against the new provider so the\n // next agent.run is bound to the right factory. Skipped on\n // same-provider picks (per-run model swap is handled inside\n // `agent.run`) and when there's no active session (we'd have\n // nothing to re-bind anyway).\n if (providerChanged && currentSession && !busy && !pendingApproval)\n await activateSession(currentSession.id, nextProvider.key)\n }, [\n availableProviders,\n providerRegistry,\n stateStore,\n modal,\n picked,\n currentSession,\n busy,\n pendingApproval,\n activateSession,\n ])\n\n const onPickEffort = useCallback((effort: ThinkingLevel) => {\n setPicked((prev) => {\n if (!prev)\n return prev\n const prior = stateStore.load()\n stateStore.save({\n ...prior,\n lastEffortByModel: { ...prior.lastEffortByModel, [prev.model]: effort },\n })\n return { ...prev, effort }\n })\n modal.close()\n }, [modal, stateStore])\n\n // Reasoning support is a property of the active model — derived here so\n // the bottom-bar hint and the ctrl+n gate read from a single source.\n // Declared above the keyboard handler so its closure sees the value.\n const modelHasReasoning = useMemo(() => {\n if (!picked)\n return false\n const descriptor = providerRegistry[picked.provider.key]\n return !!descriptor && modelSupportsReasoning(descriptor, picked.model)\n }, [picked, providerRegistry])\n\n // Agent profile switch. The ref is written synchronously so a follow-up\n // `activateSession` call in the same tick rebuilds against the new\n // preset; the state update keeps the footer badge + picker re-render in\n // sync. When the agent change happens mid-session we re-activate the\n // active session, which destroys the old agent and creates a fresh one\n // bound to the new profile (`buildAgent` reads the ref).\n const onPickAgent = useCallback(async (id: string) => {\n const profile = agentRegistry[id]\n if (!profile)\n return // unknown id — picker already filters, this is a defensive guard\n pickedAgentRef.current = profile\n setPickedAgent(profile)\n stateStore.save({ ...stateStore.load(), lastAgent: id })\n modal.close()\n // Rebuild the active agent so the new system prompt + tool set take\n // effect on the *next* turn. Conversation history is preserved (we\n // load the same session id); only the live agent's bindings change.\n if (picked && currentSession && !busy)\n await activateSession(currentSession.id, picked.provider.key)\n }, [agentRegistry, picked, currentSession, busy, activateSession, stateStore, modal])\n\n // Shift+Tab — cycle to the next registered profile, wrapping. Insertion\n // order in `agentRegistry` is the cycle order (matches the picker), so\n // pressing the key twice in a 2-profile registry returns to the original\n // — the same toggle muscle memory other agentic TUIs train. Reads the\n // ref directly so the lookup always sees the latest profile even if\n // state hasn't flushed yet between rapid presses.\n const onCycleAgent = useCallback(async () => {\n const ids = Object.keys(agentRegistry)\n if (ids.length <= 1)\n return\n const currentIdx = ids.indexOf(pickedAgentRef.current.id)\n const nextId = ids[(currentIdx + 1) % ids.length]\n await onPickAgent(nextId)\n }, [agentRegistry, onPickAgent])\n\n // `events.length` is read through a ref so the callback's identity stays\n // stable across delta-by-delta state updates (events grow on every\n // streamed markdown token). Without the ref the callback rebinds every\n // delta, which cascades into `ChatScreen` → `PromptBlock` → the textarea's\n // `onKeyDown` rebinding, defeating any future memoization downstream.\n const eventsLengthRef = useRef(0)\n eventsLengthRef.current = events.length\n\n /**\n * Run a single prompt end-to-end. Echoes the user-prompt event to the\n * transcript synchronously (BEFORE awaiting compaction / skill\n * activation) so the message lands in the conversation at the moment\n * the drain loop decides to run it — that's the cue for the\n * \"queued → user message\" transition the queue box above the prompt\n * reflects visually.\n *\n * Intentionally does NOT toggle `busy` — the drain loop owns the\n * busy-state lifecycle so the title spinner stays mounted continuously\n * across back-to-back queued messages.\n */\n const runSingleMessage = useCallback(async (prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => {\n const agent = agentRef.current\n const session = sessionRef.current\n if (!agent || !session || !picked)\n return\n\n // Echo the prompt into the transcript right as we commit to running\n // it. Queued messages live in the queue box ABOVE the prompt (see\n // {@link messageQueue}); calling the echo here is what visually\n // \"pushes\" them out of that box and into the conversation.\n if (eventsLengthRef.current > 0)\n stream.appendImmediate({ kind: 'separator', text: '' })\n const refSpans = references\n .filter(r => r.start >= 0 && r.end > r.start)\n .map(r => ({ start: r.start, end: r.end, providerId: r.providerId }))\n const attachmentMeta = attachments.map(a => ({\n name: a.name,\n mediaType: a.mediaType,\n size: a.content.length,\n }))\n stream.appendImmediate({\n kind: 'user-prompt',\n text: prompt,\n ...(refSpans.length > 0 ? { refs: refSpans } : {}),\n ...(attachmentMeta.length > 0 ? { attachments: attachmentMeta } : {}),\n })\n\n // Wait for any background auto-compaction to land BEFORE we run.\n // The user's submit IS allowed during compaction (they can type\n // freely), but the resulting `agent.run()` has to start against\n // the post-compaction history — otherwise we'd race the\n // summary's `appendTurns` and produce a corrupt message order.\n // The catch is a no-op because the compaction's own error path\n // already surfaced a transcript event; we just don't want a\n // rejected compaction to kill the user's prompt.\n if (autoCompactInFlightRef.current)\n await autoCompactInFlightRef.current.catch(() => {})\n\n // Activate every skill referenced via slash-commands AND every skill\n // the user has previously pinned in this session, BEFORE the run.\n //\n // Why the pinned set: the framework's run-end deactivate-all wipes\n // the activation state at every run boundary (see `deactivateAllSkills`\n // in `src/agent.ts`). Without re-activating here, a user typing\n // `/read-only` once would see the skill take effect for that one run\n // and then silently lose its `allowed-tools` enforcement on the next\n // prompt — exactly the \"skill activated but bash ran\" bug. The pinned\n // set survives `'run-end'` deactivates (see the `skills:deactivate`\n // hook in `buildAgent`) and is drained on `'model'` / `'explicit'`\n // / `'reset'` deactivates so a deliberate \"release this pin\" call\n // sticks.\n //\n // Idempotent — `activateSkill` is safe on already-active skills,\n // auto-resolves the catalog on first call, and de-dups via the\n // union below.\n const newRefs = uniqueSkillNamesFromReferences(references)\n const skillNames = Array.from(new Set([...activeSkillNamesRef.current, ...newRefs]))\n for (const name of skillNames) {\n try {\n await agent.activateSkill(name)\n }\n catch (err) {\n debugLog(`activateSkill(\"${name}\")`, err)\n }\n }\n\n try {\n // Build a multimodal PromptPart[] iff there are attachments; the\n // plain-string fast path stays for the typing-only case so the\n // existing single-text-part wire shape is preserved bit-for-bit.\n const runPrompt: string | PromptPart[] = attachments.length === 0\n ? prompt\n : [\n ...(prompt.length > 0 ? [{ type: 'text' as const, text: prompt }] : []),\n ...attachments.map<PromptPart>((att) => {\n if (att.mediaType.startsWith('image/')) {\n return {\n type: 'image',\n mediaType: att.mediaType,\n data: att.content.toString('base64'),\n name: att.name,\n }\n }\n // Treat text/* as inline text documents; everything else\n // gets base64-encoded as an opaque document blob.\n if (att.mediaType.startsWith('text/')) {\n return {\n type: 'document',\n mediaType: att.mediaType,\n data: att.content.toString('utf-8'),\n encoding: 'text',\n name: att.name,\n }\n }\n return {\n type: 'document',\n mediaType: att.mediaType,\n data: att.content.toString('base64'),\n encoding: 'base64',\n name: att.name,\n }\n }),\n ]\n await agent.run({\n model: picked.model,\n prompt: runPrompt,\n ...(picked.effort ? { thinking: picked.effort } : {}),\n })\n await session.save().catch(err => debugLog('session.save failed', err))\n setCurrentSession(prev => prev\n ? {\n ...prev,\n title: deriveSessionTitle(session.turns, session.metadata),\n turnCount: session.turns.length,\n userMessageCount: session.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n runCount: session.runs.length,\n updatedAt: Date.now(),\n }\n : prev)\n // Post-run auto-compaction check. Fire-and-forget — the launcher\n // runs in the background, the prompt area stays interactive, and\n // the next `runSingleMessage` invocation awaits the resulting\n // promise via `autoCompactInFlightRef`. We don't auto-continue\n // the conversation; the user controls the next turn.\n triggerAutoCompactRef.current(session.id)\n }\n catch (err) {\n stream.appendImmediate({ kind: 'error', text: errorMessage(err) })\n }\n finally {\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n }\n }, [picked, stream])\n\n const onSubmitPrompt = useCallback((prompt: string, references: readonly CompletionReference<unknown>[], attachments: readonly Attachment[]) => {\n // Allow submit with empty text when attachments are present\n // (e.g. drag-an-image-in-and-press-enter); the typed-only path\n // still requires non-blank text.\n if (!prompt.trim() && attachments.length === 0)\n return\n const agent = agentRef.current\n const session = sessionRef.current\n if (!agent || !session || !picked)\n return\n\n // Already running — park the prompt in the queue box. The active\n // drain loop picks it up after the current message lands; the box\n // shows the user a stacked preview of \"what's waiting\".\n if (runningRef.current) {\n messageQueueRef.current = [...messageQueueRef.current, { prompt, references, attachments }]\n setMessageQueue(messageQueueRef.current.slice())\n return\n }\n\n // Idle — run this message immediately (skipping the queue box\n // entirely so the user never sees their freshly-typed prompt\n // appear \"queued\" for a frame before running), then drain anything\n // submitted while this run is in flight.\n runningRef.current = true\n void (async () => {\n setBusy(true)\n try {\n await runSingleMessage(prompt, references, attachments)\n while (messageQueueRef.current.length > 0) {\n const next = messageQueueRef.current[0]\n messageQueueRef.current = messageQueueRef.current.slice(1)\n setMessageQueue(messageQueueRef.current.slice())\n // Keep the user's queue cursor glued to the same MESSAGE while\n // the drain shrinks the list under their feet. The head we\n // just popped is now in the transcript, so:\n // - selection on the popped row (index 0) → exit selection\n // (the row no longer exists)\n // - selection further down → shift left by one so it still\n // points at the same entry now sitting one slot higher\n // - empty queue → exit selection (the box is about to\n // unmount anyway)\n setQueueSelectionIndex((prev) => {\n if (prev == null)\n return prev\n if (messageQueueRef.current.length === 0)\n return null\n if (prev <= 0)\n return null\n return prev - 1\n })\n await runSingleMessage(next.prompt, next.references, next.attachments)\n }\n }\n finally {\n setBusy(false)\n runningRef.current = false\n }\n })()\n }, [picked, runSingleMessage])\n\n // -------------------------------------------------------------------------\n // Top-level keyboard. Each screen handles its own primary action; this only\n // routes esc + the modal shortcuts.\n //\n // The callback is recreated on every render, but OpenTUI's `useKeyboard`\n // wraps it with `useEffectEvent` internally — registration only happens once\n // and the handler always sees the latest closure. No leak.\n // -------------------------------------------------------------------------\n\n // `pendingApproval` is derived at the top of `AppShell` (just below\n // `queue`) so every callback that gates on \"is the user mid-approval?\"\n // (model picker swap, ctrl+x details, the keyboard handler itself,\n // the footer hints) can depend on it without temporal-deadzone\n // contortions. The reference is the same value the keyboard handler\n // captures below.\n\n // Settings → \"Re-configure providers\" jumps back to the auth screen. We\n // capture this as a callback so the modal closes cleanly before the screen\n // transition (avoiding a brief flash of the modal over the new screen).\n //\n // Switching providers mid-stream would have the old agent keep firing\n // stream/tool hooks into the shared buffer after the new session mounts,\n // leaking deltas + approvals across sessions. We gate the wiring itself\n // (returning `undefined` while busy/pending) so the Settings modal omits\n // the row at build time — no silent no-op, no dead row in the picker.\n // The modal re-renders when `busy` flips and the row re-appears.\n const onReauth = useMemo(() => {\n if (busy || pendingApproval)\n return undefined\n return () => {\n modal.close()\n setScreen('auth')\n }\n }, [modal, busy, pendingApproval])\n\n // Per-server abort controllers for in-flight OAuth flows. Modal-driven\n // cancel (`esc` on an `authorizing` row) hits .abort() here; the\n // loginMcpServer flow rejects and the agent fires `mcp:auth:error`\n // which folds back into the auth state via the reducer.\n const mcpLoginAbortsRef = useRef(new Map<string, AbortController>())\n\n // Refs for the rebuild-on-login path. The rebuild closure runs much later\n // than its capture so reads must go through refs to see the latest values.\n // `pickedRef` is reused from upthread; only `buildAgentRef` is new here.\n const buildAgentRef = useRef(buildAgent)\n buildAgentRef.current = buildAgent\n\n /**\n * Rebuild the agent for the active session — used post-login so the\n * newly-authenticated MCP server actually connects. The current agent's\n * MCP connection is closed (which releases any other server connections\n * too); the fresh agent re-bootstraps lazily on its next run / warmup\n * with the now-populated credential store.\n *\n * Skipped silently when there's no active session / picked provider —\n * an early-startup login would race the agent's existence otherwise.\n * The bootstrap event handlers below pick up the new connection's\n * status without needing a manual refresh from this caller.\n */\n const rebuildActiveAgent = useCallback(async () => {\n const session = sessionRef.current\n const p = pickedRef.current\n if (!session || !p)\n return\n const fresh = buildAgentRef.current(session, p.provider.key)\n const stale = agentRef.current\n agentRef.current = fresh\n // Tear down the previous agent only after the new one is wired so\n // any in-flight UI state pointed at agentRef sees a valid agent at\n // every microtask. MCP close errors are debug-only — a stale\n // connection that won't close cleanly should not block the swap.\n if (stale)\n await stale.destroy().catch(err => debugLog('rebuildActiveAgent: destroy failed', err))\n }, [])\n\n const onLoginMcp = useCallback(async (name: string) => {\n const entry = mcpsCatalogRef.current.find(d => d.config.name === name)\n if (!entry)\n return\n\n // Replace any prior in-flight login for the same server (rare: the\n // modal disables `l` while authorizing, but a programmatic re-entry\n // shouldn't leak the older controller).\n mcpLoginAbortsRef.current.get(name)?.abort()\n const ac = new AbortController()\n mcpLoginAbortsRef.current.set(name, ac)\n\n dispatchAuth({ type: 'login:start', name })\n try {\n await loginMcpServer(entry.config, {\n store: mcpCredentialStore,\n signal: ac.signal,\n // Pipe through the live agent's hooks so `mcp:auth:url` /\n // `mcp:auth:success` / `mcp:auth:error` flow through the same\n // listeners the bootstrap path uses — single source of truth for\n // status state. Undefined-safe: when there's no agent yet, the\n // flow still completes via direct dispatch above + reducer below.\n hooks: agentRef.current?.hooks,\n // Auto-open the user's browser as soon as the SDK builds the\n // authorization URL. Best-effort: if no GUI is available\n // (`open`/`xdg-open` missing or restricted by the sandbox), the\n // user can still click / copy the URL from the modal's\n // authorizing panel — the loopback callback server is listening\n // either way.\n onAuthorizationUrl: url => tryOpenBrowser(url.toString()),\n })\n // Tokens persisted. Rebuild the agent so the next run picks them up.\n // Don't await — agent destroy can take a beat (MCP close) and the\n // user's already done with the modal at this point.\n void rebuildActiveAgent()\n }\n catch (err) {\n // `mcp:auth:error` hook already dispatched the status on the agent\n // bus. If no agent was wired, fall back to a direct dispatch.\n if (!agentRef.current)\n dispatchAuth({ type: 'auth-error', name, error: errorMessage(err) })\n }\n finally {\n mcpLoginAbortsRef.current.delete(name)\n }\n }, [dispatchAuth, mcpCredentialStore, rebuildActiveAgent])\n\n const onLogoutMcp = useCallback((name: string) => {\n mcpCredentialStore.delete(name)\n // For OAuth-tagged servers, drop back to `needs-auth` rather than `idle`.\n // `idle` is \"we know nothing about this server's auth needs\" — but we\n // DO know: the config says `auth: 'oauth'`, the store is empty,\n // therefore the next bootstrap will need login. Without this branch\n // the `l` (login) action would disappear until the user restarted the\n // CLI and the proactive seed re-ran on discovery.\n const entry = mcpsCatalogRef.current.find(d => d.config.name === name)\n dispatchAuth(\n entry?.config.auth === 'oauth'\n ? { type: 'auth-required', name, reason: 'no-tokens' }\n : { type: 'logout', name },\n )\n // Rebuild so the next agent bootstrap re-emits `mcp:auth:required`\n // (the reducer is now in sync, but the AGENT's MCP connection still\n // holds Linear authenticated until we rebuild).\n void rebuildActiveAgent()\n }, [dispatchAuth, mcpCredentialStore, rebuildActiveAgent])\n\n const onCancelLoginMcp = useCallback((name: string) => {\n mcpLoginAbortsRef.current.get(name)?.abort()\n }, [])\n\n // Settings → Open keybindings file. Creates the file with defaults\n // when missing (per `ensureKeybindingsFile`'s idempotent contract),\n // then launches `$EDITOR` if set, falling back to the platform's\n // default opener (`open` on macOS, `xdg-open` on Linux, `start` on\n // Windows). Closes the modal first so the editor's window doesn't\n // visually launch behind the settings overlay.\n //\n // Changes to the file take effect on the NEXT TUI launch — we read\n // keybindings during `resolveConfig`, before AppShell mounts. We\n // intentionally don't hot-reload: the global keyboard handler would\n // have to teardown + re-register, which complicates the in-flight\n // run gates. Restart is a small price and matches how the same\n // shortcuts elsewhere in the app (`Start from last session`, theme\n // changes are hot, this one isn't) are documented.\n const onOpenKeybindingsFile = useCallback(() => {\n modal.close()\n void (async () => {\n try {\n const path = ensureKeybindingsFile(config.paths.userDir)\n await launchEditor(path)\n }\n catch (err) {\n debugLog('open keybindings file failed', err)\n }\n })()\n }, [modal, config.paths.userDir])\n\n // `onRefreshSkills` / `onRefreshFiles` / `onRefreshMcps` come from\n // <DiscoveryShell> via `useDiscovery()` above. They're listed here\n // in passing so future maintainers don't try to redefine them in\n // AppShell — they're load-bearing because the modal layer reads\n // them through the same context that propagates fresh catalogs.\n\n // Drives both the `ctrl+a` shortcut wiring and the hint visibility — a\n // single-profile registry hides the affordance entirely so it doesn't\n // suggest a picker that has no choices to offer. Defined before\n // `useKeyboard` so the closure reads a stable, hoisted-friendly binding.\n const hasMultipleAgents = useMemo(\n () => Object.keys(agentRegistry).length > 1,\n [agentRegistry],\n )\n\n // -------------------------------------------------------------------------\n // Select-turn mode.\n //\n // Source of truth: every `StreamEvent` carries the `turnId` of its source\n // SessionTurn (see `eventsFromTurns` + the live hooks in `buildAgent`).\n // `selectableTurnIds` derives the navigation index — deduplicated, in\n // render order, parent-conversation only (subagent turns are nested\n // execution and would highlight nothing under the default\n // `hideSubagentOutput: true`).\n // -------------------------------------------------------------------------\n\n // `settings` is threaded in so the navigation index drops turns whose\n // every event is filtered out by user toggles (e.g. an assistant turn\n // that's pure `tool` events when `showToolCalls: false`). Without this\n // the cursor can land on a row that paints nothing and appears to\n // vanish. `selectableTurnIds` also coalesces result-only user turns\n // into their owning assistant turn — see `turnSelectionOwnership`.\n const turnIds = useMemo(() => selectableTurnIds(events, settings), [events, settings])\n\n /** Drop the selection if its turn disappeared (session swap, history reset). */\n useEffect(() => {\n if (selectedTurnId && !turnIds.includes(selectedTurnId))\n setSelectedTurnId(null)\n }, [selectedTurnId, turnIds])\n\n const inSelectMode = selectedTurnId !== null\n\n const enterSelectMode = useCallback(() => {\n if (turnIds.length === 0)\n return // no turns to select — silently no-op\n setSelectedTurnId(turnIds[turnIds.length - 1])\n }, [turnIds])\n\n const cycleSelectedTurn = useCallback((direction: -1 | 1) => {\n setSelectedTurnId((prev) => {\n if (!prev || turnIds.length === 0)\n return prev\n const idx = turnIds.indexOf(prev)\n if (idx === -1) // stale id — restart from the most recent turn\n return turnIds[turnIds.length - 1]\n // Wrap at the edges so ↑ on the oldest turn lands on the newest\n // (and ↓ on the newest comes back to the oldest). Matches the\n // wrap behavior of every list picker in the app.\n const len = turnIds.length\n const nextIdx = ((idx + direction) % len + len) % len\n return turnIds[nextIdx]\n })\n }, [turnIds])\n\n // Fork — create a new session whose turns are a protocol-clean prefix\n // ending at `turnId`, then switch to it. The current session is left\n // intact on disk so the user can navigate back to it from the sessions\n // screen. We thread through `activateSession` so the agent rebuild +\n // event replay + state persist all happen via the canonical path.\n const onForkTurn = useCallback(async (turnId: string) => {\n const source = sessionRef.current\n if (!source || !picked)\n return\n const slice = truncateTurnsAt(source.turns, turnId)\n if (!slice || slice.length === 0)\n return\n // Carry forward the parent's run records that are referenced by the\n // kept turns. Without this, the fork persists with `runs: []` even\n // though its turns still reference `run_1..run_N` — the modal's\n // run / token / cost stats would read as zero, and the next\n // `agent.run` on the fork would mint `run_1` again, colliding with\n // existing turn.runId references. Shallow-clone each entry so the\n // fork's mutations don't bleed back into the parent's in-memory\n // session.\n const referencedRunIds = new Set<string>()\n for (const t of slice) {\n if (t.runId)\n referencedRunIds.add(t.runId)\n }\n const inheritedRuns = source.runs\n .filter(r => referencedRunIds.has(r.id))\n .map(r => ({ ...r }))\n // Build the new session in-memory with the truncated turns + inherited\n // runs, persist via `save()`, then activate via id so the agent loop\n // sees the full SessionData on reload. The fork inherits the parent's\n // `projectRoot` rather than re-resolving from `cwd` — a fork belongs\n // to whatever project its parent did, even if the user is forking\n // from a different working directory than where the parent was made.\n const fork = await createSession({\n store,\n ...(source.projectRoot ? { projectRoot: source.projectRoot } : {}),\n })\n fork.setTurns(slice)\n fork.setRuns(inheritedRuns)\n try {\n await fork.save()\n }\n catch (err) {\n debugLog('fork: save failed', err)\n return\n }\n setSelectedTurnId(null) // exit select mode before swapping sessions\n await activateSession(fork.id, picked.provider.key)\n }, [picked, store, activateSession])\n\n // Delete — mutate the current session in place, stripping any orphan\n // tool blocks left by the removal. Persists via `session.save()` and\n // re-renders the transcript from the new turn list. Stays in the same\n // session (and in select mode, on a sensible neighbor if possible).\n const onDeleteTurn = useCallback(async (turnId: string) => {\n const session = sessionRef.current\n if (!session)\n return\n const nextTurns = deleteTurnSafely(session.turns, turnId)\n if (!nextTurns)\n return\n session.setTurns(nextTurns)\n try {\n await session.save()\n }\n catch (err) {\n debugLog('delete: save failed', err)\n return\n }\n // Re-derive events from the mutated turn list. `lastContextSize`\n // moves to whatever the new most-recent assistant turn reports.\n setEvents(eventsFromTurns(session.turns, session.runs))\n const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs)\n setLastInputTokens(replayedTokens)\n lastInputTokensRef.current = replayedTokens\n // Branching forks the conversation — any prior compaction baseline\n // belongs to the pre-branch history. Invalidate it.\n lastCompactedInputTokensRef.current = undefined\n setSessionCost(sumRunCosts(session.runs))\n setCurrentSession(prev => prev\n ? {\n ...prev,\n turnCount: session.turns.length,\n userMessageCount: session.turns.reduce((n, t) => t.role === 'user' ? n + 1 : n, 0),\n updatedAt: Date.now(),\n }\n : prev)\n // If the active selection was the deleted turn (or one of the\n // collateral-stripped ones), drop it. The standalone effect on\n // `turnIds` would catch this too, but doing it eagerly avoids one\n // frame of \"selection points at nothing\".\n setSelectedTurnId((prev) => {\n if (!prev)\n return prev\n return nextTurns.some(t => t.id === prev) ? prev : null\n })\n }, [])\n\n // Edit — replace the text blocks of a turn with new content. Non-text\n // blocks (tool_call, tool_result, thinking, etc.) are preserved as-is.\n const onEditTurn = useCallback(async (turnId: string, newText: string) => {\n const session = sessionRef.current\n if (!session)\n return\n const turn = session.turns.find(t => t.id === turnId)\n if (!turn)\n return\n const hasTextBlock = turn.content.some(b => b.type === 'text')\n if (!hasTextBlock)\n return\n // Replace all text blocks with a single one containing the edited text.\n // Preserve non-text blocks (tool calls, tool results, thinking, etc.)\n // in their original positions.\n const nonTextBlocks = turn.content.filter(b => b.type !== 'text')\n const firstTextIdx = turn.content.findIndex(b => b.type === 'text')\n const updatedContent: SessionContentBlock[] = [...nonTextBlocks]\n if (newText.trim()) {\n updatedContent.splice(firstTextIdx >= 0 ? Math.min(firstTextIdx, updatedContent.length) : 0, 0, { type: 'text', text: newText })\n }\n turn.content = updatedContent\n session.setTurns([...session.turns])\n try {\n await session.save()\n }\n catch (err) {\n debugLog('edit: save failed', err)\n return\n }\n setEvents(eventsFromTurns(session.turns, session.runs))\n const replayedTokens = lastContextSizeFromTurns(session.turns, session.runs)\n setLastInputTokens(replayedTokens)\n lastInputTokensRef.current = replayedTokens\n // Editing rewinds the turn list — an earlier compact-summary turn may\n // have been removed entirely. Drop the baseline so the next trigger\n // decision uses the absolute-threshold path.\n lastCompactedInputTokensRef.current = undefined\n setCurrentSession(prev => prev\n ? { ...prev, updatedAt: Date.now() }\n : prev)\n }, [])\n\n // -------------------------------------------------------------------------\n // Session details — opens a metadata + actions modal for a session. Reachable\n // via ctrl+x from either screen:\n // - chat: targets the active session (current run).\n // - sessions list: targets the focused row (state-driven below).\n // -------------------------------------------------------------------------\n\n /**\n * Identity of the session row the user has focused on the sessions\n * screen — single source of truth. `SessionsScreen` is rendered fully\n * controlled against it: the select's `selectedIndex` is derived from\n * this id, so when the underlying list reorders (e.g. after `generate\n * title` updates `updatedAt`, SQLite returns rows in a new order) the\n * cursor follows the IDENTITY of the row the user was on, not the\n * numerical slot it used to occupy. `ctrl+x` reads it directly.\n *\n * `null` means the cursor is on `+ new session` (or the list is\n * empty); the keyboard handler skips opening the details modal in\n * that case.\n */\n const [focusedSessionId, setFocusedSessionId] = useState<string | null>(null)\n\n const onDeleteSession = useCallback(async (id: string) => {\n try {\n await store.delete(id)\n }\n catch (err) {\n debugLog('delete session failed', err)\n return\n }\n // Cleanup the session's persisted-tool-result blobs (best-effort).\n // The helper swallows missing-dir errors so a session that never\n // persisted anything is a clean no-op. Runs AFTER the store delete\n // so a partial failure here doesn't leave the DB pointing at gone\n // blobs without the user knowing the session was nuked.\n void cleanupPersistedSession(resolvePersistDir({ userDir: cacheDir, sessionId: id }))\n // Background-task log files live under `<userDir>/<sessionId>/tasks/`\n // — same cleanup pattern. Best-effort: a session with no background\n // tasks is a clean no-op.\n void cleanupPersistedSession(resolveTasksDir({ userDir: cacheDir, sessionId: id }))\n const wasCurrent = id === currentSession?.id\n if (wasCurrent) {\n // Drop the active agent + bound session BEFORE we touch the\n // sessions list — the chat screen reads `currentSession` and\n // `sessionRef`, both of which must be cleared in lock-step or we\n // get a half-empty transcript pointing at a row that no longer\n // exists. Order: teardown (closes agent + drains queue) →\n // clear state (forces remount of `ChatScreen` / route to\n // `sessions` below).\n await teardown()\n setCurrentSession(null)\n setEvents([])\n setSelectedTurnId(null)\n stateStore.save({ ...stateStore.load(), lastSessionId: undefined })\n }\n await refreshSessions()\n if (wasCurrent) {\n // Switch the user to the sessions list so they pick the next\n // session intentionally. Falling back to chat would leave them\n // staring at the empty-transcript placeholder. When the list is\n // empty the sessions screen renders its own empty-state with the\n // `+ new` row already focused — no separate path needed.\n setScreen('sessions')\n }\n }, [store, currentSession, teardown, refreshSessions, stateStore, dataDir, cacheDir])\n\n // Generate a title for a session via the active provider + picked\n // model, persist it to `metadata.title`, and update any live UI\n // (chat title bar, sessions list) so the change is visible without a\n // session swap. Returns the generated title so the modal can flash\n // it in its header.\n //\n // The call uses the SAME provider/model the user is currently picked\n // on — title generation feels like a quick \"ask the model\" action,\n // and routing it elsewhere would surprise users billed per provider.\n const onGenerateTitle = useCallback(async (sessionId: string, signal: AbortSignal): Promise<string> => {\n if (!picked)\n throw new Error('No provider picked — open the chat screen first.')\n const descriptor = providerRegistry[picked.provider.key]\n if (!descriptor)\n throw new Error(`Provider \"${picked.provider.key}\" is not registered.`)\n // Prefer the live session when generating for the active one (saves\n // a round-trip to disk + we already have the freshest turns there).\n // Fall back to the store for inactive sessions. Reuse `data` for the\n // persist step so we don't hit the store twice for one operation.\n let turns: SessionTurn[] | undefined\n let metadataRecord: Record<string, unknown> | undefined\n let liveSession: Session | null = null\n let loadedData: Awaited<ReturnType<typeof store.load>> = null\n if (sessionId === sessionRef.current?.id) {\n liveSession = sessionRef.current\n turns = sessionRef.current.turns\n metadataRecord = sessionRef.current.metadata\n }\n else {\n loadedData = await store.load(sessionId)\n if (!loadedData)\n throw new Error('Session not found.')\n turns = loadedData.turns\n metadataRecord = loadedData.metadata\n }\n const title = await generateSessionTitle({\n provider: descriptor.factory(),\n model: picked.model,\n turns,\n signal,\n })\n // Persist via the live session when active (the wrapper hooks fire\n // `session:meta` for downstream observers); otherwise write through\n // the store directly with the loaded SessionData.\n if (liveSession) {\n liveSession.setMeta('title', title)\n await liveSession.save().catch(err => debugLog('generate-title: save failed', err))\n }\n else {\n // `loadedData` is populated above on this branch — no second load.\n if (!loadedData)\n throw new Error('Session disappeared mid-generation.')\n const nextMeta: Record<string, unknown> = { ...(metadataRecord ?? {}), title }\n await store.save({ ...loadedData, metadata: nextMeta, updatedAt: Date.now() })\n .catch(err => debugLog('generate-title: store.save failed', err))\n }\n // Reflect the new title in the chat header when it's the active\n // session.\n setCurrentSession(prev => prev && prev.id === sessionId\n ? { ...prev, title, updatedAt: Date.now() }\n : prev)\n // Refresh the sessions list so the new title shows immediately when\n // the modal was opened from there (the list is rendered live behind\n // the modal). Skipping this would leave the user looking at a\n // stale row right after committing the rename — the UX equivalent\n // of \"did anything happen?\". `refreshSessions` is cheap (one store\n // list + per-row metadata derive) so we don't gate it.\n await refreshSessions().catch(err => debugLog('generate-title: refreshSessions failed', err))\n return title\n }, [picked, providerRegistry, store, refreshSessions])\n\n // Session compaction — drives `compactConversation` against the active\n // provider + picked model, then appends a synthetic `compact-summary`\n // marker turn so the wire-level loop elides everything before it. The\n // model only sees the summary from this point forward; the user can\n // still scroll back to see the original turns (transcript renders\n // them unchanged, and select-turn mode still walks them).\n //\n // Prefers the live session for the active one (same rationale as\n // `onGenerateTitle` — live turns are freshest); falls back to the\n // store for inactive sessions. After compaction completes we refresh\n // the chat-side derived state so the transcript repaints with the\n // boundary card immediately.\n const onCompactSession = useCallback(async (\n sessionId: string,\n signal: AbortSignal,\n ): Promise<SessionCompactResult> => {\n if (!picked)\n throw new Error('No provider picked — open the chat screen first.')\n const descriptor = providerRegistry[picked.provider.key]\n if (!descriptor)\n throw new Error(`Provider \"${picked.provider.key}\" is not registered.`)\n const isLive = sessionId === sessionRef.current?.id\n const liveSession: Session | null = isLive ? sessionRef.current : null\n const loaded = liveSession ? null : await store.load(sessionId)\n const turns = liveSession ? liveSession.turns : loaded?.turns\n if (!turns || turns.length === 0)\n throw new Error('Session has no turns to compact.')\n\n const result = await compactConversation({\n provider: descriptor.factory(),\n model: picked.model,\n turns,\n scope: 'tail',\n signal,\n })\n const summaryTurn = summaryToTurn({\n summary: result.summary,\n replacesTurnIds: result.summarizedTurnIds,\n model: result.model,\n usage: result.usage,\n })\n\n // Post-compact restoration — re-inject the recently-read files and\n // active skills as synthetic tool_call/tool_result pairs after the\n // marker so the model keeps direct access to its working set.\n //\n // Only runs for the active (live) session: we need the agent's\n // ExecutionContext + handle to fetch file contents, and we need\n // `agent.activeSkills` for the skill list. For an inactive session\n // opened from the picker, restoration is skipped — re-activating the\n // session will trigger the agent to re-Read whatever it needs on the\n // next turn. The \"restored 0\" count surfaces in the banner so the\n // user knows.\n const agent = liveSession ? agentRef.current : null\n // Match the shape `buildPostCompactAttachments` returns so the\n // assignment below carries every field forward; the `estimatedTokens`\n // piece feeds the post-compact effective-size estimate the chat layer\n // surfaces in the footer and the success banner.\n let restoration = {\n turns: [] as readonly SessionTurn[],\n restoredFiles: 0,\n restoredSkills: 0,\n estimatedTokens: 0,\n }\n if (agent && liveSession && agent.handle) {\n const recentFiles = selectFilesFromSession(liveSession, agent.handle.cwd)\n try {\n const built = await buildPostCompactAttachments({\n recentFiles,\n activeSkills: agent.activeSkills,\n execution: agent.execution,\n handle: agent.handle,\n ...(summaryTurn.runId ? { runId: summaryTurn.runId } : {}),\n })\n restoration = built\n }\n catch (err) {\n // Restoration is best-effort — never let a failure here block\n // the compaction itself. The summary still landed; the user\n // just doesn't get the post-compact attachments this round.\n debugLog('compact: post-compact restoration failed', err)\n }\n }\n\n // Estimate the post-compact wire-level context size — the model's\n // view of the conversation on its NEXT turn. Used both as the\n // success banner's \"→ N effective\" line and as the new value for\n // `lastInputTokens` so the chat footer's `ctx` indicator drops\n // immediately instead of carrying the pre-compact count until the\n // next real turn's `usage` lands.\n //\n // Composition:\n // - `result.usage.output` — the LLM-counted token size of the\n // summary itself (most accurate piece, comes from the provider).\n // - `restoration.estimatedTokens` — byte-based estimate for the\n // re-injected file/skill content. Less precise but cheap to\n // compute and within ±20% of provider counts for text content.\n //\n // Under-estimates by the size of the system prompt + preserved\n // tail turns; both are small relative to the rest, and the next\n // real `turn:after` corrects the value precisely. Good enough for\n // \"did my context drop?\" visual feedback.\n const effectiveTokens = (result.usage.output ?? 0) + restoration.estimatedTokens\n\n // Persist via the live session when active so wrapper hooks fire\n // (downstream observers see `session:turns` for the marker turn);\n // otherwise append directly through the store for inactive sessions.\n if (liveSession) {\n await liveSession.appendTurns([summaryTurn, ...restoration.turns])\n // Repaint the transcript so the boundary card appears now, not\n // after the next agent.run. `eventsFromTurns` emits the\n // `compact-summary` event the renderer already knows about.\n //\n // Gated on the session still being current — auto-compact's\n // background nature means the user can navigate away mid-flight;\n // painting the old session's events onto the new session's\n // transcript would be confusing. The persisted data above is\n // unaffected; when the user returns to this session it rehydrates\n // through `eventsFromTurns` normally.\n if (sessionRef.current?.id === sessionId) {\n setEvents(eventsFromTurns(liveSession.turns, liveSession.runs))\n // Drop the footer's `ctx` indicator to the new effective size.\n // Same session-id gate as the events repaint — we never want to\n // overwrite the active session's footer with another session's\n // post-compact estimate.\n setLastInputTokens(effectiveTokens)\n lastInputTokensRef.current = effectiveTokens\n // Latch the post-compact baseline for hysteresis. Subsequent\n // turns' `shouldAutoCompact` calls compare the new\n // `lastInputTokensRef` against this baseline + the configured\n // growth floor to suppress compact → compact thrashing.\n lastCompactedInputTokensRef.current = effectiveTokens\n }\n setCurrentSession(prev => prev && prev.id === sessionId\n ? {\n ...prev,\n turnCount: liveSession.turns.length,\n updatedAt: Date.now(),\n }\n : prev)\n }\n else {\n if (!loaded)\n throw new Error('Session disappeared mid-compaction.')\n // Inactive sessions get only the marker — restoration needs an\n // execution context we don't have without an active agent. Same\n // append shape so `summaryTurn` is always the immediate successor\n // of the elided turns.\n const nextData: SessionData = {\n ...loaded,\n turns: [...loaded.turns, summaryTurn],\n updatedAt: Date.now(),\n }\n await store.save(nextData)\n .catch(err => debugLog('compact: store.save failed', err))\n }\n\n // Refresh the sessions list so the updated turn count + timestamps\n // show immediately if the modal was opened from the sessions screen.\n await refreshSessions().catch(err => debugLog('compact: refreshSessions failed', err))\n return {\n replacedCount: result.summarizedTurnIds.length,\n droppedCount: result.droppedDueToPtl.length,\n restoredFiles: restoration.restoredFiles,\n restoredSkills: restoration.restoredSkills,\n model: result.model,\n inputTokens: (result.usage.input ?? 0) + (result.usage.cacheRead ?? 0) + (result.usage.cacheCreation ?? 0),\n outputTokens: result.usage.output ?? 0,\n effectiveTokens,\n }\n }, [picked, providerRegistry, store, refreshSessions])\n\n // ---------------------------------------------------------------------\n // Auto-compaction — fires after a normal `agent.run()` completes, when\n // the latest turn's input usage crosses the user-configured threshold.\n //\n // Mechanics:\n // - Runs in the background (no await on the caller's hot path) so the\n // prompt input area stays interactive — the user can compose their\n // next message while the summary call is in flight.\n // - Stores its in-flight promise on `autoCompactInFlightRef`. The\n // next `onSubmitPrompt` invocation awaits it before calling\n // `agent.run()`, so a quick user submit is queued behind the\n // pending compaction (no race against `appendTurns`).\n // - Never auto-continues the conversation — only the user's explicit\n // prompt submission triggers the next turn.\n // - Surfaces `info` events into the transcript so the user sees what\n // happened (\"🗜 Compacting…\" → \"✓ Compacted: …\" or \"✗ failed\").\n // ---------------------------------------------------------------------\n const triggerAutoCompactIfNeeded = useCallback((sessionId: string): void => {\n if (autoCompactInFlightRef.current)\n return // already in flight — predicate guards against double-fire too, but cheap.\n if (!picked)\n return\n const descriptor = providerRegistry[picked.provider.key]\n const rawWindow = descriptor ? getContextWindow(descriptor, picked.model) : null\n const decision = shouldAutoCompact({\n enabled: autoCompactRef.current,\n threshold: autoCompactThresholdRef.current,\n inputTokens: lastInputTokensRef.current,\n rawContextWindow: rawWindow,\n alreadyCompacting: !!autoCompactInFlightRef.current,\n ...(lastCompactedInputTokensRef.current !== undefined\n ? { lastCompactedInputTokens: lastCompactedInputTokensRef.current }\n : {}),\n minGrowthFraction: AUTO_COMPACT_MIN_GROWTH_FRACTION,\n })\n if (decision.kind !== 'fire')\n return\n\n // Announce in the transcript — same `info` kind we use for any\n // generic banner, so the renderer doesn't need a new event type.\n const pct = Math.round(decision.usedFraction * 100)\n stream.appendImmediate({\n kind: 'info',\n text: `🗜 Auto-compacting conversation (used ${pct}% of effective context)…`,\n })\n // Light up the title indicator so the user sees \"conversation is\n // doing something\" even when nothing is streaming visibly. Cleared\n // in the `.finally` below alongside the ref.\n setCompacting(true)\n\n // Real AbortController so `teardown()` can cancel the in-flight\n // LLM call when the user navigates away from the session — both\n // saves tokens and prevents the result handlers below from racing\n // a now-disposed session view.\n const abort = new AbortController()\n autoCompactAbortRef.current = abort\n\n // Capture `sessionId` here so the completion / error handlers can\n // tell whether the user is still on the session this compaction\n // was meant for. If they've navigated away by the time we resolve,\n // skip the transcript event silently — the persisted summary +\n // restoration are correct on the original session and will\n // rehydrate via `eventsFromTurns` when the user comes back.\n const compactionPromise = onCompactSession(sessionId, abort.signal)\n .then((result) => {\n if (sessionRef.current?.id !== sessionId)\n return\n const elidedSuffix = result.droppedCount > 0 ? `, ${result.droppedCount} dropped (PTL)` : ''\n const restoredSuffix = result.restoredFiles > 0 ? `, ${result.restoredFiles} file${result.restoredFiles === 1 ? '' : 's'} restored` : ''\n // Surface the post-compact effective size — the next turn will\n // start from this token count instead of the pre-compact one.\n // Pairs with the footer's `ctx` indicator, which `onCompactSession`\n // already updated; the banner just shows the same number inline\n // so the user doesn't have to glance away to see the drop.\n stream.appendImmediate({\n kind: 'info',\n text: `✓ Compacted: ${result.replacedCount} turn${result.replacedCount === 1 ? '' : 's'} elided${elidedSuffix}${restoredSuffix} → ~${result.effectiveTokens.toLocaleString()} effective tokens.`,\n })\n })\n .catch((err) => {\n if (sessionRef.current?.id !== sessionId)\n return\n // Aborts are an intentional teardown path — never an error from\n // the user's perspective. Stay silent rather than emitting a\n // \"failed\" banner for the very thing we just chose to cancel.\n if (abort.signal.aborted)\n return\n stream.appendImmediate({\n kind: 'error',\n text: `Auto-compaction failed: ${errorMessage(err)}`,\n })\n })\n .finally(() => {\n // Only clear when WE're still the in-flight reference. Defense\n // against a faulty test or future race that swapped the ref\n // mid-flight; we never want to null out somebody else's promise.\n if (autoCompactInFlightRef.current === compactionPromise) {\n autoCompactInFlightRef.current = null\n setCompacting(false)\n }\n if (autoCompactAbortRef.current === abort)\n autoCompactAbortRef.current = null\n })\n\n autoCompactInFlightRef.current = compactionPromise\n }, [picked, providerRegistry, stream, onCompactSession])\n\n // Populate the hoisted `triggerAutoCompactRef` (declared up with the other\n // refs) now that `triggerAutoCompactIfNeeded` exists. The ref bridges the\n // ordering between `continueResumedInteractions` / `onSubmitPrompt` (which\n // need to fire the trigger) and `onCompactSession` (which the trigger\n // depends on), without forcing a major re-arrangement of the file.\n useEffect(() => { triggerAutoCompactRef.current = triggerAutoCompactIfNeeded }, [triggerAutoCompactIfNeeded])\n\n // Session export — render `SessionData` as Markdown/JSON and write it\n // under the project's (or user's) `.{prefix}/sessions/` directory.\n // Prefers the live in-memory snapshot for the active session (freshest\n // turns, no extra store roundtrip); reads the persisted blob from the\n // store for inactive sessions. The renderer-agnostic logic + path\n // resolution live in `chat/session-export.ts`.\n const onExportSession = useCallback(async (\n sessionId: string,\n format: SessionExportFormat,\n ): Promise<{ filepath: string, format: SessionExportFormat }> => {\n let data: SessionData | null = null\n if (sessionId === sessionRef.current?.id)\n data = sessionRef.current.toJSON()\n else\n data = await store.load(sessionId)\n if (!data)\n throw new Error('Session not found.')\n const target = await writeSessionExport({\n session: data,\n format,\n cwd: projectDir,\n prefix: config.prefix,\n })\n return { filepath: target.filepath, format }\n }, [store, projectDir, config.prefix])\n\n const openSessionDetails = useCallback(async (sessionId: string) => {\n // Always reload from the store. The chat screen has the live\n // `sessionRef`, but it's mutated by streaming hooks — loading a\n // fresh snapshot means the modal sees the persisted truth, which\n // is what the user expects (e.g. \"what's on disk if I delete?\").\n const data = await store.load(sessionId)\n if (!data) {\n debugLog('openSessionDetails: session not found', sessionId)\n return\n }\n modal.open(\n <SessionDetailsModal\n session={data}\n title={sessionId === currentSession?.id ? currentSession.title : undefined}\n isCurrent={sessionId === currentSession?.id}\n keybindings={keybindings}\n actions={{\n onDelete: onDeleteSession,\n onExport: onExportSession,\n ...(picked ? { onGenerateTitle, onCompact: onCompactSession } : {}),\n }}\n />,\n )\n }, [modal, store, currentSession, onDeleteSession, picked, onGenerateTitle, onCompactSession, onExportSession, keybindings])\n\n // Open the details modal for the active selection. Reads the turn from\n // `sessionRef.current` lazily so we don't have to wire the full session\n // through React state — it's append-only between activations and the\n // ref is updated synchronously in `activateSession`.\n const openSelectedTurn = useCallback(() => {\n const id = selectedTurnId\n if (!id)\n return\n const session = sessionRef.current\n if (!session)\n return\n const turn = session.turns.find(t => t.id === id)\n if (!turn)\n return\n const index = turnIds.indexOf(id) + 1\n modal.open(\n <TurnDetailsModal\n turn={turn}\n index={index}\n total={turnIds.length}\n actions={{ onFork: onForkTurn, onDelete: onDeleteTurn, onEdit: onEditTurn }}\n keybindings={keybindings}\n />,\n )\n }, [modal, selectedTurnId, turnIds, onForkTurn, onDeleteTurn, onEditTurn, keybindings])\n\n useKeyboard((key) => {\n if (modal.isOpen)\n return\n // Select-turn mode takes precedence on the chat screen — the textarea\n // is unfocused in this mode (see `ChatScreen` → `PromptBlock`) so up /\n // down / return reach this handler instead of moving the textarea\n // cursor. Esc exits the mode; the parent's \"esc = back / abort\" rule\n // only applies in normal mode.\n if (inSelectMode && screen === 'chat') {\n if (key.name === 'up') {\n cycleSelectedTurn(-1)\n return\n }\n if (key.name === 'down') {\n cycleSelectedTurn(1)\n return\n }\n if (key.name === 'return') {\n openSelectedTurn()\n return\n }\n if (key.name === 'escape') {\n setSelectedTurnId(null)\n return\n }\n // Swallow other keys so a stray ctrl+o doesn't open Settings mid-\n // selection — the user can exit first if they want a global action.\n return\n }\n // Queue-selection mode — same precedence rationale as select-turn.\n // The textarea is unfocused while `queueSelectionIndex != null` (see\n // PromptBlock's `selectMode`-style branch), so plain `up` / `down` /\n // `escape` / `delete` reach this handler without being eaten by the\n // textarea's own bindings. `pushQueuedMessage` carries the configured\n // shortcut (ctrl+return by default) so the user can rebind it.\n if (queueSelectionIndex != null && screen === 'chat') {\n if (key.name === 'up') {\n moveQueueSelection(-1)\n return\n }\n if (key.name === 'down') {\n moveQueueSelection(1)\n return\n }\n if (matchesBinding(key, keybindings.pushQueuedMessage)) {\n pushSelectedQueuedMessage()\n return\n }\n if (matchesBinding(key, keybindings.dropQueuedMessage)) {\n dropSelectedQueuedMessage()\n return\n }\n if (key.name === 'escape') {\n exitQueueSelection()\n return\n }\n // Same \"swallow stray globals\" rule as select-turn mode — the\n // user has explicitly picked the queue as their focus surface.\n return\n }\n // `enterQueueSelection` — moves focus into the queue box. Only fires\n // when there's something to select; gated on chat screen + no\n // approval / interaction (those own the slot the prompt would\n // otherwise be in). Busy state is fine — the queue itself implies\n // a run is in flight.\n if (\n matchesBinding(key, keybindings.enterQueueSelection)\n && screen === 'chat'\n && !pendingApproval\n && !pendingInteraction\n && messageQueue.length > 0\n ) {\n enterQueueSelection()\n return\n }\n // Global action shortcuts — every binding resolves through\n // `config.keybindings` so the user can rebind any of them via\n // `<userDir>/keybindings.json`. The hardcoded `key.name === 'o'`-\n // style checks used to live here; they got replaced with\n // `matchesBinding(key, …)` against the merged user+defaults map.\n // The action's *gates* (screen / busy / pendingApproval / model\n // capability / registry size) stay attached to its trigger so a\n // rebind to a different shortcut still respects the same eligibility.\n if (matchesBinding(key, keybindings.openSettings) && screen !== 'auth') {\n // `detectAuth` runs synchronously on a small JSON file + env\n // lookups — cheap enough to call at modal-open time so the\n // Authentication tab always reflects the FRESHEST credentials\n // state (e.g. the user just ran the wizard from another modal).\n // `availableProviders` state is filtered to `.available === true`\n // for the model picker; we want the FULL list here so users see\n // unconfigured providers too.\n const allProviders = detectAuth(config.paths.userDir, providerRegistry)\n // Closure-wrap `onPickProvider` to also tear down the modal —\n // the existing top-level `onPickProvider` flips screens (chat\n // ↔ sessions) and would leave the Settings modal hanging over\n // the new screen if we didn't dismiss it first.\n const onPickProviderFromSettings = (provider: ProviderAuth) => {\n modal.close()\n void onPickProvider(provider)\n }\n modal.open(\n <SettingsModal\n keybindings={keybindings}\n keybindingsPath={keybindingsPath(config.paths.userDir)}\n authentication={{\n currentProviderLabel: picked?.provider.label,\n currentProviderKey: picked?.provider.key,\n currentModel: picked?.model,\n providers: allProviders,\n }}\n actions={{\n onReauth,\n onPickProvider: onPickProviderFromSettings,\n onOpenKeybindings: onOpenKeybindingsFile,\n onLoginMcp,\n onLogoutMcp,\n onCancelLoginMcp,\n onRefreshSkills,\n onRefreshMcps,\n }}\n />,\n )\n return\n }\n // `openSessionDetails` — targets the active session on the chat\n // screen, or the focused row in the sessions list. Gated on the\n // chat side by `!busy && !pendingApproval` so a live run isn't\n // interrupted mid-stream by the modal stealing focus + handlers.\n // On the sessions screen the modal is read-mostly (delete confirms\n // first) so no run-state gate is needed there.\n if (matchesBinding(key, keybindings.openSessionDetails)) {\n if (screen === 'chat' && currentSession && !busy && !pendingApproval) {\n void openSessionDetails(currentSession.id)\n return\n }\n if (screen === 'sessions' && isSessionRowId(focusedSessionId)) {\n void openSessionDetails(focusedSessionId)\n return\n }\n }\n if (matchesBinding(key, keybindings.openModelPicker) && screen === 'chat' && picked && !busy) {\n modal.open(\n <ModelPickerModal\n providers={availableProviders}\n modelsFor={modelsFor}\n current={{ providerKey: picked.provider.key, modelId: picked.model }}\n onPick={onPickModel}\n />,\n )\n return\n }\n // `openEffortPicker` — gated on reasoning support: a non-reasoning\n // model has no effort knob and the modal would be a dead-end. Same\n // idle gating as the model picker so the modal doesn't steal\n // focus mid-stream.\n if (matchesBinding(key, keybindings.openEffortPicker) && screen === 'chat' && picked && !busy && modelHasReasoning) {\n const descriptor = providerRegistry[picked.provider.key]\n modal.open(\n <EffortPickerModal\n current={picked.effort}\n supportsAdaptive={!!descriptor && piIdOf(descriptor) === 'anthropic'}\n onPick={onPickEffort}\n />,\n )\n return\n }\n // `openTodos` — chat-only viewer for the active run's `todowrite`\n // checkpoints. Read-only by design (the model owns the list), so\n // there's no run-state gate beyond \"we're in a session\": opening\n // the modal mid-stream is fine, the live data flows through\n // `useActiveTodos` on every parent re-render. Still gated on\n // `screen === 'chat'` so the binding stays inert on auth /\n // sessions screens where there's no session to read.\n if (matchesBinding(key, keybindings.openTodos) && screen === 'chat' && currentSession) {\n // Pass `agentRef.current` so the modal can subscribe to\n // `tool:after` for live updates — `<ModalRoot>` sits ABOVE\n // `<AppShell>` in the tree, so the streaming-buffer cascade\n // doesn't reach it. Without the subscription the modal would\n // freeze at its open-time snapshot.\n modal.open(<TodosModal session={sessionRef.current} agent={agentRef.current} />)\n return\n }\n // `openKeybindings` — quick reference panel listing every shortcut\n // grouped by surface, with a button to edit the JSON file. Read-only\n // (changes to `keybindings.json` apply on next launch, see\n // `onOpenKeybindingsFile`), so no run-state gating beyond\n // `screen !== 'auth'`: the catalog is meaningful on every screen\n // and opening it mid-stream is safe.\n if (matchesBinding(key, keybindings.openKeybindings) && screen !== 'auth') {\n modal.open(\n <KeybindingsModal\n bindings={keybindings}\n filePath={keybindingsPath(config.paths.userDir)}\n onEditFile={onOpenKeybindingsFile}\n onClose={() => modal.close()}\n />,\n )\n return\n }\n // `enterSelectTurnMode` — only on the chat screen, only when idle.\n // A streaming turn would have a moving `turnIds` tail under our\n // feet — disrupting the muscle memory of \"I just selected this\n // turn\". No-op when the session has no turns yet. Also gated on\n // `pendingInteraction` so up/down don't fight the InteractionBlock's\n // own picker.\n if (matchesBinding(key, keybindings.enterSelectTurnMode) && screen === 'chat' && !busy && !pendingApproval && !pendingInteraction) {\n enterSelectMode()\n return\n }\n // `cycleAgent` — quick-cycle through agent profiles without\n // opening the picker. Gated on chat screen, idle state, no pending\n // approval/interaction (the wizard reuses `shift+↹` for back), and\n // a multi-profile registry so the textarea never sees it as input\n // and single-agent setups don't get a no-op binding. (The\n // descriptive picker is intentionally not bound at the global\n // level — `ctrl+a` is the textarea's select-all.)\n if (matchesBinding(key, keybindings.cycleAgent) && screen === 'chat' && hasMultipleAgents && !busy && !pendingApproval && !pendingInteraction) {\n void onCycleAgent()\n return\n }\n // `cancelToolCall` — surfaces a small picker that lets the user\n // stop something the agent has running without aborting the whole\n // run. Two kinds of entries land in the picker:\n //\n // 1. Mid-dispatch tool calls — `inFlightTools`, populated by\n // `tool:before` / drained by `tool:after`. Cancelled via\n // `agent.cancelTool(callId)`.\n //\n // 2. Running background tasks — `backgroundTasks`, populated by\n // `background:start` / drained by `background:exit`. A\n // background task SURVIVES past its spawning `tool:after`\n // (the shell tool body returns the handle and ends, but the\n // child process keeps running), so it can't live in\n // `inFlightTools` alone. Cancelled via\n // `agent.killBackgroundTask(taskId)`.\n //\n // The picker is open whenever EITHER registry has entries — not\n // just `busy`. A background task running while the agent is idle\n // (waiting for the user's next prompt) is the central use case.\n //\n // `!pendingApproval` stays the same — the approval picker IS the\n // gate for un-dispatched calls; this picker is for already-running\n // things.\n if (matchesBinding(key, keybindings.cancelToolCall) && screen === 'chat' && !pendingApproval) {\n // Filter to PARENT-level rows only. Subagent entries (rows with\n // `childId`) bubble through the parent's hook bus and look\n // cancellable here, but the cancel primitives (`cancelTool` /\n // `killBackgroundTask`) walk the PARENT agent's maps — the\n // child's entries live in the CHILD agent's maps. Without the\n // filter, picking a child row would be a silent no-op. Cancelling\n // the parent's `spawn` call IS in the picker and DOES unwind the\n // whole subtree (spawn body's `ctx.signal` flips, child run\n // sees the abort, child's destroy SIGTERMs its tasks).\n const tools = inFlightToolsRef.current.filter(entry => entry.childId === undefined)\n const tasks = backgroundTasksRef.current.filter(entry => entry.childId === undefined)\n const snapshot = [...tools, ...tasks]\n if (snapshot.length === 0)\n return\n modal.open(\n <CancelToolModal\n inFlight={snapshot}\n onCancel={(entry, reason) => {\n const agent = agentRef.current\n if (!agent)\n return false\n // Route by discriminator. Tool calls go through the\n // per-call abort signal; background tasks go through the\n // execution context's process-group kill.\n if (entry.kind === 'task')\n return agent.killBackgroundTask(entry.callId)\n return agent.cancelTool(entry.callId, reason)\n }}\n onCancelAll={() => {\n const agent = agentRef.current\n if (!agent)\n return\n for (const entry of snapshot) {\n if (entry.kind === 'task')\n void agent.killBackgroundTask(entry.callId)\n else\n agent.cancelTool(entry.callId, 'user-cancelled-all')\n }\n }}\n onClose={() => modal.close()}\n />,\n )\n return\n }\n if (matchesBinding(key, keybindings.changeCwd) && screen !== 'auth') {\n modal.open(\n <CwdPickerModal\n currentCwd={cwdRef.current}\n onPick={(dir) => {\n setCwd(dir)\n modal.close()\n }}\n />,\n )\n return\n }\n if (key.name !== 'escape')\n return\n // Esc always aborts the in-flight run (whether or not a prompt is\n // pending) — `onAbort` denies every queued approval and flips the\n // agent's signal. Per-call accept/deny lives in the approval picker\n // itself; this keeps \"esc = stop everything\" as a single rule.\n if (busy || pendingApproval)\n return onAbort()\n // Defer to the prompt's completion popup if it's open — Esc there\n // means \"close the @file / /skill picker\", *not* \"leave the chat\n // for the session picker\". `onAbort` above already short-circuits\n // on the same ref; this is the matching guard for the idle path.\n if (popupOpenRef.current)\n return\n if (screen === 'chat')\n return onOpenSessions()\n if (screen === 'sessions') {\n if (currentSession)\n setScreen('chat')\n else\n renderer.destroy()\n return\n }\n // auth screen — if the user got here from settings (picked already\n // exists), bounce them back to wherever they were. Only exit on a true\n // first-launch (no provider picked yet).\n if (picked) {\n setScreen(currentSession ? 'chat' : 'sessions')\n return\n }\n renderer.destroy()\n })\n\n // -------------------------------------------------------------------------\n // Derived footer state.\n // -------------------------------------------------------------------------\n\n // Resumed-interaction state — `pendingInteraction` is set but no live\n // run is in flight. The footer + esc handler treat this as \"pause\"\n // rather than \"abort\": esc leaves the form without writing a\n // `tool_result`, so the session stays paused and re-enqueues the\n // same interaction on next activation. Live interactions (busy=true)\n // continue to follow the abort rules below.\n const pendingInteractionIsResumed = !!pendingInteraction && !busy\n\n // Auto-update — quietly poll the registry every 24 h, surface a passive\n // `↑ vX.Y.Z` chip in the footer when a newer release is available. The\n // hook is a no-op when the host didn't wire `autoUpdate` or the user\n // disabled `Settings.checkForUpdates`; env opt-outs (CI / NO_UPDATE_NOTIFIER\n // / ZIDANE_NO_UPDATE) fire inside `checkForUpdate`.\n const updateStatus = useUpdateCheck({\n packageName: autoUpdateConfig?.packageName ?? '',\n currentVersion: autoUpdateConfig?.currentVersion ?? '',\n enabled: !!autoUpdateConfig && settings.checkForUpdates,\n cacheDir,\n registry: autoUpdateConfig?.registry,\n channel: autoUpdateConfig?.channel,\n cacheTtlMs: autoUpdateConfig?.cacheTtlMs,\n })\n const updateHint = useMemo(\n () => buildUpdateHint(updateStatus, { color: COLOR.accent }),\n [updateStatus, COLOR.accent],\n )\n\n const hints: Hint[] = useMemo(\n () => buildHints({\n screen,\n busy,\n pending: !!pendingApproval,\n pendingInteractionLive: !!pendingInteraction && busy,\n pendingInteractionResumed: pendingInteractionIsResumed,\n currentSession,\n hasMultipleAgents,\n uiMode: settings.uiMode,\n modelLabel: picked?.model ?? null,\n modelColor: COLOR.model,\n effortLabel: modelHasReasoning ? (picked?.effort ?? 'medium') : null,\n effortColor: COLOR.warn,\n // `/n` rides the same `warn` accent as `ctrl+m` — both are real\n // shortcuts, so the chord reads as one keyboard binding (\"ctrl+m\n // or ctrl+n\") instead of a primary key with a hidden tail.\n effortKeyColor: COLOR.warn,\n agentLabel: pickedAgent.label,\n agentColor: accentColor(pickedAgent.accent, COLOR),\n keybindings,\n // Count parent-level rows from BOTH registries — mid-dispatch\n // tool calls AND running background tasks. Subagent entries\n // (rows with `childId`) are filtered out of the picker itself\n // (different cancel map), so the hint mirrors that scope.\n inFlightToolCount: inFlightTools.reduce(\n (n, entry) => entry.childId === undefined ? n + 1 : n,\n 0,\n ) + backgroundTasks.reduce(\n (n, entry) => entry.childId === undefined ? n + 1 : n,\n 0,\n ),\n activeSkillCount: activeSkillNames.size,\n skillsChipColor: COLOR.brand,\n updateHint,\n }),\n [screen, busy, pendingApproval, pendingInteraction, pendingInteractionIsResumed, currentSession, hasMultipleAgents, settings.uiMode, picked, pickedAgent, COLOR, modelHasReasoning, keybindings, inFlightTools, backgroundTasks, activeSkillNames, updateHint],\n )\n\n // Trigger affordances rendered on the right of the prompt-box title\n // overlay (after the prompt's keyboard shortcuts). Each entry is\n // gated on its provider having content — an empty skills catalog\n // means `/ skills` would be a dead hint, so we drop it entirely\n // rather than advertise a no-op. Files: same. Threaded into\n // `ChatScreen` → `PromptBlock` → `PromptHints` which owns the\n // responsive drop logic (these vanish before the primary shortcuts\n // when the terminal is too narrow).\n // Flatten the {@link QueuedMessage} shape (prompt + raw refs) into the\n // lighter {@link QueuedPreview} the ChatScreen renders. The mapping is\n // memoised by `messageQueue` identity so the queue box only re-renders\n // when the underlying queue actually changes — and the same identity\n // contract is preserved for the consumer's `=== EMPTY_QUEUED_MESSAGES`\n // default-stability check (we pass the live `messageQueue` array\n // through; React's default-prop fallback only fires when undefined).\n const queuedMessagePreviews = useMemo(\n () => messageQueue.map(m => ({\n text: m.prompt,\n refs: m.references\n .filter(r => r.start >= 0 && r.end > r.start)\n .map(r => ({ start: r.start, end: r.end, providerId: r.providerId })),\n })),\n [messageQueue],\n )\n\n // Resolved shortcut specs threaded into the queue UI so the title\n // overlay (\"ctrl+↑ select\") and the per-row action hints (\"ctrl+↵\n // push\", \"⌫ drop\") track the user's customizations from\n // `keybindings.json` instead of hardcoding the defaults.\n const queueShortcuts = useMemo(() => ({\n enter: keybindings.enterQueueSelection,\n push: keybindings.pushQueuedMessage,\n drop: keybindings.dropQueuedMessage,\n }), [keybindings])\n\n const promptTriggerHints: Hint[] = useMemo(() => {\n // Always advertise both triggers — discovery for the underlying\n // catalogs is now lazy (kicks off when the popover first opens),\n // so gating on `catalog.length > 0` would hide the hints until\n // the user has already used them. Hints are pure discoverability\n // affordances; an empty popover is harmless.\n return [\n {\n key: '@',\n label: 'files',\n keyColor: resolveChipColor(SURFACE.chips, 'files').bg,\n },\n {\n key: '/',\n label: 'skills',\n keyColor: resolveChipColor(SURFACE.chips, 'skills').bg,\n },\n ]\n }, [SURFACE])\n\n const contextUsage: ContextUsage | null = useMemo(() => {\n if (screen !== 'chat' || !picked)\n return null\n const descriptor = providerRegistry[picked.provider.key]\n if (!descriptor)\n return null\n // `getContextWindow` already checks `descriptor.models` first and falls\n // back to pi-ai's registry — one call covers both cases.\n const max = getContextWindow(descriptor, picked.model)\n return max ? { used: lastInputTokens, max } : null\n }, [screen, picked, lastInputTokens, providerRegistry])\n\n const footerCost = screen === 'chat' ? sessionCost : null\n\n // Drop the agent + clear timers when the app unmounts.\n useEffect(() => () => { void teardown() }, [teardown])\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1, backgroundColor: SURFACE.background }}>\n <box style={{ flexDirection: 'column', flexGrow: 1, paddingLeft: 1, paddingRight: 1 }}>\n {screen === 'auth' && <AuthScreen onPick={onPickProvider} />}\n {screen === 'sessions' && (\n <SessionsScreen\n sessions={sessions}\n currentId={currentSession?.id ?? null}\n focusedSessionId={focusedSessionId}\n onPick={onSwitchSession}\n onCreate={onCreateSession}\n onFocusChange={setFocusedSessionId}\n showAllProjects={settings.showAllProjects}\n currentProjectRoot={projectDir}\n />\n )}\n {screen === 'chat' && (\n <ChatScreen\n cwd={cwd}\n events={events}\n busy={busy}\n compacting={compacting}\n queuedMessages={queuedMessagePreviews}\n queueSelectionIndex={queueSelectionIndex}\n queueShortcuts={queueShortcuts}\n onEnterQueueFromEmptyPrompt={enterQueueSelectionFromEmptyPrompt}\n settings={settings}\n onSubmit={onSubmitPrompt}\n session={currentSession}\n liveSession={sessionRef.current}\n pending={pendingApproval}\n onApproval={resolveHead}\n pendingInteraction={pendingInteraction}\n onInteraction={onInteractionResolve}\n completionProviders={completionProviders}\n onPopupOpenChange={onPopupOpenChange}\n selectedTurnId={selectedTurnId}\n promptTriggerHints={promptTriggerHints}\n />\n )}\n </box>\n <Footer\n hints={hints}\n context={contextUsage}\n cost={footerCost}\n status={\n pendingInteraction?.kind === 'question'\n ? 'asking'\n : busy\n ? 'busy'\n : compacting\n ? 'compacting'\n : null\n }\n />\n </box>\n )\n}\n\n/**\n * Resolve the reasoning effort to seed `Picked.effort` for the given model.\n * Returns `undefined` when the model has no reasoning knob; otherwise the\n * per-model remembered value, defaulting to `'medium'` (sensible middle\n * ground when the user has never picked an explicit effort for this model).\n */\nfunction effortForModel(\n descriptor: ProviderDescriptor,\n modelId: string,\n remembered: Record<string, ThinkingLevel> | undefined,\n): ThinkingLevel | undefined {\n if (!modelSupportsReasoning(descriptor, modelId))\n return undefined\n return remembered?.[modelId] ?? 'medium'\n}\n","import type { FiletypeParserOptions } from '@opentui/core'\nimport { addDefaultParsers, getTreeSitterClient } from '@opentui/core'\n\n/**\n * Register Tree-sitter parsers for the languages we'd like highlighted\n * inside fenced markdown code blocks.\n *\n * OpenTUI ships JS/TS/Markdown/Zig out of the box. Anything else needs a\n * Tree-sitter `.wasm` grammar + a `highlights.scm` capture query. We fetch\n * both from the upstream Tree-sitter grammar repos; OpenTUI's worker\n * caches them under its data path (`~/.local/share/opentui/...` by\n * default) so the download is a one-shot cost per language per machine.\n *\n * `aliases` lets a single grammar handle multiple fence info-strings — e.g.\n * `bash` also matches ` ```sh ` and ` ```shell `. The model picks fences\n * inconsistently across providers; aliases save us from missing highlights\n * on synonyms.\n *\n * Runtime caveats:\n * - **First use** of a language triggers an HTTPS download. Subsequent\n * uses (same machine, same data path) are instant.\n * - **Compiled binaries** (`bun --compile`) still work — the data path\n * is a writable OS dir, not the bunfs. Air-gapped deployments would\n * need to either pre-populate the cache or migrate to local-file\n * vendoring via `with { type: 'file' }` imports (see\n * https://opentui.com/docs/reference/tree-sitter/#use-local-files).\n * - If a download fails (offline / firewall), the language renders as\n * plain `markup.raw.block` — no crash, just no syntax color.\n * - **Self-hosted wasm.** Languages whose upstream grammars don't ship\n * `.wasm` releases (currently: SQL) are built ahead of time and\n * vendored under `tui/parsers/`; the TUI binary embeds the file via\n * `with { type: 'file' }` (see `tui/src/tree-sitter-bundle.ts`),\n * extracts it to a tmpdir at startup, and points the SDK at the\n * extracted path through a `ZIDANE_LOCAL_TREE_SITTER_<X>_WASM` env\n * var — keeps the repo's privacy boundary intact (no\n * `raw.githubusercontent.com` fetches) and works offline / inside\n * air-gapped runners. SDK consumers without these env vars set just\n * skip the affected grammars. See `tui/parsers/README.md` for the\n * rebuild recipe and `LOCAL_PARSERS` below for the env-var\n * contract.\n *\n * Versions are pinned in the WASM URLs so a grammar repo's `master`\n * landing a breaking change can't silently affect us.\n */\nconst EXTRA_PARSERS: FiletypeParserOptions[] = [\n {\n filetype: 'python',\n aliases: ['py'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-python/releases/download/v0.23.6/tree-sitter-python.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-python/v0.23.6/queries/highlights.scm'],\n },\n },\n {\n filetype: 'bash',\n aliases: ['sh', 'shell', 'zsh'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-bash/releases/download/v0.23.3/tree-sitter-bash.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-bash/v0.23.3/queries/highlights.scm'],\n },\n },\n {\n filetype: 'json',\n wasm: 'https://github.com/tree-sitter/tree-sitter-json/releases/download/v0.24.8/tree-sitter-json.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-json/v0.24.8/queries/highlights.scm'],\n },\n },\n {\n filetype: 'rust',\n aliases: ['rs'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-rust/releases/download/v0.23.2/tree-sitter-rust.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-rust/v0.23.2/queries/highlights.scm'],\n },\n },\n {\n filetype: 'go',\n aliases: ['golang'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-go/releases/download/v0.23.4/tree-sitter-go.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-go/v0.23.4/queries/highlights.scm'],\n },\n },\n {\n filetype: 'yaml',\n aliases: ['yml'],\n wasm: 'https://github.com/tree-sitter-grammars/tree-sitter-yaml/releases/download/v0.7.0/tree-sitter-yaml.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter-grammars/tree-sitter-yaml/v0.7.0/queries/highlights.scm'],\n },\n },\n {\n filetype: 'html',\n aliases: ['htm'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-html/releases/download/v0.23.2/tree-sitter-html.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-html/v0.23.2/queries/highlights.scm'],\n },\n },\n {\n filetype: 'css',\n wasm: 'https://github.com/tree-sitter/tree-sitter-css/releases/download/v0.23.2/tree-sitter-css.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-css/v0.23.2/queries/highlights.scm'],\n },\n },\n {\n filetype: 'haskell',\n aliases: ['hs'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-haskell/releases/download/v0.23.1/tree-sitter-haskell.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-haskell/v0.23.1/queries/highlights.scm'],\n },\n },\n {\n filetype: 'c',\n // `h` would clash with C++ header conventions — leave header fences\n // unmapped so they fall through to the standard fence renderer\n // rather than picking the wrong grammar.\n wasm: 'https://github.com/tree-sitter/tree-sitter-c/releases/download/v0.24.1/tree-sitter-c.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-c/v0.24.1/queries/highlights.scm'],\n },\n },\n {\n filetype: 'cpp',\n // CommonMark accepts arbitrary fence info-strings — `c++`, `cxx`, `cc`\n // are common in the wild and all unambiguously C++.\n aliases: ['c++', 'cxx', 'cc'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-cpp/releases/download/v0.23.4/tree-sitter-cpp.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-cpp/v0.23.4/queries/highlights.scm'],\n },\n },\n {\n filetype: 'csharp',\n // Upstream ships the wasm with an underscore in the filename\n // (`tree-sitter-c_sharp.wasm`) — pinned URL handles that quirk.\n aliases: ['cs', 'c#'],\n wasm: 'https://github.com/tree-sitter/tree-sitter-c-sharp/releases/download/v0.23.1/tree-sitter-c_sharp.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/tree-sitter/tree-sitter-c-sharp/v0.23.1/queries/highlights.scm'],\n },\n },\n {\n filetype: 'elixir',\n aliases: ['ex', 'exs'],\n wasm: 'https://github.com/elixir-lang/tree-sitter-elixir/releases/download/v0.3.4/tree-sitter-elixir.wasm',\n queries: {\n highlights: ['https://raw.githubusercontent.com/elixir-lang/tree-sitter-elixir/v0.3.4/queries/highlights.scm'],\n },\n },\n]\n\n/**\n * Vendored-grammar registration table for languages whose upstream\n * maintainers don't ship a `.wasm`. Each entry is gated on an env var\n * that points at an on-disk wasm path; when the var is unset (SDK\n * consumer running zidane outside the TUI binary), the grammar is\n * simply skipped — fences fall through to plain text.\n *\n * The TUI binary populates these via `tui/src/tree-sitter-bundle.ts`,\n * which embeds the wasm as a `with { type: 'file' }` import and\n * extracts it to a tmpdir at startup. External consumers can wire\n * their own paths by exporting the matching env var before calling\n * {@link setupTreeSitter}.\n */\ninterface LocalParser {\n /** Skeleton entry — filled in with the resolved wasm path at registration time. */\n template: Omit<FiletypeParserOptions, 'wasm'>\n /** Env var holding the absolute path to the local `.wasm`. */\n pathEnvVar: string\n}\n\nconst LOCAL_PARSERS: readonly LocalParser[] = [\n {\n // SQL: upstream `DerekStride/tree-sitter-sql` ships grammar source only;\n // we build the wasm ahead of time and ship it inside the TUI binary\n // (see `tui/parsers/tree-sitter-sql.wasm` + `tui/parsers/README.md`).\n // The `highlights.scm` is still pinned to upstream's release tag —\n // their repo is public so no credentials needed.\n template: {\n filetype: 'sql',\n aliases: ['psql', 'mysql', 'sqlite', 'plsql'],\n queries: {\n highlights: ['https://raw.githubusercontent.com/DerekStride/tree-sitter-sql/v0.3.11/queries/highlights.scm'],\n },\n },\n pathEnvVar: 'ZIDANE_LOCAL_TREE_SITTER_SQL_WASM',\n },\n]\n\n/**\n * Walk {@link LOCAL_PARSERS} and return the parsers whose `pathEnvVar`\n * resolves to a non-empty string. Stripped entries log a one-shot warning\n * to stderr so a TUI host with broken bundle setup doesn't fail\n * silently.\n */\nfunction resolveLocalParsers(): FiletypeParserOptions[] {\n const resolved: FiletypeParserOptions[] = []\n for (const { template, pathEnvVar } of LOCAL_PARSERS) {\n const wasm = process.env[pathEnvVar]\n if (wasm && wasm.length > 0)\n resolved.push({ ...template, wasm })\n }\n return resolved\n}\n\nlet registered = false\nlet workerInitStarted = false\n\n/**\n * Synchronously append every URL-fetched + locally-vendored grammar to\n * OpenTUI's global parser registry. Cheap (just a couple of `Array.push`\n * calls into a module-level table) and idempotent — subsequent calls\n * are no-ops.\n *\n * Split out from {@link initTreeSitterWorker} so the boot path can land\n * the registry *synchronously* on its critical path (no awaiting\n * required) and defer the heavier worker spin-up below until after the\n * first frame is on screen. OpenTUI's `highlightOnce` self-initialises\n * the worker on first use anyway — registering parsers ahead of time is\n * the only piece that genuinely has to happen synchronously.\n */\nexport function registerTreeSitterParsers(): void {\n if (registered)\n return\n registered = true\n addDefaultParsers([...EXTRA_PARSERS, ...resolveLocalParsers()])\n}\n\n/**\n * Spin up OpenTUI's parser worker thread. Idempotent — concurrent\n * callers share a single in-flight promise; subsequent calls after\n * resolution are no-ops.\n *\n * Safe to fire-and-forget after the renderer mounts: the worker boot\n * is the heaviest step in tree-sitter setup (~40–100ms on most\n * machines) and nothing visible needs it until the first fenced code\n * block highlight, which is many frames away even on the fastest\n * sessions. If a `<markdown>` element happens to call\n * `highlightOnce()` before our explicit init completes, OpenTUI's\n * client guards that call with its own `if (!this.initialized) await\n * this.initialize()` — no race.\n */\nexport function initTreeSitterWorker(): Promise<void> {\n if (!workerInitStarted) {\n workerInitStarted = true\n // Make sure the parsers are registered before the worker comes up;\n // otherwise `registerDefaultParsers()` inside `initialize()` would\n // run against an incomplete set on first boot.\n registerTreeSitterParsers()\n }\n return getTreeSitterClient().initialize()\n}\n\n/**\n * Convenience: register parsers AND await worker boot.\n *\n * Kept exported for hosts that genuinely need tree-sitter ready before\n * the first paint (CI smoke tests, scripted runs). The TUI itself uses\n * the split form to keep boot fast — see `src/tui/index.tsx`.\n */\nexport async function setupTreeSitter(): Promise<void> {\n registerTreeSitterParsers()\n await initTreeSitterWorker()\n}\n","/** @jsxImportSource @opentui/react */\nimport type { McpAuthStatus } from '../chat/mcp-auth-state'\nimport type { DiscoveredMcp, DiscoveryError } from '../chat/mcps-discovery'\nimport { homedir } from 'node:os'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useState } from 'react'\nimport { useEnabledToggleSet } from '../chat/enabled-toggle-set'\nimport { useMcpAuthState } from '../chat/mcp-auth-context'\nimport { getMcpAuthStatus } from '../chat/mcp-auth-state'\nimport { useColors } from '../chat/theme-context'\nimport { Modal } from './modal'\nimport { McpAuthorizingPanel } from './oauth-auth-block'\n\n/**\n * MCP server picker. Shows discovered entries with three columns:\n *\n * [enabled?] <name> <transport · detail> <auth status>\n *\n * Keybindings:\n *\n * ↑ / ↓ / k / j navigate\n * enter / space toggle enabled/disabled\n * l login (needs-auth or error rows; opens browser)\n * o logout (authed rows; wipes stored tokens)\n * esc close — also cancels an in-flight `authorizing` row\n *\n * Status badge legend:\n *\n * ✓ authed tokens stored + bootstrap connected\n * ! needs login bootstrap needs OAuth, no tokens\n * … authorizing interactive login in flight\n * ✗ <error> login attempt failed\n *\n * The state behind the badges is read via `useMcpAuthState()` so updates\n * propagate live while the modal is open — a `mcp:auth:url` arriving from\n * an in-flight login flips the row to `authorizing` without re-opening.\n *\n * Errors (`DiscoveryError[]`) surface in a warn-colored preamble so a\n * broken `mcps.json` is loud rather than invisible.\n */\nexport function McpsSettingsModal({\n catalog,\n errors,\n onLogin,\n onLogout,\n onCancelLogin,\n onRefresh,\n}: {\n catalog: readonly DiscoveredMcp[]\n errors?: readonly DiscoveryError[]\n onLogin: (name: string) => Promise<void> | void\n onLogout: (name: string) => void\n onCancelLogin: (name: string) => void\n /**\n * Optional force-rescan hook bound to `r` on this standalone modal.\n * Hosts wire it to `discoverProjectMcps` (or the future async\n * registry-aware variant). Unlike `SettingsModal`, here we use\n * plain `r` because there's no search input to swallow the key —\n * matches the single-letter convention of `l` / `o`.\n */\n onRefresh?: () => Promise<void> | void\n}) {\n const COLOR = useColors()\n const home = homedir()\n const authState = useMcpAuthState()\n const { enabledSet, toggle } = useEnabledToggleSet<DiscoveredMcp>({\n catalog,\n keyOf: d => d.config.name,\n settingKey: 'enabledMcps',\n })\n const [cursor, setCursorRaw] = useState(0)\n\n // Wrap-around on the edges so ↑ at row 0 jumps to the last server (and\n // ↓ at the bottom comes back to the first). The clamp-on-read below\n // covers the rare case where `catalog` shrinks between writes.\n const moveCursor = useCallback(\n (delta: number) =>\n setCursorRaw((prev) => {\n if (catalog.length === 0)\n return prev\n return (((prev + delta) % catalog.length) + catalog.length) % catalog.length\n }),\n [catalog.length],\n )\n const safeCursor = Math.min(cursor, Math.max(0, catalog.length - 1))\n\n const focusedEntry = catalog[safeCursor]\n const focusedStatus = focusedEntry ? getMcpAuthStatus(authState, focusedEntry.config.name) : undefined\n // While the paste-redirect input on the focused row is live, swallow\n // every single-letter shortcut (`l` / `o` / `r` / `k` / `j` / space)\n // so they end up in the text field. We keep `escape` working so the\n // user can still cancel the in-flight login with one keypress.\n const inputActive = focusedStatus?.kind === 'authorizing' && !!focusedStatus.url\n\n useKeyboard((key) => {\n // Escape always reaches us — it's the cancel affordance.\n if (key.name === 'escape' && focusedEntry) {\n const name = focusedEntry.config.name\n const status = getMcpAuthStatus(authState, name)\n if (status.kind === 'authorizing') {\n onCancelLogin(name)\n return\n }\n }\n if (inputActive)\n return\n if (key.name === 'up' || key.name === 'k' || (key.ctrl && key.name === 'p')) {\n moveCursor(-1)\n return\n }\n if (key.name === 'down' || key.name === 'j' || (key.ctrl && key.name === 'n')) {\n moveCursor(1)\n return\n }\n if (catalog.length === 0)\n return\n const entry = catalog[safeCursor]\n if (!entry)\n return\n const name = entry.config.name\n const status = getMcpAuthStatus(authState, name)\n if (key.name === 'return' || key.name === 'space') {\n toggle(name)\n return\n }\n if (key.name === 'l' && canLogin(entry, status)) {\n void onLogin(name)\n return\n }\n if (key.name === 'o' && canLogout(status)) {\n onLogout(name)\n return\n }\n if (key.name === 'r' && onRefresh) {\n void onRefresh()\n }\n })\n\n if (catalog.length === 0) {\n return (\n <Modal title=\"mcp servers\">\n {renderErrors(errors, home, COLOR.warn)}\n <text fg={COLOR.dim}>No MCP servers discovered.</text>\n <text fg={COLOR.mute}>\n Drop a\n <span fg={COLOR.model}>{' mcps.json '}</span>\n (or\n <span fg={COLOR.model}>{' mcp.json '}</span>\n ) into\n <span fg={COLOR.model}>{' .zidane/ '}</span>\n or\n <span fg={COLOR.model}>{' .agents/ '}</span>\n (project or\n <span fg={COLOR.model}>{' ~/'}</span>\n ).\n </text>\n <text fg={COLOR.mute}>\n Flat array\n <span fg={COLOR.model}>{' [ { \"name\": \"fs\", \"command\": \"...\" } ] '}</span>\n or\n <span fg={COLOR.model}>{' { \"name\": {...} } '}</span>\n map.\n </text>\n <text fg={COLOR.mute}>\n <span fg={COLOR.model}>{' { \"mcpServers\": { ... } } '}</span>\n wrapper from Claude Code / Cursor also works.\n </text>\n <text fg={COLOR.mute}>\n {onRefresh && (\n <span>\n <span fg={COLOR.warn}>r</span>\n {' refresh · '}\n </span>\n )}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n }\n\n return (\n <Modal title={` mcp servers · ${enabledSet.size} / ${catalog.length} enabled `}>\n {renderErrors(errors, home, COLOR.warn)}\n <box style={{ flexDirection: 'column' }}>\n {catalog.map((entry, i) => {\n const focused = i === safeCursor\n const name = entry.config.name\n const enabled = enabledSet.has(name)\n const status = getMcpAuthStatus(authState, name)\n return (\n <text key={name} fg={focused ? COLOR.brand : COLOR.dim}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{name}</span>\n <span fg={COLOR.mute}>\n {' '}\n {detailFor(entry)}\n </span>\n {renderInlineBadge(status, COLOR)}\n </text>\n )\n })}\n </box>\n {focusedEntry && focusedStatus && renderDetailPanel(focusedEntry, focusedStatus, COLOR)}\n {renderActionHints(focusedEntry, focusedStatus, !!onRefresh, COLOR)}\n </Modal>\n )\n}\n\nfunction detailFor(entry: DiscoveredMcp): string {\n const transport = entry.config.transport\n const detail = transport === 'stdio'\n ? entry.config.command ?? ''\n : entry.config.url ?? ''\n return `${transport} · ${detail}`\n}\n\nfunction canLogin(entry: DiscoveredMcp, status: McpAuthStatus): boolean {\n // OAuth login only applies to http transports. `stdio` MCP servers\n // don't speak OAuth — login keypress on them is a no-op.\n if (entry.config.transport === 'stdio')\n return false\n // `idle` is excluded — we'd be guessing at OAuth on a server we haven't\n // tried yet, and the SDK's discovery would 404 on servers that don't\n // advertise it. Let the bootstrap run first; it'll flip the row to\n // `needs-auth` (which IS loginable) or just connect cleanly.\n return status.kind === 'needs-auth' || status.kind === 'error'\n}\n\nfunction canLogout(status: McpAuthStatus): boolean {\n return status.kind === 'authed' || status.kind === 'error' || status.kind === 'authorizing'\n}\n\n/**\n * One-liner badge next to the row name. Stays short so the row never wraps —\n * the verbose info (full URL, full error message) lives in the focused-row\n * detail panel below the list, where it can wrap naturally.\n */\nfunction renderInlineBadge(status: McpAuthStatus, COLOR: ReturnType<typeof useColors>) {\n switch (status.kind) {\n case 'idle':\n return null\n case 'authed':\n return (\n <span fg={COLOR.accent}>\n {' '}\n ✓ authed\n </span>\n )\n case 'needs-auth':\n return (\n <span fg={COLOR.warn}>\n {' '}\n ! needs login\n </span>\n )\n case 'authorizing':\n return (\n <span fg={COLOR.warn}>\n {' '}\n … authorizing\n </span>\n )\n case 'error':\n return (\n <span fg={COLOR.error}>\n {' '}\n ✗ login failed\n </span>\n )\n }\n}\n\n/**\n * Verbose detail panel rendered below the list for the focused row. Only\n * draws something when the status carries information that doesn't fit on\n * the row itself (authorizing URL, error description). Idle / authed /\n * needs-auth rows render nothing here — the inline badge and the action\n * hints already say everything needed.\n *\n * Wraps via stacked `<text>` lines rather than a single multi-line string\n * so long URLs / errors break at safe points (and the modal panel sizes\n * itself to the content automatically).\n */\nfunction renderDetailPanel(\n entry: DiscoveredMcp,\n status: McpAuthStatus,\n COLOR: ReturnType<typeof useColors>,\n) {\n if (status.kind === 'authorizing') {\n if (!status.url) {\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.brand}>{`Authorizing ${entry.config.name}`}</text>\n <text fg={COLOR.dim}>Starting login flow…</text>\n </box>\n )\n }\n // `McpAuthorizingPanel` owns the paste-back input + handler. Default\n // Modal maxWidth=92, padding=2 each side, border=1 each side → content\n // area ≤ 86 cols.\n return (\n <McpAuthorizingPanel\n serverName={entry.config.name}\n authUrl={status.url}\n maxLineWidth={86}\n inputFocused\n />\n )\n }\n if (status.kind === 'error') {\n return (\n <box\n style={{\n flexDirection: 'column',\n border: ['top'],\n borderColor: COLOR.border,\n paddingTop: 1,\n }}\n >\n <text fg={COLOR.error}>\n {`Login failed: ${entry.config.name}`}\n </text>\n <text fg={COLOR.dim}>{status.error}</text>\n <text fg={COLOR.mute}>\n Press\n {' '}\n <span fg={COLOR.warn}>l</span>\n {' '}\n to retry.\n </text>\n </box>\n )\n }\n return null\n}\n\nfunction renderActionHints(\n entry: DiscoveredMcp | undefined,\n status: McpAuthStatus | undefined,\n showRefresh: boolean,\n COLOR: ReturnType<typeof useColors>,\n) {\n const effectiveStatus = status ?? { kind: 'idle' as const }\n const canL = entry ? canLogin(entry, effectiveStatus) : false\n const canO = canLogout(effectiveStatus)\n const canCancel = effectiveStatus.kind === 'authorizing'\n return (\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' toggle'}\n {canL && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>l</span>\n {' login'}\n </span>\n )}\n {canO && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>o</span>\n {' logout'}\n </span>\n )}\n {showRefresh && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>r</span>\n {' refresh'}\n </span>\n )}\n {canCancel && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' cancel'}\n </span>\n )}\n {!canCancel && (\n <span>\n {' · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </span>\n )}\n </text>\n )\n}\n\nfunction renderErrors(\n errors: readonly DiscoveryError[] | undefined,\n home: string,\n warnColor: string,\n) {\n if (!errors || errors.length === 0)\n return null\n return (\n <box style={{ flexDirection: 'column' }}>\n {errors.map(err => (\n <text key={err.path} fg={warnColor}>\n {`! ${displayPath(err.path, home)}: ${err.message}`}\n </text>\n ))}\n </box>\n )\n}\n\nfunction displayPath(path: string, home: string): string {\n if (home && path.startsWith(`${home}/`))\n return `~/${path.slice(home.length + 1)}`\n return path\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ReactNode } from 'react'\nimport type { EnabledAllowlistKey } from '../chat/enabled-toggle-set'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useState } from 'react'\nimport { useEnabledToggleSet } from '../chat/enabled-toggle-set'\nimport { useColors } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/**\n * Generic list-with-checkboxes modal. Powers both the Skills and MCP\n * server pickers — same state machine, same keyboard map, same row\n * geometry. Per-feature variance flows in through props:\n *\n * - `keyOf` — extract the persisted identity (skill name, server name).\n * - `settingKey` — `'enabledSkills'` | `'enabledMcps'`.\n * - `renderDetail` — appended to each row in mute color (descriptions,\n * transports, …). Optional.\n * - `emptyState` — replacement content when `catalog` is empty.\n *\n * Renderer-agnostic state machine lives in `useEnabledToggleSet`\n * (chat layer) — a GUI shell can build its own toggle list against the\n * same hook without pulling OpenTUI.\n */\nexport function ToggleListModal<T>({\n catalog,\n keyOf,\n settingKey,\n title,\n renderDetail,\n emptyState,\n preamble,\n onRefresh,\n}: {\n catalog: readonly T[]\n keyOf: (entry: T) => string\n settingKey: EnabledAllowlistKey\n /** Modal title stem — rendered as `${title} · N / M enabled` in non-empty mode. */\n title: string\n /** Mute-colored trailing copy for each row (description, transport, …). */\n renderDetail?: (entry: T) => ReactNode\n /** What to render when `catalog` is empty (hint about where to drop config). */\n emptyState: ReactNode\n /**\n * Optional content rendered ABOVE the list (or the empty state). Used to\n * surface non-fatal warnings — e.g. discovered files that failed to parse,\n * so the user can fix them without quitting the TUI. Distinct from\n * `emptyState`, which only renders when there's nothing to show; this\n * renders even when the catalog has entries.\n */\n preamble?: ReactNode\n /**\n * Optional force-rescan hook bound to `ctrl+R`. Hosts wire it to a\n * discovery rerun (`discoverProjectSkills`, `discoverProjectMcps`,\n * …); the modal just exposes the keybind and footer hint.\n */\n onRefresh?: () => Promise<void> | void\n}) {\n const COLOR = useColors()\n const { enabledSet, toggle } = useEnabledToggleSet<T>({ catalog, keyOf, settingKey })\n const [cursor, setCursorRaw] = useState(0)\n\n // Wrap-around navigation — ↑ on the first row jumps to the last entry,\n // ↓ on the last row comes back to the first. Same pattern the model\n // picker / settings modal use; keeps long lists navigable without a\n // separate \"home / end\" shortcut.\n const moveCursor = useCallback(\n (delta: number) =>\n setCursorRaw((prev) => {\n if (catalog.length === 0)\n return prev\n return (((prev + delta) % catalog.length) + catalog.length) % catalog.length\n }),\n [catalog.length],\n )\n\n const safeCursor = Math.min(cursor, Math.max(0, catalog.length - 1))\n\n useKeyboard((key) => {\n if (key.name === 'up' || (key.ctrl && key.name === 'p')) {\n moveCursor(-1)\n }\n else if (key.name === 'down' || (key.ctrl && key.name === 'n')) {\n moveCursor(1)\n }\n else if (key.name === 'return' || key.name === 'space') {\n const entry = catalog[safeCursor]\n if (entry)\n toggle(keyOf(entry))\n }\n else if (key.ctrl && key.name === 'r' && onRefresh) {\n void onRefresh()\n }\n })\n\n if (catalog.length === 0) {\n return (\n <Modal title={title}>\n {preamble}\n {emptyState}\n <text fg={COLOR.mute}>\n {onRefresh && (\n <span>\n <span fg={COLOR.warn}>ctrl+R</span>\n {' refresh · '}\n </span>\n )}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n }\n\n return (\n <Modal title={` ${title} · ${enabledSet.size} / ${catalog.length} enabled `}>\n {preamble}\n <box style={{ flexDirection: 'column' }}>\n {catalog.map((entry, i) => {\n const focused = i === safeCursor\n const name = keyOf(entry)\n const enabled = enabledSet.has(name)\n return (\n <text key={name} fg={focused ? COLOR.brand : COLOR.dim}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={enabled ? COLOR.accent : COLOR.mute}>{enabled ? '[✓] ' : '[ ] '}</span>\n <span fg={focused ? COLOR.brand : COLOR.dim}>{name}</span>\n {renderDetail && (\n <span fg={COLOR.mute}>\n {' '}\n {renderDetail(entry)}\n </span>\n )}\n </text>\n )\n })}\n </box>\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' toggle · '}\n {onRefresh && (\n <span>\n <span fg={COLOR.warn}>ctrl+R</span>\n {' refresh · '}\n </span>\n )}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { SkillConfig } from '../skills'\nimport { useColors } from '../chat/theme-context'\nimport { ToggleListModal } from './toggle-list-modal'\n\n/**\n * List + toggle modal for discovered skills. State machine + keyboard +\n * row geometry live in `<ToggleListModal>`; this file just supplies the\n * skill-specific column (description) and the empty-state hint.\n *\n * Pass `onRefresh` to expose `ctrl+R` (force re-scan) on the list. The\n * standalone modal is for embedders — the in-app flow goes through\n * `<SettingsModal>` which exposes the same affordance via its own\n * `SettingsActions.onRefreshSkills` plumbing.\n */\nexport function SkillsSettingsModal({\n catalog,\n onRefresh,\n}: {\n catalog: readonly SkillConfig[]\n onRefresh?: () => Promise<void> | void\n}) {\n const COLOR = useColors()\n return (\n <ToggleListModal<SkillConfig>\n catalog={catalog}\n keyOf={s => s.name}\n settingKey=\"enabledSkills\"\n title=\"skills\"\n renderDetail={skill => skill.description}\n onRefresh={onRefresh}\n emptyState={(\n <>\n <text fg={COLOR.dim}>No skills discovered.</text>\n <text fg={COLOR.mute}>\n Drop a\n <span fg={COLOR.model}>{' SKILL.md '}</span>\n into\n <span fg={COLOR.model}>{' .zidane/skills/<name>/ '}</span>\n or\n <span fg={COLOR.model}>{' .agents/skills/<name>/ '}</span>\n (project or\n <span fg={COLOR.model}>{' ~/'}</span>\n ).\n </text>\n </>\n )}\n />\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ChatOptions } from '../chat/config'\nimport { createCliRenderer } from '@opentui/core'\nimport { createRoot } from '@opentui/react'\nimport { bootTick } from '../chat/boot-profiler'\nimport { resolveConfig } from '../chat/config'\nimport { clampFps, DEFAULT_SETTINGS } from '../chat/settings-context'\nimport { errorMessage } from '../errors'\nimport { createTuiStore } from '../session/sqlite'\nimport { App } from './app'\nimport { initTreeSitterWorker, registerTreeSitterParsers } from './tree-sitter'\n\n// ---------------------------------------------------------------------------\n// Public API\n//\n// `runTui(options)` is the one-shot launcher. For consumers who want to embed\n// the App inside a renderer they already control, the OpenTUI-specific\n// building blocks below are also exported individually.\n//\n// The renderer-agnostic engine (auth, credentials, providers, safe-mode,\n// store, streaming, settings, theme palette, formatters, markdown healer,\n// `resolveConfig`, …) lives in `zidane/chat`. Import from there directly if\n// you're building a non-TUI shell.\n// ---------------------------------------------------------------------------\n\n/**\n * Tracks whether `runTui` has been invoked in this process. `createCliRenderer`\n * installs signal handlers + hijacks stdin; calling it a second time produces\n * undefined behavior. We bail loudly instead.\n */\nlet runTuiInvoked = false\n\n/**\n * Boot a full chat TUI with sensible defaults. **Does not return** under\n * normal use — it terminates the host process via `process.exit(0)` once\n * the user dismisses the renderer (Ctrl+C / Esc on the auth screen / a\n * non-zero exit code is mapped via the `catch` path below).\n *\n * **One-shot:** this function may only be invoked once per process. The\n * underlying OpenTUI renderer wires up global terminal state on init.\n *\n * **Why it exits the process:** after the renderer's `destroy()` restores\n * the terminal, React's reconciler and OpenTUI's internal listeners can\n * keep the Node/Bun event loop open indefinitely — the script appears to\n * hang in `bun run`, and under `bun --watch run` the watcher waits forever\n * for the child to exit. Forcing a clean exit here is the contract that\n * makes `runTui` a true one-shot launcher.\n *\n * Hosts that need post-renderer cleanup should mount `<App config={...} />`\n * against their own `createCliRenderer()` instead of calling `runTui()`.\n *\n * Env-var overrides (handy when launching from CI or restricted shells):\n * - `ZIDANE_STORAGE_DIR` — sets `storageDir`\n * - `ZIDANE_PREFIX` — sets `prefix`\n * - `ZIDANE_DEBUG` — enables `gatherStats` on the renderer and dumps the\n * final fps / frametime distribution to stderr on exit. Useful when\n * tuning the renderer fps setting or comparing terminal emulators.\n *\n * Renderer fps lives in Settings (`targetFps`, cycle `30 / 60 / 120`);\n * the on-disk value seeds the renderer at boot and `AppShell` mirrors\n * subsequent flips onto the live renderer.\n *\n * Hosts building on top of `zidane/tui` typically want their own env vars\n * (e.g. `MYAPP_STORAGE_DIR`); read them in your launch script and forward\n * to `runTui({ storageDir, prefix })`.\n *\n * ```ts\n * import { BUILTIN_AGENTS, BUILTIN_PROVIDERS } from 'zidane/chat'\n * import { runTui } from 'zidane/tui'\n * import { createRemoteStore } from 'zidane/session' // for the `store` option\n *\n * await runTui() // ~/.zidane/sessions.db + state.json\n * await runTui({ prefix: '.myapp' }) // ~/.myapp/...\n * await runTui({ storageDir: '/data', prefix: 'myapp' })\n * await runTui({ providers: { ...BUILTIN_PROVIDERS, mine: myDescriptor } })\n * await runTui({ store: createRemoteStore({ url: '…' }) })\n * await runTui({ agents: { ...BUILTIN_AGENTS, debug: myDebugProfile } })\n * ```\n */\nexport async function runTui(options: Partial<ChatOptions> = {}): Promise<never> {\n if (runTuiInvoked) {\n throw new Error(\n 'runTui() can only be invoked once per process. '\n + 'Compose `<App config={resolveConfig(...)} />` against your own renderer '\n + 'if you need to run multiple TUIs in the same lifetime.',\n )\n }\n runTuiInvoked = true\n bootTick('runTui:enter')\n\n // Register extra Tree-sitter parsers (python, bash, json, rust, go,\n // yaml, html, css, haskell, c, cpp, csharp, elixir, sql) so fenced\n // code blocks in those languages get syntax highlighting. The grammar\n // `.wasm` files are downloaded lazily on first use and cached under\n // OpenTUI's data path. Parser *registration* is sync + cheap and runs\n // on the critical boot path; the heavier *worker* spin-up below is\n // fired off after the first paint — see post-mount block.\n registerTreeSitterParsers()\n bootTick('runTui:after-registerTreeSitterParsers')\n\n // `resolveConfig` wires `ZIDANE_CREDENTIALS_PATH` and applies stored API\n // keys into `process.env` internally — both happen before any provider\n // factory is constructed, so the wizard can render even on fresh installs\n // where the harness providers would otherwise throw \"No API key found\".\n //\n // `store` is injected as a factory so `zidane/chat` stays free of\n // `bun:sqlite`. Callers can override by passing their own `store` in\n // `options`; the explicit fallback keeps `runTui()` usable with zero args\n // while letting hosts swap in their own adapter.\n const config = resolveConfig({\n ...options,\n store: options.store ?? (paths => createTuiStore(paths.db)),\n })\n bootTick('runTui:after-resolveConfig')\n\n // Promise + explicit resolver: avoid the `let done!: ...` non-null trick.\n let done: () => void = () => {}\n const exited = new Promise<void>((resolve) => { done = resolve })\n\n // Seed the renderer's fps from the persisted setting (or its default\n // when state.json predates the field). `AppShell` keeps the live\n // renderer in sync with subsequent flips via the `settings.targetFps`\n // effect — boot value just avoids one flash of 30fps on the way in.\n const bootFps = clampFps(config.initialSettings.targetFps ?? DEFAULT_SETTINGS.targetFps)\n const gatherStats = !!process.env.ZIDANE_DEBUG\n\n const renderer = await createCliRenderer({\n exitOnCtrlC: true,\n onDestroy: () => done(),\n // OpenTUI debounces resize events by 100ms by default, which freezes the\n // current frame while the user drags the terminal corner and produces the\n // visible \"snap\" once the drag stops. Setting this to 0 re-lays out on\n // every SIGWINCH — the OS already caps the rate, so we just track it.\n debounceDelay: 0,\n // OpenTUI's stock target is 30fps (`_targetFps = 30` in its `CliRenderer`\n // ctor). We pin both knobs to the same value — uncapping `maxFps` past\n // the target only burns CPU on terminals that paint at the target rate.\n targetFps: bootFps,\n maxFps: bootFps,\n gatherStats,\n })\n bootTick('runTui:after-createCliRenderer')\n\n createRoot(renderer).render(<App config={config} />)\n bootTick('runTui:after-mount')\n\n // Spin up the Tree-sitter worker in the background. Highlighting on\n // the very first fence is gated by OpenTUI's `highlightOnce()`, which\n // self-initialises the worker if it isn't up yet — moving the boot\n // off the critical path costs ~zero correctness and saves the longest\n // single step (~40–100ms) in `runTui()`.\n void initTreeSitterWorker().then(\n () => bootTick('runTui:after-treeSitterWorker'),\n (err) => {\n process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${errorMessage(err)}\\n`)\n },\n )\n\n await exited\n\n // Dump fps stats AFTER teardown so the numbers cover the full session.\n // The JS-side counters survive `destroy()` — only the native bridge +\n // terminal hijack are gone by this point. Wrapped defensively so a\n // future shape change in `getStats()` can't keep the process pinned.\n if (gatherStats) {\n try {\n const s = renderer.getStats()\n process.stderr.write(\n `[zidane/tui] render stats: fps=${s.fps.toFixed(1)} `\n + `avgFrame=${s.averageFrameTime.toFixed(2)}ms `\n + `min=${s.minFrameTime.toFixed(2)}ms max=${s.maxFrameTime.toFixed(2)}ms `\n + `frames=${s.frameCount}\\n`,\n )\n }\n catch (err) {\n process.stderr.write(`[zidane/tui] render stats unavailable: ${errorMessage(err)}\\n`)\n }\n }\n\n // Force a clean exit. See JSDoc above for rationale — without this the\n // process hangs after the renderer's `destroy()` returns, which manifests\n // as a stuck `bun --watch` parent and a terminal that needs a second Ctrl+C\n // to actually surrender control.\n process.exit(0)\n}\n\n// ---------------------------------------------------------------------------\n// TUI-specific composition exports.\n//\n// Renderer-agnostic engine (auth, store, safe-mode, settings, providers,\n// streaming hooks, theme palette, …) is in `zidane/chat`. Don't re-export it\n// from here — keep `zidane/tui` focused on OpenTUI presentation primitives so\n// non-TUI consumers (a GUI, an SDK, a CI script) never pull OpenTUI in.\n// ---------------------------------------------------------------------------\n\n// Back-compat re-exports from `zidane/chat` so existing `zidane/tui`\n// consumers keep working after the renderer-agnostic helpers (chip\n// splitter, accent resolver, transcript visibility/spacing, tool\n// formatters, hint primitives) moved into the chat layer. New code\n// should import these directly from `zidane/chat` to keep a GUI shell\n// off OpenTUI.\n//\n// @deprecated These re-exports will be removed in the next minor — switch\n// the imports below over to `zidane/chat` (or `zidane/chat/<module>` for\n// the tree-shaken entry points).\n\n/** @deprecated Import from `zidane/chat`. */\nexport { accentColor } from '../chat/agents'\n/** @deprecated Import `Hint` / `hintsLength` from `zidane/chat`. */\nexport { clipHintsToWidth, type Hint, hintsLength, truncateTrailing } from '../chat/hints'\n/** @deprecated Import from `zidane/chat`. */\nexport { type MarkdownSegment, splitMarkdownCodeBlocks } from '../chat/markdown-segments'\n/** @deprecated Import from `zidane/chat`. */\nexport { type PromptSegment, type PromptSegmentRef, splitPromptSegments } from '../chat/prompt-segments'\n/** @deprecated Import from `zidane/chat`. */\nexport { isEditErrorResult, isTurnHighlighted, isVisible, marginTopFor, selectableTurnIds, turnSelectionOwnership } from '../chat/store'\n/** @deprecated Import from `zidane/chat`. */\nexport {\n displayNameFor,\n formatToolCall,\n TOOL_DISPLAY,\n type ToolDisplayMeta,\n type ToolFormatLine,\n} from '../chat/tool-formatters'\n/** @deprecated Import from `zidane/chat`. */\nexport { computeTurnAnchors, type TranscriptItem } from '../chat/transcript-anchors'\nexport { AgentPickerModal } from './agent-picker'\nexport { App } from './app'\nexport { CompletionPopup } from './completion-popup'\nexport {\n type ContextUsage,\n Footer,\n type MetaSegment,\n onInputSubmit,\n renderHintSpans,\n Spinner,\n StatusSpinner,\n TitleOverlay,\n Transcript,\n} from './components'\nexport { EffortPickerModal } from './effort-picker'\nexport { InteractionBlock } from './interaction-block'\nexport { McpsSettingsModal } from './mcps-settings'\nexport { Modal, type ModalProps, ModalRoot, useModal, useModalAwareFocus } from './modal'\nexport { ModelPickerModal, type PickedModel } from './model-picker'\nexport { AuthScreen, ChatScreen, type QueuedPreview, type QueueShortcuts, SessionsScreen } from './screens'\nexport {\n type SessionCompactResult,\n SessionDetailsModal,\n type SessionDetailsModalActions,\n type SessionExportResult,\n} from './session-details-modal'\nexport { type SettingsActions, SettingsModal } from './settings-modal'\nexport { SkillsSettingsModal } from './skills-settings'\nexport { buildMdStyle, useMdStyle } from './theme'\nexport { ToggleListModal } from './toggle-list-modal'\nexport { TurnDetailsModal, type TurnDetailsModalActions } from './turn-details-modal'\n"],"mappings":"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"}