zidane 5.0.1 → 5.0.3
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/agent-JhicgLOV.d.ts.map +1 -1
- package/dist/chat.js +1 -1
- package/dist/index.js +2 -2
- package/dist/{messages-z5Pq20p7.js → messages-xaYMMFlb.js} +13 -10
- package/dist/{messages-z5Pq20p7.js.map → messages-xaYMMFlb.js.map} +1 -1
- package/dist/{providers-CCDvIXGJ.js → providers-BCbdv99U.js} +2 -2
- package/dist/{providers-CCDvIXGJ.js.map → providers-BCbdv99U.js.map} +1 -1
- package/dist/providers.js +2 -2
- package/dist/session.js +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +18 -18
- package/dist/tui.js.map +1 -1
- package/dist/{turn-operations-5aQu4dJg.js → turn-operations-CHS2Prne.js} +2 -2
- package/dist/{turn-operations-5aQu4dJg.js.map → turn-operations-CHS2Prne.js.map} +1 -1
- package/package.json +1 -1
package/dist/tui.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui.js","names":["VISIBLE_ROW_CAP","EmptyState","EmptyState","ActionRow","ActionRow"],"sources":["../src/tui/modal.tsx","../src/tui/agent-picker.tsx","../src/tui/theme.ts","../src/tui/components.tsx","../src/tui/toggle-list-modal.tsx","../src/tui/mcps-settings.tsx","../src/tui/model-picker.tsx","../src/tui/completion-popup.tsx","../src/tui/screens.tsx","../src/tui/clipboard.ts","../src/tui/session-details-modal.tsx","../src/tui/settings-modal.tsx","../src/tui/skills-settings.tsx","../src/tui/turn-details-modal.tsx","../src/tui/app.tsx","../src/tui/tree-sitter.ts","../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 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\n const api = useMemo<ModalApi>(() => ({\n open: node => setActive(node),\n close: () => setActive(null),\n get isOpen() { return active !== null },\n }), [active])\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 /** 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 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 return (\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","/** @jsxImportSource @opentui/react */\nimport type { AgentAccent, 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/**\n * Resolve a profile's `accent` token to a concrete theme color via the\n * caller's color palette. Exposed for the Footer badge so all surfaces\n * stay in sync with the picker's row tinting.\n */\nexport function accentColor(\n accent: AgentAccent | undefined,\n COLOR: { brand: string, accent: string, warn: string, model: string },\n): string {\n switch (accent) {\n case 'brand': return COLOR.brand\n case 'warn': return COLOR.warn\n case 'model': return COLOR.model\n case 'accent':\n default:\n return COLOR.accent\n }\n}\n\n/** Re-export so consumers don't need to import the type from `zidane/chat` separately. */\nexport type { AgentProfile }\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\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 */\nexport function buildMdStyle(theme: Theme): SyntaxStyle {\n interface Style {\n fg?: RGBA\n bg?: RGBA\n bold?: boolean\n italic?: boolean\n underline?: boolean\n dim?: boolean\n }\n const styles: Record<string, Style> = {}\n for (const [token, style] of Object.entries(theme.syntax)) {\n const out: Style = {}\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 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\nconst MdStyleContext = createContext<SyntaxStyle | null>(null)\n\nexport function MdStyleProvider({ children }: { children: ReactNode }) {\n const theme = useTheme()\n const style = useMemo(() => buildMdStyle(theme), [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: style }, 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 * 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(): SyntaxStyle {\n const style = useContext(MdStyleContext)\n if (!style)\n throw new Error('useMdStyle must be used inside <MdStyleProvider>')\n return style\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 { ScrollBoxRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type { ThemeColors } from '../chat/theme'\nimport type { Settings, StreamEvent } from '../chat/types'\nimport { useTerminalDimensions } from '@opentui/react'\nimport { memo, useEffect, useMemo, useRef, useState } from 'react'\nimport { fmtTokens } from '../chat/format'\nimport { splitPromptSegments } from '../chat/prompt-segments'\nimport { resolveChipColor } from '../chat/theme'\nimport { useColors, useSurfaces } from '../chat/theme-context'\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 }: {\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 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} />\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 Hint {\n key: string\n label: string\n /** Optional override for the key color. Defaults to `COLOR.warn`. */\n keyColor?: string\n /** Optional override for the label color. Defaults to `COLOR.dim`. */\n labelColor?: string\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}: {\n hints: Hint[]\n context: ContextUsage | null\n}) {\n const { width } = useTerminalDimensions()\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 cW = context ? contextIndicatorLength(context) : 0\n\n const oneRowFits = hW + (cW > 0 ? cW + 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 <box style={{ flexGrow: 1 }} />\n {context && <ContextIndicator context={context} />}\n </box>\n )\n }\n\n return (\n <box style={{ flexDirection: 'column', paddingLeft: 1, paddingRight: 1 }}>\n <box style={{ flexDirection: 'row', height: 1 }}>\n <HintsText hints={hints} />\n </box>\n {context && (\n <box style={{ flexDirection: 'row', height: 1 }}>\n <box style={{ flexGrow: 1 }} />\n <ContextIndicator context={context} />\n </box>\n )}\n </box>\n )\n}\n\nfunction HintsText({ hints }: { hints: 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 <span fg={h.labelColor ?? COLOR.dim}>{` ${h.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 <text fg={COLOR.dim}>\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 </text>\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 * Colored title for a full-screen bordered surface. The `title` slot\n * rides `titleColor` (defaults to `COLOR.brand` — the theme's primary\n * anchor) on the LEFT of the top border; the optional `meta` slot\n * 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}: {\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 const COLOR = useColors()\n const { width: termWidth } = useTerminalDimensions()\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 const metaLen = metaSegmentsLength(meta)\n const showMeta = meta != null && metaLen > 0\n && title.length + 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\n const visibleTitle = titleBudget <= 0 ? '' : truncateTrailing(title, titleBudget)\n\n return (\n <>\n {visibleTitle && (\n <text style={{ position: 'absolute', top: 0, left: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={fg}>{visibleTitle}</span>\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\n/**\n * Truncate `text` to at most `max` characters, replacing the trailing\n * overflow with `…`. Edge cases:\n * - `max <= 0` → empty string (no room to render at all).\n * - `max === 1` → just the ellipsis glyph.\n * - `text.length <= max` → unchanged.\n *\n * Trailing-style truncation matches the natural read order of titles:\n * the prefix carries enough signal to identify the surface.\n *\n * Exported for unit-tests; consumers should normally lean on\n * {@link TitleOverlay} instead of calling this directly.\n */\nexport function truncateTrailing(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\n// ---------------------------------------------------------------------------\n// Plain-text width estimates for the footer's responsive tiering.\n//\n// We approximate with `.length` instead of `stringWidth` because every glyph\n// involved (hint keys, provider names, model ids, token counts) is either\n// ASCII or a 1-cell symbol. The estimates only need to be precise enough to\n// pick a layout tier — being off by a column at the breakpoint is harmless.\n// ---------------------------------------------------------------------------\n\n/**\n * Plain-text width estimate for a list of {@link Hint}s rendered via\n * `renderHintSpans` — `<key> <label> · <key> <label> · …`. Exported so\n * the prompt-box overlay (in `screens.tsx`) can run the same responsive\n * math as the bottom-bar footer when deciding whether trigger hints\n * fit. Pure / total.\n */\nexport function hintsLength(hints: readonly Hint[]): number {\n if (hints.length === 0)\n return 0\n // \" · \" between hints, \"<key> <label>\" per hint.\n return hints.reduce(\n (sum, h, i) => sum + h.key.length + 1 + h.label.length + (i > 0 ? 3 : 0),\n 0,\n )\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// ---------------------------------------------------------------------------\n// Spinner — animated braille used while a run is streaming.\n// ---------------------------------------------------------------------------\n\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']\nconst SPINNER_INTERVAL_MS = 80\n\nexport function Spinner({ label }: { label: string }) {\n const [frame, setFrame] = useState(0)\n const COLOR = useColors()\n\n useEffect(() => {\n const id = setInterval(() => setFrame(f => (f + 1) % SPINNER_FRAMES.length), SPINNER_INTERVAL_MS)\n return () => clearInterval(id)\n }, [])\n\n return (\n <text fg={COLOR.warn}>\n {SPINNER_FRAMES[frame]}\n <span fg={COLOR.dim}>{` ${label}`}</span>\n </text>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Transcript — scrollbox with sticky-bottom and structured event rendering.\n// ---------------------------------------------------------------------------\n\n/**\n * Minimum scrollbar thumb size, in half-block units (OpenTUI's\n * `SliderRenderable` renders the vertical thumb at 2 half-blocks per\n * character cell). `8` half-blocks = 4 character cells — always large\n * enough to read + grab with the mouse, never so large that it\n * dominates the track on short transcripts.\n */\nconst MIN_THUMB_HALF_BLOCKS = 8\n\nexport function Transcript({\n events,\n settings,\n selectedTurnId = null,\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 const items = useMemo(() => partitionTranscript(events, settings), [events, settings])\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Enforce a minimum scrollbar thumb size. OpenTUI's `SliderRenderable`\n // computes the thumb as `floor(2 * height * viewportSize / contentSize)`\n // and clamps the result to a one-half-block floor — meaning that a\n // long transcript (1000+ events) produces a thumb of literally one\n // half-character cell, which is visible but ungrabbable. There's no\n // public `minThumbSize` knob today, so we monkey-patch the slider's\n // `getVirtualThumbSize` to raise the floor to `MIN_THUMB_HALF_BLOCKS`\n // (4 character cells). Capping at `virtualTrackSize` keeps very short\n // transcripts unaffected — their natural thumb already fills the\n // track. Defensive: bail when OpenTUI's internals don't match (e.g.\n // a future version restructures or renames `getVirtualThumbSize`).\n useEffect(() => {\n const scrollbox = scrollboxRef.current\n if (!scrollbox)\n return\n // `getVirtualThumbSize` is declared `private` on OpenTUI's\n // `SliderRenderable` so TypeScript refuses a direct structural cast;\n // a two-step `unknown → { … }` widening keeps the type-check honest\n // while keeping the runtime monkey-patch focused on the one method\n // we touch.\n const slider = scrollbox.verticalScrollBar?.slider as unknown as\n | { height: number, getVirtualThumbSize: () => number }\n | undefined\n if (!slider || typeof slider.getVirtualThumbSize !== 'function')\n return\n const original = slider.getVirtualThumbSize.bind(slider)\n slider.getVirtualThumbSize = function () {\n const upstream = original()\n const virtualTrackSize = slider.height * 2\n return Math.min(virtualTrackSize, Math.max(MIN_THUMB_HALF_BLOCKS, upstream))\n }\n return () => {\n slider.getVirtualThumbSize = original\n }\n }, [])\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 if (selectedTurnId === anchors.lastTurnId) {\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])\n\n if (items.length === 0)\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 >\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={selectedTurnId !== null && item.event.turnId === selectedTurnId}\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 </scrollbox>\n )\n}\n\n/**\n * Per-item anchor ids for auto-scroll. Walks `items` in render order and,\n * for each event, returns either:\n * - `'turn-anchor-<turnId>'` — the first event of this turn (the\n * scrollbox's target).\n * - `undefined` — later event of an already-tagged turn (or a synthetic\n * event with no `turnId`).\n *\n * `ids[i]` is a tuple per item: length 1 for plain events, length N for\n * subagent runs (one entry per inner event). `idByTurn` is the inverse\n * lookup used by the scroll effect. `lastTurnId` is the most-recently-\n * rendered turn — the scroll effect special-cases it to snap to bottom.\n *\n * Exported so the anchor-tagging matrix can be unit-tested without rendering.\n */\nexport function computeTurnAnchors(items: readonly TranscriptItem[]): {\n idByTurn: ReadonlyMap<string, string>\n ids: readonly (readonly (string | undefined)[])[]\n lastTurnId: string | undefined\n} {\n const idByTurn = new Map<string, string>()\n let lastTurnId: string | undefined\n const tag = (turnId: string | undefined): string | undefined => {\n if (!turnId)\n return undefined\n lastTurnId = turnId\n if (idByTurn.has(turnId))\n return undefined\n const id = `turn-anchor-${turnId}`\n idByTurn.set(turnId, id)\n return id\n }\n const ids: (string | undefined)[][] = []\n for (const item of items) {\n if (item.kind === 'event')\n ids.push([tag(item.event.turnId)])\n else\n ids.push(item.events.map(e => tag(e.turnId)))\n }\n return { idByTurn, ids, lastTurnId }\n}\n\n/**\n * Per-event visibility — filters honor user toggles and the\n * `hideSubagentOutput` setting. When subagent output is hidden:\n * - Child-agent events are filtered down to the `spawn-start` /\n * `spawn-end` markers so the user still sees \"🌱 working… 🌳 done\".\n * - The parent's `tool-result` for `spawn` is hidden too. Its body\n * duplicates `spawn-end`'s stats line *and* the parent's next markdown\n * turn (\"Here's what the sub-agent found: …\"). Showing it again\n * produced an extra `┃ [sub-agent child-1] Completed …` block that\n * the user just wanted gone.\n *\n * Exported so the visibility matrix can be unit-tested without rendering.\n */\nexport function isVisible(event: StreamEvent, settings: Settings): boolean {\n if (settings.hideSubagentOutput) {\n if (isChild(event))\n return event.kind === 'spawn-start' || event.kind === 'spawn-end'\n if (event.kind === 'tool-result' && event.tool === 'spawn')\n return false\n }\n switch (event.kind) {\n case 'thinking': return settings.showThinking\n case 'tool': return settings.showToolCalls\n case 'tool-result': return settings.showToolResults\n default: return true\n }\n}\n\n/**\n * Output of `partitionTranscript`. Single events render as a normal\n * `EventLine`; child-event runs render as a bordered subagent box.\n *\n * Exported alongside `computeTurnAnchors` so the anchor-tagging matrix\n * can be unit-tested without going through React rendering.\n */\nexport type TranscriptItem\n = | { kind: 'event', event: StreamEvent, previous?: StreamEvent }\n | { kind: 'child-run', events: StreamEvent[], previous?: StreamEvent }\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 return (\n <box\n title={title}\n style={{\n border: true,\n borderColor: COLOR.mute,\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 />\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/**\n * Default top-margin per kind. Spacing intent:\n * - `info` / `markdown` / `tool` / `error` / `spawn-start` open a new block\n * so they each get one row of breathing room above.\n * - `thinking` / `tool-result` / `spawn-end` continue the previous block\n * and stay flush.\n *\n * Context-aware overrides live in `marginTopFor` — e.g. consecutive tool\n * round-trips collapse to a tight list regardless of whether outputs are shown.\n */\nconst MARGIN_TOP: Record<StreamEvent['kind'], number> = {\n 'separator': 0,\n 'user-prompt': 1,\n 'info': 1,\n 'thinking': 0,\n 'tool': 1,\n 'tool-result': 0,\n 'error': 1,\n 'markdown': 1,\n 'spawn-start': 1,\n 'spawn-end': 0,\n}\n\nconst TOOL_KINDS: ReadonlySet<StreamEvent['kind']> = new Set(['tool', 'tool-result'])\n\n/**\n * Resolve the top margin for an event given the one rendered just before it.\n *\n * Context-aware rules:\n *\n * - A `tool` / `tool-result` event right after another `tool` / `tool-result`\n * collapses to a tight list — call→result pairs and back-to-back calls\n * read as one logical block.\n * - A parent-level event (`depth === 0`) right after a subagent event\n * (`depth > 0`) collapses too. The subagent's `🌳` end marker (and, in\n * show mode, the subagent box's bottom border) already provides the\n * separation; adding the event's default `marginTop` on top would\n * produce the visible \"line jump\" between a subagent's outcome and the\n * parent's follow-up. Either form of marker is enough — we don't want\n * both.\n *\n * Exported so the spacing matrix can be unit-tested without rendering.\n */\nexport function marginTopFor(event: StreamEvent, previous: StreamEvent | undefined): number {\n if (TOOL_KINDS.has(event.kind) && previous && TOOL_KINDS.has(previous.kind))\n return 0\n const eventDepth = event.depth ?? 0\n const previousDepth = previous?.depth ?? 0\n if (eventDepth === 0 && previousDepth > 0)\n return 0\n return MARGIN_TOP[event.kind] ?? 0\n}\n\nfunction EventLineImpl({ event, depthOffset = 0 }: {\n event: StreamEvent\n depthOffset?: number\n}) {\n const COLOR = useColors()\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} />\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 return (\n <box style={row}>\n <text fg={child ? COLOR.dim : COLOR.model}>\n <span fg={COLOR.mute}>↳ </span>\n {safeText}\n </text>\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 <MarkdownBlock text={event.text} streaming={event.streaming ?? false} dim={child} />\n </box>\n )\n case 'spawn-start':\n return (\n <box style={row}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>🌱 </span>\n <span fg={COLOR.dim}>{`[${event.childId ?? 'child'}] `}</span>\n <span fg={COLOR.dim}>{safeText}</span>\n </text>\n </box>\n )\n case 'spawn-end':\n // Use a 2-cell emoji (like the 🌱 sprout on spawn-start) so the `[…]`\n // label lands in the same column on every row of a subagent's life\n // cycle — the previous `✓` glyph is 1 cell wide and shifted the\n // label one column left of the start marker.\n return (\n <box style={row}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>🌳 </span>\n <span fg={COLOR.dim}>{`[${event.childId ?? 'child'}] `}</span>\n <span fg={COLOR.mute}>{safeText}</span>\n </text>\n </box>\n )\n default:\n return <text>{safeText}</text>\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}: {\n text: string\n refs?: readonly { start: number, end: number, providerId: string }[]\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 if (!refs || refs.length === 0) {\n return (\n <box style={boxStyle}>\n <text fg={COLOR.brand}>\n <span fg={COLOR.brand}>{USER_PROMPT_PREFIX}</span>\n {text}\n </text>\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} fg={COLOR.brand}>{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 </box>\n )\n}\n\n/**\n * Markdown block. Renders either live-streaming markdown (with `streaming`\n * on, while deltas are still appending) or finalized markdown (after\n * `turn:after`, or every entry on a reloaded transcript).\n *\n * `internalBlockMode` is the load-bearing knob for layout: the OpenTUI\n * default (`\"coalesced\"`) fuses adjacent top-level blocks into one render\n * block, which is the right tradeoff for finalized markdown — fewer flex\n * children, fewer layout passes, the parser already knows the final shape.\n * During streaming, that same coalescing makes earlier paragraphs visually\n * re-flow on every token, so we switch to `\"top-level\"` (each block its\n * own renderable, only the trailing one is unstable).\n *\n * `internalBlockMode` is set only at construction by OpenTUI — there's no\n * setter — so a `<MarkdownBlock>` keeps whichever mode it was born with.\n * Live blocks start `streaming=true` → top-level; reloaded blocks start\n * `streaming=false` → coalesced. Each variant stays optimal for its\n * lifecycle.\n *\n * Note: we don't pre-process unclosed delimiters. OpenTUI's markdown\n * parser already renders partial input reasonably during streaming (the\n * trailing block reflows as tokens close), and the simplicity is worth\n * accepting a brief literal `**` before the closer arrives. Persisted\n * reloads come from completed assistant turns whose markdown is closed.\n */\nfunction MarkdownBlock({ text, streaming, dim }: { text: string, streaming: boolean, dim: boolean }) {\n const COLOR = useColors()\n const mdStyle = useMdStyle()\n return (\n <markdown\n content={text}\n syntaxStyle={mdStyle}\n streaming={streaming}\n internalBlockMode={streaming ? 'top-level' : 'coalesced'}\n fg={dim ? COLOR.dim : undefined}\n />\n )\n}\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 const lines = text.split('\\n')\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","/** @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}: {\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 const COLOR = useColors()\n const { enabledSet, toggle } = useEnabledToggleSet<T>({ catalog, keyOf, settingKey })\n const [cursor, setCursorRaw] = useState(0)\n\n const setCursor = useCallback(\n (update: (c: number) => number) =>\n setCursorRaw(prev => Math.min(Math.max(0, update(prev)), Math.max(0, catalog.length - 1))),\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 setCursor(c => c - 1)\n }\n else if (key.name === 'down' || (key.ctrl && key.name === 'n')) {\n setCursor(c => c + 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 })\n\n if (catalog.length === 0) {\n return (\n <Modal title={title}>\n {emptyState}\n <text fg={COLOR.mute}>\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 <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 <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { DiscoveredMcp } from '../chat/mcps-discovery'\nimport { useColors } from '../chat/theme-context'\nimport { ToggleListModal } from './toggle-list-modal'\n\n/**\n * List + toggle modal for MCP servers discovered from `.{prefix}/mcps.json`\n * / `.agents/mcps.json` (project + user). State machine lives in\n * `<ToggleListModal>` (shared with the Skills picker); this file supplies\n * the transport/command detail column and the empty-state copy.\n *\n * Toggling does NOT restart the active agent — the change applies on the\n * next session activation (the app rebuilds the agent there), keeping\n * current runs stable.\n */\nexport function McpsSettingsModal({\n catalog,\n}: {\n catalog: readonly DiscoveredMcp[]\n}) {\n const COLOR = useColors()\n return (\n <ToggleListModal<DiscoveredMcp>\n catalog={catalog}\n keyOf={d => d.config.name}\n settingKey=\"enabledMcps\"\n title=\"mcp servers\"\n renderDetail={(entry) => {\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 emptyState={(\n <>\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 ). Array of\n <span fg={COLOR.model}>{' McpServerConfig '}</span>\n or\n <span fg={COLOR.model}>{' { \"mcpServers\": { ... } } '}</span>\n .\n </text>\n </>\n )}\n />\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ModelInfo } from '../chat/providers'\nimport { useMemo } from 'react'\nimport { fmtTokens } from '../chat/format'\nimport { useColors, useSelectStyle } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/** Cap the visible scroll window so a 30-model list doesn't push the modal off-screen. */\nconst VISIBLE_ROW_CAP = 12\n\n/**\n * Modal that lists the available models for the current provider and lets\n * the user pick one. Options come from the active `ProviderDescriptor` —\n * either its declared `models` list or, when absent, pi-ai's built-in\n * registry looked up via `piProviderId`.\n *\n * Each row shows: `● selected · name (ctx N · reasoning · vision)`.\n */\nexport function ModelPickerModal({\n models,\n currentModelId,\n onPick,\n}: {\n models: readonly ModelInfo[]\n currentModelId: string\n onPick: (modelId: string) => void\n}) {\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n // Hooks must always run in the same order — keep them above any conditional\n // return. The \"no models\" branch becomes JSX below.\n const initialIndex = useMemo(\n () => models.findIndex(m => m.id === currentModelId),\n [models, currentModelId],\n )\n\n const options = useMemo(\n () => models.map(m => ({\n name: `${m.id === currentModelId ? '● ' : ' '}${m.name ?? m.id}`,\n description: describeModel(m),\n value: m.id,\n })),\n [models, currentModelId],\n )\n\n if (models.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 previously-saved model isn't\n // available in the current registry.\n const safeIndex = currentMissing ? 0 : initialIndex\n\n return (\n <Modal title=\"select model\">\n {currentMissing && (\n <text fg={COLOR.warn}>\n {`Current model \"${currentModelId}\" is not in this registry — pick one below to switch.`}\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 model\">\n <text fg={COLOR.dim}>No models available for this provider.</text>\n <text fg={COLOR.mute}>\n Set\n <span fg={COLOR.model}> models </span>\n on the provider descriptor (or a\n <span fg={COLOR.model}> piProviderId </span>\n that pi-ai recognizes) to populate this list.\n </text>\n </Modal>\n )\n}\n\n/** \"ctx 200k · reasoning · vision\" — compact per-model description. */\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 */\nimport type { InputRenderable, KeyEvent, TextareaRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type { ProviderAuth } from '../chat/auth'\nimport type { CompletionProvider, CompletionReference } from '../chat/completion'\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 { Hint, MetaSegment } from './components'\nimport { defaultTextareaKeyBindings } 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 { runOAuthLogin, supportsOAuth } from '../chat/oauth'\nimport { suggestSafelistEntry } from '../chat/safe-mode'\nimport { useColors, useSelectStyle } from '../chat/theme-context'\nimport { CompletionPopup } from './completion-popup'\nimport { hintsLength, renderHintSpans, Spinner, TitleOverlay, Transcript } from './components'\nimport { useModalAwareFocus } from './modal'\nimport { useChipHighlights, useChipStyle } from './theme'\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 */\nfunction makeSubmitBindings(allowShiftReturnNewline: boolean) {\n const base = defaultTextareaKeyBindings.filter(b => b.name !== 'return')\n return allowShiftReturnNewline\n ? [...base, { name: 'return', action: 'submit' as const }, { name: 'return', shift: true, action: 'newline' as const }]\n : [...base, { name: 'return', action: 'submit' as const }]\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(err instanceof Error ? err.message : String(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\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 [url, setUrl] = useState<string | null>(null)\n const [status, setStatus] = useState('starting browser…')\n const COLOR = useColors()\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('waiting for browser callback…')\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 const message = err instanceof Error ? err.message : String(err)\n onError(message)\n }\n })()\n\n return () => { cancelled = true; ac.abort() }\n }, [descriptor, dataDir, onSuccess, onError])\n\n return (\n <WizardPanel title={`configure ${descriptor.label} — OAuth`}>\n <WizardEscHint />\n <Spinner label={status} />\n {url && (\n <box style={{ flexDirection: 'column', gap: 0 }}>\n <text fg={COLOR.dim}>If the browser didn't open, visit:</text>\n <text fg={COLOR.model}>{url}</text>\n </box>\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\n const rows = useMemo<readonly SessionRowItem[]>(() => [\n { kind: 'new', rowId: NEW_SESSION_ROW_ID, meta: null },\n ...sessions.map<SessionRowItem>(meta => ({ kind: 'session', rowId: meta.id, meta })),\n ], [sessions])\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 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 === 'home') {\n moveCursor(0)\n return\n }\n if (key.name === 'end') {\n moveCursor(rows.length - 1)\n return\n }\n if (key.name === 'return') {\n commitCurrent()\n }\n })\n\n // `meta` summarizes the catalog state — \"no sessions\" tells the user\n // why only the \"+ new\" row is visible; \"N sessions\" rides the dim\n // top-right slot once there's actual history. Dropping it on a narrow\n // terminal is handled by `TitleOverlay` automatically. The count is\n // `warn`-tinted so the volume of history reads at a glance, matching\n // the colored turn-count treatment on the chat screen's title bar.\n const titleMeta = useMemo<readonly MetaSegment[]>(() => {\n if (sessions.length === 0)\n return [{ text: 'no sessions yet', color: COLOR.mute }]\n return [\n { text: String(sessions.length), color: COLOR.warn },\n { text: ` session${sessions.length === 1 ? '' : 's'}` },\n ]\n }, [sessions.length, COLOR])\n\n // cwd header — left-truncates to the box's interior width minus\n // the `cwd ` prefix + 1 trailing cell of safety. Computed against\n // the live terminal width via `useTerminalDimensions()`. The full-\n // height bordered box eats 2 cells of horizontal padding (`padding:\n // 1` on each side) plus 2 for the border, so the inner content\n // width is `termWidth - 4`. We reserve 4 more cells for `cwd ` so\n // the path itself gets `termWidth - 8`.\n const { width: termWidth } = useTerminalDimensions()\n const cwdMaxWidth = Math.max(16, termWidth - 8)\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 cwd header — tells the user which project the list is\n filtered to (or that they're in the cross-project view).\n Own line, left-truncated, dimmed so it sits below the\n rows in the visual hierarchy. Always shown so users always\n know where they are; the `all projects` suffix only\n appears when the filter is off.\n */}\n {currentProjectRoot && (\n <box style={{ flexDirection: 'column', flexShrink: 0, marginBottom: 1 }}>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>cwd </span>\n <span fg={COLOR.dim}>{compactPath(currentProjectRoot, cwdMaxWidth)}</span>\n {showAllProjects && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.accent}>all projects</span>\n </>\n )}\n </text>\n </box>\n )}\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 </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\nexport function ChatScreen({\n events,\n busy,\n settings,\n onSubmit,\n session,\n pending,\n onApproval,\n completionProviders,\n onPopupOpenChange,\n selectedTurnId,\n promptTriggerHints,\n}: {\n events: StreamEvent[]\n busy: boolean\n settings: Settings\n /**\n * Submit handler — receives the raw prompt text and the parsed references\n * (skills, files, …) so the App can act on them (e.g. activate the\n * referenced skill before `agent.run()`).\n */\n onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[]) => 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 * 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 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 const showSessionShortcut = !!session && !busy && !pending\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 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 return segments\n }, [session, userMessageCount, COLOR, showSessionShortcut])\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 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 }}\n >\n <Transcript events={events} settings={settings} selectedTurnId={selectedTurnId ?? null} />\n </box>\n\n {/*\n Priority: pending approval wins over busy (a paused run still has\n `busy === true` while the gate awaits). The picker takes the prompt\n slot so the user can decide without losing context.\n */}\n {pending\n ? <ApprovalBlock request={pending} onPick={onApproval} />\n : busy\n ? <BusyBlock />\n : (\n <PromptBlock\n userPrompts={userPrompts}\n onSubmit={onSubmit}\n completionProviders={completionProviders}\n onPopupOpenChange={onPopupOpenChange}\n selectMode={selectedTurnId != null}\n triggerHints={promptTriggerHints}\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 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 + 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\nfunction BusyBlock() {\n const COLOR = useColors()\n return (\n <box\n style={{\n border: true,\n borderColor: COLOR.warn,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <Spinner label=\"streaming response — esc to abort\" />\n </box>\n )\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 = false,\n triggerHints,\n}: {\n userPrompts: string[]\n onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[]) => void\n completionProviders?: readonly CompletionProvider<unknown>[]\n onPopupOpenChange?: (open: boolean) => void\n /**\n * True while the user is in select-turn mode. Unfocuses the textarea so\n * up/down/return reach the parent's keyboard handler, and swaps the\n * overlay hints to the navigation set.\n */\n selectMode?: boolean\n /** Optional trigger hints appended to the right overlay when there's room. */\n triggerHints?: readonly Hint[]\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\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 /**\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 setContentLines(Math.max(MIN_CONTENT_LINES, ta.lineCount))\n }, [])\n\n const submit = useCallback(() => {\n const value = textareaRef.current?.plainText ?? ''\n if (!value.trim())\n return\n onSubmit(value, completion.references)\n textareaRef.current?.clear()\n historyRef.current = null\n setBufferState({ text: '', cursor: 0 })\n setContentLines(MIN_CONTENT_LINES)\n }, [onSubmit, completion.references])\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 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 const cursorRow = buffer.logicalCursor.row\n if (event.name === 'up' && cursorRow === 0) {\n cycleHistory(-1)\n event.preventDefault()\n }\n else if (event.name === 'down' && cursorRow === buffer.lineCount - 1) {\n cycleHistory(1)\n event.preventDefault()\n }\n }, [popupOpen, completion, commitCompletion, cycleHistory])\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 <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 placeholder={selectMode ? '— turn-select mode — press ⎋ to resume typing —' : 'Ask zidane…'}\n syntaxStyle={chipStyle}\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={submit}\n onContentChange={syncBuffer}\n onKeyDown={onKeyDown}\n />\n </box>\n <PromptHints selectMode={selectMode} triggerHints={triggerHints} />\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 <box style={{ position: 'absolute', bottom: '100%', left: 0, right: 0, flexDirection: 'column' }}>\n <CompletionPopup state={completion} />\n </box>\n )}\n </box>\n )\n}\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/** Prompt-box shortcuts in select-turn mode — only the selection actions are valid. */\nconst PROMPT_HINTS_SELECT: readonly Hint[] = [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\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 }: {\n selectMode: boolean\n triggerHints?: readonly Hint[]\n}) {\n const COLOR = useColors()\n const { width: termWidth } = useTerminalDimensions()\n const primary = selectMode ? PROMPT_HINTS_SELECT : PROMPT_HINTS_NORMAL\n const hints = useMemo<readonly Hint[]>(() => {\n if (selectMode || !triggerHints || triggerHints.length === 0)\n return primary\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 primaryLen = hintsLength(primary)\n // Combined length = primary + \" · \" join + triggers.\n const triggerLen = hintsLength(triggerHints) + 3\n if (primaryLen + triggerLen > budget)\n return primary\n return [...primary, ...triggerHints]\n }, [selectMode, primary, triggerHints, termWidth])\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","/**\n * Write text to the system clipboard via the terminal's OSC 52 escape\n * sequence — `\\x1b]52;c;<base64>\\x07`. Modern terminals (iTerm2, kitty,\n * alacritty, wezterm, recent xterm, recent macOS Terminal) honor this;\n * tmux/screen pass it through with the right config. Older or stripped\n * terminals silently drop it — we have no way to detect that in-band.\n *\n * Why OSC 52 over `pbcopy` / `xclip` / `clip.exe`: zero dependencies, no\n * shell-out, works equally over SSH (which is where copy/paste is most\n * painful — the user's clipboard, not the remote box's clipboard).\n *\n * Returns `true` on a successful write to stdout, `false` otherwise.\n * Callers should reflect the result in UX (toast / inline message) but\n * not treat `false` as a hard failure — it just means \"stdout is not a\n * TTY\" or the terminal swallowed the sequence.\n */\n\nimport { Buffer } from 'node:buffer'\n\nexport function writeToClipboard(text: string): boolean {\n if (typeof process === 'undefined' || !process.stdout?.write)\n return false\n // Reject non-TTY stdouts so `bun run app | tee log.txt` doesn't claim\n // success while the OSC 52 escape lands in the pipe as literal bytes.\n // The terminal needs to be on the receiving end of stdout for the\n // sequence to reach the OSC handler.\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","/** @jsxImportSource @opentui/react */\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 { useColors } from '../chat/theme-context'\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\nexport function SessionDetailsModal({\n session,\n title,\n isCurrent,\n actions,\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}) {\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 // Abort handle for the in-flight title generation. Captured in a ref 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 // `mountedRef` flips false on unmount so the async `handleGenerate`\n // doesn't push state into a tree React already tore down. The abort\n // covers the provider call; 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 }, [])\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' | '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 }\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(err instanceof Error ? err.message : String(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(err instanceof Error ? err.message : String(err))\n setTitleStatus('failed')\n }\n finally {\n if (generationAbortRef.current === ac)\n generationAbortRef.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. Both title generation and export run on the same lock.\n if (titleStatus === 'loading' || exportStatus === 'writing')\n return\n if (key.name === 'escape' && pending) {\n setPending(null)\n return\n }\n if (key.name === 'd') {\n if (pending === 'delete')\n commitDelete()\n else\n setPending('delete')\n return\n }\n if (key.name === 'c') {\n setPending(null)\n handleCopy()\n return\n }\n if (key.name === 'g' && hasGenerate) {\n setPending(null)\n void handleGenerate()\n return\n }\n if (key.name === 'e' && hasExport) {\n setPending(null)\n void handleExport('markdown')\n return\n }\n if (key.name === 'j' && hasExport) {\n setPending(null)\n void handleExport('json')\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={titleStatus === 'loading' || exportStatus === 'writing' || pending !== null}\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 />\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}: {\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}) {\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 (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}>g</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}>e</span>\n {' / '}\n <span fg={COLOR.warn}>j</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}>d</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 (copyStatus === 'copied') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>✓ session id copied</span>\n {' · '}\n <span fg={COLOR.warn}>d</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}>g</span>\n {' generate title · '}\n </>\n )}\n {hasExport && (\n <>\n <span fg={COLOR.warn}>e</span>\n /\n <span fg={COLOR.warn}>j</span>\n {' export md/json · '}\n </>\n )}\n <span fg={COLOR.warn}>d</span>\n {' delete · '}\n <span fg={COLOR.warn}>c</span>\n {' copy id · '}\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 { BooleanSettingKey } from '../chat/settings-context'\nimport type { Settings } from '../chat/types'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useMemo, useState } from 'react'\nimport { SETTINGS_CHOICES, SETTINGS_TOGGLES, useSettings } from '../chat/settings-context'\nimport { useColors } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n// ---------------------------------------------------------------------------\n// SettingsModal — vertical list. Up/Down navigates, Enter/Space activates the\n// focused row (toggles a flag, cycles a choice, or invokes an action). Esc\n// dismisses (handled by the parent <Modal>).\n//\n// Three row kinds:\n// - `toggle` — a boolean stored under `settings[key]` (see SETTINGS_TOGGLES)\n// - `choice` — a string stored under `settings[key]` cycling through a fixed\n// options list (see SETTINGS_CHOICES — e.g. the theme picker)\n// - `action` — a one-shot callback (e.g. \"Re-configure providers\")\n//\n// Hosts that mount the modal pass `actions` to wire in app-level actions\n// like re-opening the auth panel. Toggle and choice rows are static —\n// sourced from the corresponding tables in `chat/settings-context.tsx` so a\n// GUI settings panel can share the same labels.\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 SettingsItem = ToggleItem | ChoiceItem | ActionItem\n\nexport interface SettingsActions {\n /**\n * Re-open the auth screen so the user can switch providers or run the\n * wizard for a new/existing one. Wiring this callback adds a\n * \"Re-configure providers\" row to the modal.\n */\n onReauth?: () => void\n /**\n * Open the Skills list + toggle modal. Adds a \"Skills\" row when wired.\n */\n onOpenSkills?: () => void\n /**\n * Open the MCP servers list + toggle modal. Adds an \"MCP servers\" row\n * when wired.\n */\n onOpenMcps?: () => void\n}\n\nexport function SettingsModal({ actions }: { actions?: SettingsActions } = {}) {\n const { settings, toggle, setSetting } = useSettings()\n const [cursor, setCursorRaw] = useState(0)\n const COLOR = useColors()\n\n // Build the row list dynamically so the modal only shows action rows whose\n // callbacks are wired. Toggle/choice rows are always present.\n const items: readonly SettingsItem[] = useMemo(() => {\n const toggleItems: ToggleItem[] = SETTINGS_TOGGLES.map(t => ({ kind: 'toggle' as const, ...t }))\n const choiceItems: ChoiceItem[] = SETTINGS_CHOICES.map(c => ({ kind: 'choice' as const, ...c }))\n const actionItems: ActionItem[] = []\n if (actions?.onOpenSkills) {\n actionItems.push({\n kind: 'action',\n id: 'skills',\n label: 'Skills',\n description: 'discover + toggle slash-command skills',\n onPick: actions.onOpenSkills,\n })\n }\n if (actions?.onOpenMcps) {\n actionItems.push({\n kind: 'action',\n id: 'mcps',\n label: 'MCP servers',\n description: 'enable / disable discovered servers',\n onPick: actions.onOpenMcps,\n })\n }\n if (actions?.onReauth) {\n actionItems.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 [...toggleItems, ...choiceItems, ...actionItems]\n }, [actions])\n\n // The modal re-mounts on each open via `modal.open(<SettingsModal/>)`, but\n // if the host swaps `actions` while the modal is open (e.g. tears down the\n // re-auth wiring mid-session), the cursor might now point past the end of\n // the truncated row list. Clamp on read so we never index out of bounds.\n const safeCursor = Math.min(cursor, items.length - 1)\n const setCursor = useCallback(\n (update: (c: number) => number) =>\n setCursorRaw(prev => Math.min(Math.max(0, update(prev)), items.length - 1)),\n [items.length],\n )\n\n useKeyboard((key) => {\n if (key.name === 'up' || (key.ctrl && key.name === 'p')) {\n setCursor(c => c - 1)\n }\n else if (key.name === 'down' || (key.ctrl && key.name === 'n')) {\n setCursor(c => c + 1)\n }\n else if (key.name === 'return' || key.name === 'space') {\n const item = items[safeCursor]\n if (!item)\n return\n if (item.kind === 'toggle') {\n toggle(item.key)\n }\n else if (item.kind === 'choice') {\n // Cycle to the next option, wrapping. Single-option choice = no-op\n // (find returns the only entry; (idx+1)%1 === 0 → same value).\n const current = settings[item.key]\n const idx = item.options.findIndex(o => o.value === current)\n const next = item.options[(idx + 1) % item.options.length]\n if (next)\n setSetting(item.key, next.value as Settings[typeof item.key])\n }\n else {\n item.onPick()\n }\n }\n })\n\n // Index where the post-toggle block starts — separator divides transcript\n // filter toggles (top) from app-level settings (theme picker, auth, …).\n // Falls back to -1 when only toggles exist.\n const firstNonToggleIndex = items.findIndex(i => i.kind !== 'toggle')\n\n return (\n <Modal title=\"settings\">\n <box style={{ flexDirection: 'column' }}>\n {items.map((item, i) => (\n <box\n key={item.kind === 'action' ? item.id : item.key}\n style={{ flexDirection: 'column' }}\n >\n {/*\n Full-width separator between transcript-filter toggles and the\n app-level block (theme picker, authentication, …).\n `border={['top']}` draws only the top edge, which yoga stretches\n to the parent's content width — no need to measure the modal\n manually. Vertical margin gives the rule breathing room from\n the rows it separates.\n */}\n {i === firstNonToggleIndex && i > 0 && (\n <box\n style={{\n border: ['top'],\n borderColor: COLOR.mute,\n height: 1,\n marginTop: 1,\n marginBottom: 1,\n }}\n />\n )}\n {item.kind === 'toggle' && (\n <ToggleRow\n label={item.label}\n description={item.description}\n enabled={settings[item.key]}\n focused={i === safeCursor}\n />\n )}\n {item.kind === 'choice' && (\n <ChoiceRow\n label={item.label}\n description={item.description}\n value={\n item.options.find(o => o.value === settings[item.key])?.label\n ?? String(settings[item.key])\n }\n cyclable={item.options.length > 1}\n focused={i === safeCursor}\n />\n )}\n {item.kind === 'action' && (\n <ActionRow\n label={item.label}\n description={item.description}\n focused={i === safeCursor}\n />\n )}\n </box>\n ))}\n </box>\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' toggle/cycle/select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n\n/**\n * Toggle row — `▶` marker · checkbox · label · description.\n *\n * Rendered as one `<text>` so OpenTUI's word-wrap handles narrow terminals\n * automatically: on wide screens everything sits on one line; on narrow ones\n * the trailing description wraps under the label without breaking the row.\n */\nfunction ToggleRow({\n label,\n description,\n enabled,\n focused,\n}: {\n label: string\n description: string\n enabled: boolean\n focused: boolean\n}) {\n const COLOR = useColors()\n return (\n <text 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}>{label}</span>\n <span fg={COLOR.mute}>{` ${description}`}</span>\n </text>\n )\n}\n\n/**\n * Choice row — `▶` marker · label · `:` · current value · description.\n *\n * Cycles through `options` on enter/space. When only one option is\n * available (`cyclable=false`) the row still renders with the current\n * value but the enter handler is a no-op — we surface this via the absence\n * of the trailing `›` affordance so it visually reads as informational.\n */\nfunction ChoiceRow({\n label,\n description,\n value,\n cyclable,\n focused,\n}: {\n label: string\n description: string\n value: string\n cyclable: boolean\n focused: boolean\n}) {\n const COLOR = useColors()\n return (\n <text fg={focused ? COLOR.brand : COLOR.dim}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</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 <span fg={COLOR.mute}>{` ${description}`}</span>\n {focused && cyclable && <span fg={COLOR.brand}>{' ↻'}</span>}\n </text>\n )\n}\n\n/**\n * Action row — cursor marker · label · description · (focus-only) trailing arrow.\n *\n * The label sits in the same column as a toggle row's `[✓]` checkbox (right\n * after the 2-col cursor slot). The trailing `›` only renders when focused\n * so it reads as a \"this row runs\" affordance, not a static decoration on\n * every action.\n */\nfunction ActionRow({\n label,\n description,\n focused,\n}: {\n label: string\n description: string\n focused: boolean\n}) {\n const COLOR = useColors()\n return (\n <text fg={focused ? COLOR.brand : COLOR.dim}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={focused ? COLOR.brand : COLOR.accent}>{label}</span>\n <span fg={COLOR.mute}>{` ${description}`}</span>\n {focused && <span fg={COLOR.brand}>{' ›'}</span>}\n </text>\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 */\nexport function SkillsSettingsModal({\n catalog,\n}: {\n catalog: readonly SkillConfig[]\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 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 { SessionTurn } from '../types'\nimport { useKeyboard } from '@opentui/react'\nimport { useState } from 'react'\nimport { ageString, shortId } from '../chat/format'\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// - 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\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}\n\nexport function TurnDetailsModal({\n turn,\n index,\n total,\n actions,\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}) {\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\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\n useKeyboard((key) => {\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 (key.name === 'f') {\n setCopyStatus('idle')\n if (pending === 'fork')\n commitFork()\n else\n setPending('fork')\n return\n }\n if (key.name === 'd') {\n setCopyStatus('idle')\n if (pending === 'delete')\n commitDelete()\n else\n setPending('delete')\n return\n }\n if (key.name === 'c') {\n setPending(null)\n handleCopy()\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={`turn ${index} / ${total} · ${turn.role}`}\n bottomTitle={bottomTitle}\n maxHeight={MAX_MODAL_HEIGHT}\n disableEscape={pending !== null}\n >\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 <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>blocks </span>\n <span fg={COLOR.dim}>{summary}</span>\n </text>\n\n {/*\n Preview pane — bordered + scrollable so a long assistant reply\n doesn't spill over the modal's geometry. `flexGrow: 1` lets the\n scrollbox absorb the variable remaining space between the info\n header above and the action footer below.\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 <ActionRow\n pending={pending}\n copyStatus={copyStatus}\n canCopy={fullText.length > 0}\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}: {\n pending: 'fork' | 'delete' | null\n copyStatus: 'idle' | 'copied' | 'failed'\n canCopy: boolean\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}>f</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}>d</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}>f</span>\n {' fork · '}\n <span fg={COLOR.warn}>d</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 <span fg={COLOR.warn}>f</span>\n {' fork · '}\n <span fg={COLOR.warn}>d</span>\n {' delete · '}\n <span fg={canCopy ? COLOR.warn : COLOR.mute}>c</span>\n {canCopy ? ' copy · ' : ' (nothing to copy) · '}\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 { FileEntry } from '../chat/files-discovery'\nimport type { DiscoveredMcp } from '../chat/mcps-discovery'\nimport type { SessionExportFormat } from '../chat/session-export'\nimport type { Picked, Screen, SessionMeta, Settings, StreamEvent } from '../chat/types'\nimport type { Session, SessionData } from '../session'\nimport type { SkillConfig } from '../skills'\nimport type { SessionTurn } from '../types'\nimport type { ContextUsage, Hint } from './components'\nimport { useKeyboard, useRenderer } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { createAgent } from '../agent'\nimport { createFilesCompletionProvider } from '../chat/completion-files'\nimport { createSkillsCompletionProvider, uniqueSkillNamesFromReferences } from '../chat/completion-skills'\nimport { ConfigProvider, useConfig } from '../chat/config-context'\nimport { listProjectFiles } from '../chat/files-discovery'\nimport { generateSessionTitle } from '../chat/generate-title'\nimport { buildMcpServers, discoverProjectMcps } from '../chat/mcps-discovery'\nimport { findGitRoot } from '../chat/project-root'\nimport { getContextWindow } 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 { DEFAULT_SETTINGS, SettingsProvider, useSettings } from '../chat/settings-context'\nimport { buildSkillsConfig, defaultSkillScanPaths, discoverProjectSkills } from '../chat/skills-discovery'\nimport {\n deriveSessionTitle,\n eventsFromTurns,\n lastContextSizeFromTurns,\n listSessionMeta,\n selectableTurnIds,\n stripSpawnTokensLine,\n toolCallPreview,\n toolResultText,\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 { createSession, loadSession } from '../session'\nimport { formatTokenUsage } from '../stats'\nimport { accentColor, AgentPickerModal } from './agent-picker'\nimport { Footer } from './components'\nimport { McpsSettingsModal } from './mcps-settings'\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 { SkillsSettingsModal } from './skills-settings'\nimport { ChipStyleProvider, MdStyleProvider } from './theme'\nimport { TurnDetailsModal } from './turn-details-modal'\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 * 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 <ModalRoot>\n <AppShell />\n </ModalRoot>\n </SafeModeProvider>\n </ChipStyleProvider>\n </MdStyleProvider>\n </ThemeProvider>\n )\n}\n\nfunction AppShell() {\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 // `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\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 } = config\n const lastResumedSessionId = initialState.lastSessionId\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 // `process.cwd()` is the project key in `projects.json` (the\n // safelist). Captured once per AppShell mount with `useState` lazy\n // init — `useMemo([])` would technically allow React to recompute\n // under memory pressure, `useState` is a hard guarantee.\n const [projectDir] = useState(() => process.cwd())\n // Project root that scopes the SESSIONS list — git root walked up\n // from `cwd` if we're in a repo, else cwd itself. Two sessions\n // launched from `repo/` and `repo/subdir/foo` therefore land in the\n // same scope, which is what the user expects (\"show me sessions for\n // THIS project, not THIS directory\").\n //\n // Distinct from `projectDir` above (which still keys the safelist by\n // exact cwd — that's a different concept and intentionally not\n // git-walked).\n const [sessionProjectRoot] = useState(() => findGitRoot(process.cwd()) ?? process.cwd())\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 // Skills + MCPs discovery — runs once per `prefix + projectDir`.\n //\n // Skills: parsed `SkillConfig[]` from the project + user scan paths. The\n // catalog is read-only state; user-toggled allowlist lives in\n // `settings.enabledSkills` and is composed into the agent's\n // `SkillsConfig` at `buildAgent` time.\n //\n // MCPs: parsed `DiscoveredMcp[]` from `.{prefix}/mcps.json` /\n // `.agents/mcps.json`. Same shape — `settings.enabledMcps` filters the\n // discovered list before the agent receives `mcpServers`.\n //\n // Errors during discovery are swallowed (logged under ZIDANE_DEBUG) so a\n // single bad SKILL.md or mcps.json doesn't keep the user out of the TUI.\n const [skillsCatalog, setSkillsCatalog] = useState<readonly SkillConfig[]>([])\n const [mcpsCatalog, setMcpsCatalog] = useState<readonly DiscoveredMcp[]>([])\n const [filesCatalog, setFilesCatalog] = useState<readonly FileEntry[]>([])\n useEffect(() => {\n const ac = new AbortController()\n let cancelled = false\n void (async () => {\n try {\n const skills = await discoverProjectSkills({ cwd: projectDir, prefix: config.prefix })\n if (!cancelled)\n setSkillsCatalog(skills)\n }\n catch (err) {\n debugLog('discoverProjectSkills failed', err)\n }\n })()\n void (async () => {\n try {\n const files = await listProjectFiles({ cwd: projectDir, signal: ac.signal })\n if (!cancelled)\n setFilesCatalog(files)\n }\n catch (err) {\n debugLog('listProjectFiles failed', err)\n }\n })()\n try {\n setMcpsCatalog(discoverProjectMcps({ cwd: projectDir, prefix: config.prefix }))\n }\n catch (err) {\n debugLog('discoverProjectMcps failed', err)\n }\n return () => {\n cancelled = true\n ac.abort()\n }\n }, [projectDir, config.prefix])\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 const completionProviders = useMemo(\n () => [\n createSkillsCompletionProvider({\n getCatalog: () => skillsCatalogRef.current,\n getEnabled: () => enabledSkillsRef.current,\n }),\n createFilesCompletionProvider({\n getCatalog: () => filesCatalogRef.current,\n }),\n ] as const,\n [],\n )\n\n /**\n * Single source of truth for \"should this call execute?\". Returns true to\n * let the call through, false to refuse it. Handles 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\").\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 (tool: string, input: Record<string, unknown>): Promise<boolean> => {\n if (!safeModeEnabledRef.current)\n return true\n if (isOnSafelist(readSafelist(), tool, input))\n return true\n const decision = await requestApproval(tool, input)\n if (decision === 'deny')\n return false\n if (decision === 'accept-safelist') {\n const entry = suggestSafelistEntry(tool, input)\n addToSafelist(dataDir, projectDir, entry)\n safelistRef.current = null // force re-read on next gate\n }\n return true\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 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 /** Token count from the most recent assistant turn (caching-aware). */\n const [lastInputTokens, setLastInputTokens] = useState(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 const agentRef = useRef<Agent | null>(null)\n const sessionRef = useRef<Session | null>(null)\n\n const stream = useStreamBuffer(setEvents)\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 return { provider, model }\n }, [providerRegistry, initialState])\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 const agent = createAgent({\n ...profile.preset,\n skills: { ...skillsConfig, ...(profile.preset.skills ?? {}) },\n mcpServers: [...projectMcps, ...(profile.preset.mcpServers ?? [])],\n provider: descriptor.factory(),\n session,\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: …` and adapts.\n const applyGate = async (\n name: string,\n input: Record<string, unknown>,\n ctx: { block: boolean, reason: string },\n ): Promise<void> => {\n if (ctx.block) // already refused by a higher-priority gate (skills/budgets/dedup)\n return\n if (!(await gateDecision(name, input))) {\n ctx.block = true\n ctx.reason = 'User denied this tool call'\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 // 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', ({ name, input, turnId }) => {\n stream.appendImmediate({ kind: 'tool', text: toolCallPreview(name, input), tool: name, turnId })\n })\n agent.hooks.hook('tool:after', ({ name, result, turnId }) => {\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, turnId })\n })\n agent.hooks.hook('mcp:tool:after', ({ displayName, result, turnId }) => {\n stream.appendImmediate({ kind: 'tool-result', text: toolResultText(result), tool: displayName, turnId })\n })\n agent.hooks.hook('turn:after', ({ usage }) => {\n if (usage)\n setLastInputTokens(turnContextSize(usage))\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n })\n\n // Subagent streams ----------------------------------------------------\n agent.hooks.hook('spawn:before', ({ id, task, depth }) => {\n const taskPreview = task.length > 80 ? `${task.slice(0, 80)}…` : task\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', ({ name, input, childId, depth, turnId }) => {\n stream.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(name, input),\n tool: name,\n childId,\n depth,\n turnId,\n })\n })\n agent.hooks.hook('child:tool:after', ({ name, result, childId, depth, turnId }) => {\n stream.appendImmediate({\n kind: 'tool-result',\n text: toolResultText(result),\n tool: name,\n childId,\n depth,\n turnId,\n })\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 return agent\n }, [providerRegistry, stream, gateDecision, projectDir, config.prefix])\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: sessionProjectRoot }\n const list = await listSessionMeta(store, filter)\n setSessions(list)\n return list\n }, [store, settings.showAllProjects, sessionProjectRoot])\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 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 }, [stream, denyAll])\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: sessionProjectRoot, ...(id ? { id } : {}) })\n\n sessionRef.current = session\n agentRef.current = buildAgent(session, key)\n\n setEvents(eventsFromTurns(session.turns, session.runs))\n setLastInputTokens(lastContextSizeFromTurns(session.turns, 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 }, [teardown, buildAgent, store, stateStore, sessionProjectRoot])\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 if (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 === sessionProjectRoot)\n if (data && sessionMatchesProject) {\n await activateSession(lastResumedSessionId, resumeProvider.key)\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 else\n setScreen('sessions')\n })()\n return () => { cancelled = true }\n }, [activateSession, refreshSessions, resumeProvider, lastResumedSessionId, store, settings.showAllProjects, sessionProjectRoot])\n\n // -------------------------------------------------------------------------\n // Screen actions.\n // -------------------------------------------------------------------------\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 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])\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 any pending approvals so the run can unwind cleanly; otherwise\n // `agent.abort()` only flips the run's signal and our gate handlers\n // would still hang in `await requestApproval(...)`.\n denyAll()\n agentRef.current?.abort()\n }, [denyAll])\n\n const onPickModel = useCallback((modelId: string) => {\n setPicked((prev) => {\n if (!prev)\n return prev\n // Remember per-provider so the next launch resumes the right model.\n const prior = stateStore.load()\n stateStore.save({\n ...prior,\n lastModelByProvider: { ...prior.lastModelByProvider, [prev.provider.key]: modelId },\n })\n return { ...prev, model: modelId }\n })\n modal.close()\n }, [modal, stateStore])\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 const onSubmitPrompt = useCallback(async (prompt: string, references: readonly CompletionReference<unknown>[]) => {\n const agent = agentRef.current\n const session = sessionRef.current\n if (!agent || !session || !picked || !prompt.trim())\n return\n\n if (eventsLengthRef.current > 0)\n stream.appendImmediate({ kind: 'separator', text: '' })\n // Skill / file refs highlight chips in the echoed prompt. Both the\n // text and the offsets stay raw — the renderer prepends the `❯ `\n // chevron without touching ref spans.\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 stream.appendImmediate({\n kind: 'user-prompt',\n text: prompt,\n ...(refSpans.length > 0 ? { refs: refSpans } : {}),\n })\n setBusy(true)\n\n // Activate every skill referenced via slash-commands BEFORE the run\n // so the catalog injects the body into the system prompt on the\n // first turn. Idempotent — `activateSkill` is safe on already-active\n // skills, and auto-resolves the catalog on first call.\n //\n // The popup-driven path can't produce unknown-skill names (the\n // completion catalog gates which `/name` tokens become refs), and\n // skills are not disabled at runtime — so failure here is exotic\n // (e.g. `maxActive` cap). Log under ZIDANE_DEBUG; don't pollute the\n // transcript with errors the user can't act on.\n const skillNames = uniqueSkillNamesFromReferences(references)\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 await agent.run({ model: picked.model, prompt })\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 }\n catch (err) {\n stream.appendImmediate({ kind: 'error', text: err instanceof Error ? err.message : String(err) })\n }\n finally {\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n setBusy(false)\n }\n }, [picked, stream])\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 // Head of the safe-mode approval queue. `null` means \"nothing pending\"\n // and the chat screen shows the prompt input as usual. Defined here so the\n // keyboard handler and the footer hints both see it.\n const pendingApproval = queue[0] ?? null\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 // Settings → Skills / MCPs sub-modals. Both reuse `modal.open` so the\n // single overlay slot stays consistent (esc closes whichever is on top\n // by replacement, not by stacking).\n const onOpenSkillsSettings = useCallback(() => {\n modal.open(<SkillsSettingsModal catalog={skillsCatalog} />)\n }, [modal, skillsCatalog])\n const onOpenMcpsSettings = useCallback(() => {\n modal.open(<McpsSettingsModal catalog={mcpsCatalog} />)\n }, [modal, mcpsCatalog])\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 const turnIds = useMemo(() => selectableTurnIds(events), [events])\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 const nextIdx = Math.max(0, Math.min(turnIds.length - 1, idx + direction))\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 setLastInputTokens(lastContextSizeFromTurns(session.turns, 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 // -------------------------------------------------------------------------\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 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])\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 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 actions={{\n onDelete: onDeleteSession,\n onExport: onExportSession,\n ...(picked ? { onGenerateTitle } : {}),\n }}\n />,\n )\n }, [modal, store, currentSession, onDeleteSession, picked, onGenerateTitle, onExportSession])\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 }}\n />,\n )\n }, [modal, selectedTurnId, turnIds, onForkTurn, onDeleteTurn])\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+, doesn't open Settings mid-\n // selection — the user can exit first if they want a global action.\n return\n }\n if (key.ctrl && key.name === ',' && screen !== 'auth') {\n modal.open(<SettingsModal actions={{ onReauth, onOpenSkills: onOpenSkillsSettings, onOpenMcps: onOpenMcpsSettings }} />)\n return\n }\n // Ctrl+X — open the session details modal. Targets the active\n // session on the chat screen, or the focused row in the sessions\n // list. Gated on the chat side by `!busy && !pendingApproval` so\n // a live run isn't interrupted mid-stream by the modal stealing\n // focus + handlers. On the sessions screen the modal is read-mostly\n // (delete confirms first) so no run-state gate is needed there.\n if (key.ctrl && key.name === 'x') {\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 (key.ctrl && key.name === 'm' && screen === 'chat' && picked && !busy) {\n modal.open(\n <ModelPickerModal\n models={modelsFor(picked.provider.key)}\n currentModelId={picked.model}\n onPick={onPickModel}\n />,\n )\n return\n }\n // Ctrl+S — enter select-turn mode. Only on the chat screen, only when\n // idle (a streaming turn would have a moving `turnIds` tail under our\n // feet — disrupting the muscle memory of \"I just selected this turn\").\n // No-op when the session has no turns yet.\n if (key.ctrl && key.name === 's' && screen === 'chat' && !busy && !pendingApproval) {\n enterSelectMode()\n return\n }\n // Ctrl+A — agent profile picker. Only meaningful when more than one\n // profile is registered; hosts with a single-agent registry never see\n // a useful list, so we suppress the shortcut to avoid an empty modal.\n if (key.ctrl && key.name === 'a' && screen === 'chat' && hasMultipleAgents && !busy) {\n modal.open(\n <AgentPickerModal\n agents={agentRegistry}\n currentAgentId={pickedAgent.id}\n onPick={onPickAgent}\n />,\n )\n return\n }\n // Shift+Tab — quick-cycle through agent profiles without opening the\n // picker. Gated on the same conditions as Ctrl+A (chat screen, idle,\n // multi-profile registry) so the textarea never sees it as input and\n // single-agent setups don't get a no-op binding.\n if (key.shift && key.name === 'tab' && screen === 'chat' && hasMultipleAgents && !busy) {\n void onCycleAgent()\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 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 const hints: Hint[] = useMemo(\n () => buildHints({\n screen,\n busy,\n pending: !!pendingApproval,\n currentSession,\n hasMultipleAgents,\n modelLabel: picked?.model ?? null,\n modelColor: COLOR.model,\n agentLabel: pickedAgent.label,\n agentColor: accentColor(pickedAgent.accent, COLOR),\n }),\n [screen, busy, pendingApproval, currentSession, hasMultipleAgents, picked, pickedAgent, COLOR],\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 const promptTriggerHints: Hint[] = useMemo(() => {\n const out: Hint[] = []\n if (filesCatalog.length > 0) {\n out.push({\n key: '@',\n label: 'files',\n keyColor: resolveChipColor(SURFACE.chips, 'files').bg,\n })\n }\n if (skillsCatalog.length > 0) {\n out.push({\n key: '/',\n label: 'skills',\n keyColor: resolveChipColor(SURFACE.chips, 'skills').bg,\n })\n }\n return out\n }, [filesCatalog, skillsCatalog, 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 // Drop the agent + clear timers when the app unmounts.\n useEffect(() => () => { void teardown() }, [teardown])\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\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={sessionProjectRoot}\n />\n )}\n {screen === 'chat' && (\n <ChatScreen\n events={events}\n busy={busy}\n settings={settings}\n onSubmit={onSubmitPrompt}\n session={currentSession}\n pending={pendingApproval}\n onApproval={resolveHead}\n completionProviders={completionProviders}\n onPopupOpenChange={onPopupOpenChange}\n selectedTurnId={selectedTurnId}\n promptTriggerHints={promptTriggerHints}\n />\n )}\n </box>\n <Footer hints={hints} context={contextUsage} />\n </box>\n )\n}\n\n/**\n * Build the footer's shortcut hints for the current screen. On the chat\n * screen the model id rides next to its `ctrl+m` shortcut and the agent\n * label rides next to `shift+tab`, each in its accent color — the bar\n * doubles as the status display without needing separate badges.\n */\nfunction buildHints({\n screen,\n busy,\n pending,\n currentSession,\n hasMultipleAgents,\n modelLabel,\n modelColor,\n agentLabel,\n agentColor,\n}: {\n screen: Screen\n busy: boolean\n pending: boolean\n currentSession: SessionMeta | null\n hasMultipleAgents: boolean\n modelLabel: string | null\n modelColor: string\n agentLabel: string\n agentColor: string\n}): Hint[] {\n if (pending)\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'abort run' }]\n if (busy)\n return [{ key: 'esc', label: 'abort' }]\n if (screen === 'auth')\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'exit' }]\n if (screen === 'sessions') {\n return [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\n { key: 'ctrl+x', label: 'session' },\n { key: 'ctrl+,', label: 'settings' },\n { key: 'esc', label: currentSession ? 'back' : 'exit' },\n ]\n }\n // Chat screen. Prompt-input shortcuts (`↵ send`, `shift+↵ newline`,\n // `↑↓ history`) live in the prompt box's overlay title — the bottom\n // bar only carries agent / model / global affordances. `shift+tab`\n // cycles agent profiles with the active label inline; `ctrl+m` opens\n // the model picker with the active model id inline. Ctrl+A opens the\n // descriptive agent picker for power users but isn't advertised here\n // to keep the hint row short. `shift+tab` is suppressed when there's\n // only one profile to switch between. `ctrl+x` opens the active\n // session's details / actions modal — only meaningful once a session\n // exists, hence the `currentSession` gate.\n return [\n ...(hasMultipleAgents ? [{ key: 'shift+tab', label: agentLabel, labelColor: agentColor }] : []),\n ...(modelLabel ? [{ key: 'ctrl+m', label: modelLabel, labelColor: modelColor }] : []),\n ...(currentSession ? [{ key: 'ctrl+x', label: 'session' }] : []),\n { key: 'ctrl+,', label: 'settings' },\n { key: 'esc', label: 'sessions' },\n ]\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 *\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\nlet registered = false\n\n/**\n * Register the extra Tree-sitter parsers + start the worker. Idempotent —\n * subsequent calls are no-ops. Safe to invoke from `runTui()` and from\n * composition hosts that mount `<App>` directly.\n */\nexport async function setupTreeSitter(): Promise<void> {\n if (registered)\n return\n registered = true\n addDefaultParsers(EXTRA_PARSERS)\n // Initialize the global client. The actual grammar `.wasm` downloads are\n // lazy — they happen on the first fence of each language — but the worker\n // needs to be alive before any markdown renders.\n await getTreeSitterClient().initialize()\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ChatOptions } from '../chat/config'\nimport { createCliRenderer } from '@opentui/core'\nimport { createRoot } from '@opentui/react'\nimport { resolveConfig } from '../chat/config'\nimport { createTuiStore } from '../session/sqlite'\nimport { App } from './app'\nimport { setupTreeSitter } 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 *\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\n // Register extra Tree-sitter parsers (python, bash, json, rust, go,\n // yaml, html, css) so fenced code blocks in those languages get\n // syntax highlighting. The grammar `.wasm` files are downloaded\n // lazily on first use and cached under OpenTUI's data path. Swallow\n // init errors — if the worker can't start (offline, sandboxed FS),\n // the renderer still works, those fences just render as plain text.\n await setupTreeSitter().catch((err) => {\n const cause = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${cause}\\n`)\n })\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\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 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 })\n\n createRoot(renderer).render(<App config={config} />)\n\n await exited\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// `splitPromptSegments` + `PromptSegment` live in `zidane/chat` so a GUI\n// shell can reuse the chip splitter without pulling OpenTUI. Re-exported\n// here for back-compat with TUI consumers that imported it from `zidane/tui`.\nexport { type PromptSegment, type PromptSegmentRef, splitPromptSegments } from '../chat/prompt-segments'\nexport { accentColor, AgentPickerModal } from './agent-picker'\nexport { App } from './app'\nexport { CompletionPopup } from './completion-popup'\nexport {\n type ContextUsage,\n Footer,\n type Hint,\n hintsLength,\n isVisible,\n marginTopFor,\n onInputSubmit,\n renderHintSpans,\n Spinner,\n Transcript,\n} from './components'\nexport { McpsSettingsModal } from './mcps-settings'\nexport { Modal, type ModalProps, ModalRoot, useModal, useModalAwareFocus } from './modal'\nexport { ModelPickerModal } from './model-picker'\nexport { AuthScreen, ChatScreen, SessionsScreen } from './screens'\nexport { type SettingsActions, SettingsModal } from './settings-modal'\nexport { SkillsSettingsModal } from './skills-settings'\nexport { buildMdStyle, useMdStyle } from './theme'\nexport { ToggleListModal } from './toggle-list-modal'\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"tui.js","names":["VISIBLE_ROW_CAP","EmptyState","EmptyState","ActionRow","ActionRow"],"sources":["../src/tui/modal.tsx","../src/tui/agent-picker.tsx","../src/tui/theme.ts","../src/tui/components.tsx","../src/tui/toggle-list-modal.tsx","../src/tui/mcps-settings.tsx","../src/tui/model-picker.tsx","../src/tui/completion-popup.tsx","../src/tui/screens.tsx","../src/tui/clipboard.ts","../src/tui/session-details-modal.tsx","../src/tui/settings-modal.tsx","../src/tui/skills-settings.tsx","../src/tui/turn-details-modal.tsx","../src/tui/app.tsx","../src/tui/tree-sitter.ts","../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 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\n const api = useMemo<ModalApi>(() => ({\n open: node => setActive(node),\n close: () => setActive(null),\n get isOpen() { return active !== null },\n }), [active])\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 /** 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 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 return (\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","/** @jsxImportSource @opentui/react */\nimport type { AgentAccent, 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/**\n * Resolve a profile's `accent` token to a concrete theme color via the\n * caller's color palette. Exposed for the Footer badge so all surfaces\n * stay in sync with the picker's row tinting.\n */\nexport function accentColor(\n accent: AgentAccent | undefined,\n COLOR: { brand: string, accent: string, warn: string, model: string },\n): string {\n switch (accent) {\n case 'brand': return COLOR.brand\n case 'warn': return COLOR.warn\n case 'model': return COLOR.model\n case 'accent':\n default:\n return COLOR.accent\n }\n}\n\n/** Re-export so consumers don't need to import the type from `zidane/chat` separately. */\nexport type { AgentProfile }\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\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 */\nexport function buildMdStyle(theme: Theme): SyntaxStyle {\n interface Style {\n fg?: RGBA\n bg?: RGBA\n bold?: boolean\n italic?: boolean\n underline?: boolean\n dim?: boolean\n }\n const styles: Record<string, Style> = {}\n for (const [token, style] of Object.entries(theme.syntax)) {\n const out: Style = {}\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 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\nconst MdStyleContext = createContext<SyntaxStyle | null>(null)\n\nexport function MdStyleProvider({ children }: { children: ReactNode }) {\n const theme = useTheme()\n const style = useMemo(() => buildMdStyle(theme), [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: style }, 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 * 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(): SyntaxStyle {\n const style = useContext(MdStyleContext)\n if (!style)\n throw new Error('useMdStyle must be used inside <MdStyleProvider>')\n return style\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 { ScrollBoxRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type { ThemeColors } from '../chat/theme'\nimport type { Settings, StreamEvent } from '../chat/types'\nimport { useTerminalDimensions } from '@opentui/react'\nimport { memo, useEffect, useMemo, useRef, useState } from 'react'\nimport { fmtTokens } from '../chat/format'\nimport { splitPromptSegments } from '../chat/prompt-segments'\nimport { resolveChipColor } from '../chat/theme'\nimport { useColors, useSurfaces } from '../chat/theme-context'\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 }: {\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 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} />\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 Hint {\n key: string\n label: string\n /** Optional override for the key color. Defaults to `COLOR.warn`. */\n keyColor?: string\n /** Optional override for the label color. Defaults to `COLOR.dim`. */\n labelColor?: string\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}: {\n hints: Hint[]\n context: ContextUsage | null\n}) {\n const { width } = useTerminalDimensions()\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 cW = context ? contextIndicatorLength(context) : 0\n\n const oneRowFits = hW + (cW > 0 ? cW + 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 <box style={{ flexGrow: 1 }} />\n {context && <ContextIndicator context={context} />}\n </box>\n )\n }\n\n return (\n <box style={{ flexDirection: 'column', paddingLeft: 1, paddingRight: 1 }}>\n <box style={{ flexDirection: 'row', height: 1 }}>\n <HintsText hints={hints} />\n </box>\n {context && (\n <box style={{ flexDirection: 'row', height: 1 }}>\n <box style={{ flexGrow: 1 }} />\n <ContextIndicator context={context} />\n </box>\n )}\n </box>\n )\n}\n\nfunction HintsText({ hints }: { hints: 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 <span fg={h.labelColor ?? COLOR.dim}>{` ${h.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 <text fg={COLOR.dim}>\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 </text>\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 * Colored title for a full-screen bordered surface. The `title` slot\n * rides `titleColor` (defaults to `COLOR.brand` — the theme's primary\n * anchor) on the LEFT of the top border; the optional `meta` slot\n * 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}: {\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 const COLOR = useColors()\n const { width: termWidth } = useTerminalDimensions()\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 const metaLen = metaSegmentsLength(meta)\n const showMeta = meta != null && metaLen > 0\n && title.length + 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\n const visibleTitle = titleBudget <= 0 ? '' : truncateTrailing(title, titleBudget)\n\n return (\n <>\n {visibleTitle && (\n <text style={{ position: 'absolute', top: 0, left: 1 }}>\n <span fg={COLOR.mute}>{' '}</span>\n <span fg={fg}>{visibleTitle}</span>\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\n/**\n * Truncate `text` to at most `max` characters, replacing the trailing\n * overflow with `…`. Edge cases:\n * - `max <= 0` → empty string (no room to render at all).\n * - `max === 1` → just the ellipsis glyph.\n * - `text.length <= max` → unchanged.\n *\n * Trailing-style truncation matches the natural read order of titles:\n * the prefix carries enough signal to identify the surface.\n *\n * Exported for unit-tests; consumers should normally lean on\n * {@link TitleOverlay} instead of calling this directly.\n */\nexport function truncateTrailing(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\n// ---------------------------------------------------------------------------\n// Plain-text width estimates for the footer's responsive tiering.\n//\n// We approximate with `.length` instead of `stringWidth` because every glyph\n// involved (hint keys, provider names, model ids, token counts) is either\n// ASCII or a 1-cell symbol. The estimates only need to be precise enough to\n// pick a layout tier — being off by a column at the breakpoint is harmless.\n// ---------------------------------------------------------------------------\n\n/**\n * Plain-text width estimate for a list of {@link Hint}s rendered via\n * `renderHintSpans` — `<key> <label> · <key> <label> · …`. Exported so\n * the prompt-box overlay (in `screens.tsx`) can run the same responsive\n * math as the bottom-bar footer when deciding whether trigger hints\n * fit. Pure / total.\n */\nexport function hintsLength(hints: readonly Hint[]): number {\n if (hints.length === 0)\n return 0\n // \" · \" between hints, \"<key> <label>\" per hint.\n return hints.reduce(\n (sum, h, i) => sum + h.key.length + 1 + h.label.length + (i > 0 ? 3 : 0),\n 0,\n )\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// ---------------------------------------------------------------------------\n// Spinner — animated braille used while a run is streaming.\n// ---------------------------------------------------------------------------\n\nconst SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']\nconst SPINNER_INTERVAL_MS = 80\n\nexport function Spinner({ label }: { label: string }) {\n const [frame, setFrame] = useState(0)\n const COLOR = useColors()\n\n useEffect(() => {\n const id = setInterval(() => setFrame(f => (f + 1) % SPINNER_FRAMES.length), SPINNER_INTERVAL_MS)\n return () => clearInterval(id)\n }, [])\n\n return (\n <text fg={COLOR.warn}>\n {SPINNER_FRAMES[frame]}\n <span fg={COLOR.dim}>{` ${label}`}</span>\n </text>\n )\n}\n\n// ---------------------------------------------------------------------------\n// Transcript — scrollbox with sticky-bottom and structured event rendering.\n// ---------------------------------------------------------------------------\n\n/**\n * Minimum scrollbar thumb size, in half-block units (OpenTUI's\n * `SliderRenderable` renders the vertical thumb at 2 half-blocks per\n * character cell). `8` half-blocks = 4 character cells — always large\n * enough to read + grab with the mouse, never so large that it\n * dominates the track on short transcripts.\n */\nconst MIN_THUMB_HALF_BLOCKS = 8\n\nexport function Transcript({\n events,\n settings,\n selectedTurnId = null,\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 const items = useMemo(() => partitionTranscript(events, settings), [events, settings])\n const scrollboxRef = useRef<ScrollBoxRenderable | null>(null)\n\n // Enforce a minimum scrollbar thumb size. OpenTUI's `SliderRenderable`\n // computes the thumb as `floor(2 * height * viewportSize / contentSize)`\n // and clamps the result to a one-half-block floor — meaning that a\n // long transcript (1000+ events) produces a thumb of literally one\n // half-character cell, which is visible but ungrabbable. There's no\n // public `minThumbSize` knob today, so we monkey-patch the slider's\n // `getVirtualThumbSize` to raise the floor to `MIN_THUMB_HALF_BLOCKS`\n // (4 character cells). Capping at `virtualTrackSize` keeps very short\n // transcripts unaffected — their natural thumb already fills the\n // track. Defensive: bail when OpenTUI's internals don't match (e.g.\n // a future version restructures or renames `getVirtualThumbSize`).\n useEffect(() => {\n const scrollbox = scrollboxRef.current\n if (!scrollbox)\n return\n // `getVirtualThumbSize` is declared `private` on OpenTUI's\n // `SliderRenderable` so TypeScript refuses a direct structural cast;\n // a two-step `unknown → { … }` widening keeps the type-check honest\n // while keeping the runtime monkey-patch focused on the one method\n // we touch.\n const slider = scrollbox.verticalScrollBar?.slider as unknown as\n | { height: number, getVirtualThumbSize: () => number }\n | undefined\n if (!slider || typeof slider.getVirtualThumbSize !== 'function')\n return\n const original = slider.getVirtualThumbSize.bind(slider)\n slider.getVirtualThumbSize = function () {\n const upstream = original()\n const virtualTrackSize = slider.height * 2\n return Math.min(virtualTrackSize, Math.max(MIN_THUMB_HALF_BLOCKS, upstream))\n }\n return () => {\n slider.getVirtualThumbSize = original\n }\n }, [])\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 if (selectedTurnId === anchors.lastTurnId) {\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])\n\n if (items.length === 0)\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 >\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={selectedTurnId !== null && item.event.turnId === selectedTurnId}\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 </scrollbox>\n )\n}\n\n/**\n * Per-item anchor ids for auto-scroll. Walks `items` in render order and,\n * for each event, returns either:\n * - `'turn-anchor-<turnId>'` — the first event of this turn (the\n * scrollbox's target).\n * - `undefined` — later event of an already-tagged turn (or a synthetic\n * event with no `turnId`).\n *\n * `ids[i]` is a tuple per item: length 1 for plain events, length N for\n * subagent runs (one entry per inner event). `idByTurn` is the inverse\n * lookup used by the scroll effect. `lastTurnId` is the most-recently-\n * rendered turn — the scroll effect special-cases it to snap to bottom.\n *\n * Exported so the anchor-tagging matrix can be unit-tested without rendering.\n */\nexport function computeTurnAnchors(items: readonly TranscriptItem[]): {\n idByTurn: ReadonlyMap<string, string>\n ids: readonly (readonly (string | undefined)[])[]\n lastTurnId: string | undefined\n} {\n const idByTurn = new Map<string, string>()\n let lastTurnId: string | undefined\n const tag = (turnId: string | undefined): string | undefined => {\n if (!turnId)\n return undefined\n lastTurnId = turnId\n if (idByTurn.has(turnId))\n return undefined\n const id = `turn-anchor-${turnId}`\n idByTurn.set(turnId, id)\n return id\n }\n const ids: (string | undefined)[][] = []\n for (const item of items) {\n if (item.kind === 'event')\n ids.push([tag(item.event.turnId)])\n else\n ids.push(item.events.map(e => tag(e.turnId)))\n }\n return { idByTurn, ids, lastTurnId }\n}\n\n/**\n * Per-event visibility — filters honor user toggles and the\n * `hideSubagentOutput` setting. When subagent output is hidden:\n * - Child-agent events are filtered down to the `spawn-start` /\n * `spawn-end` markers so the user still sees \"🌱 working… 🌳 done\".\n * - The parent's `tool-result` for `spawn` is hidden too. Its body\n * duplicates `spawn-end`'s stats line *and* the parent's next markdown\n * turn (\"Here's what the sub-agent found: …\"). Showing it again\n * produced an extra `┃ [sub-agent child-1] Completed …` block that\n * the user just wanted gone.\n *\n * Exported so the visibility matrix can be unit-tested without rendering.\n */\nexport function isVisible(event: StreamEvent, settings: Settings): boolean {\n if (settings.hideSubagentOutput) {\n if (isChild(event))\n return event.kind === 'spawn-start' || event.kind === 'spawn-end'\n if (event.kind === 'tool-result' && event.tool === 'spawn')\n return false\n }\n switch (event.kind) {\n case 'thinking': return settings.showThinking\n case 'tool': return settings.showToolCalls\n case 'tool-result': return settings.showToolResults\n default: return true\n }\n}\n\n/**\n * Output of `partitionTranscript`. Single events render as a normal\n * `EventLine`; child-event runs render as a bordered subagent box.\n *\n * Exported alongside `computeTurnAnchors` so the anchor-tagging matrix\n * can be unit-tested without going through React rendering.\n */\nexport type TranscriptItem\n = | { kind: 'event', event: StreamEvent, previous?: StreamEvent }\n | { kind: 'child-run', events: StreamEvent[], previous?: StreamEvent }\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 return (\n <box\n title={title}\n style={{\n border: true,\n borderColor: COLOR.mute,\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 />\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/**\n * Default top-margin per kind. Spacing intent:\n * - `info` / `markdown` / `tool` / `error` / `spawn-start` open a new block\n * so they each get one row of breathing room above.\n * - `thinking` / `tool-result` / `spawn-end` continue the previous block\n * and stay flush.\n *\n * Context-aware overrides live in `marginTopFor` — e.g. consecutive tool\n * round-trips collapse to a tight list regardless of whether outputs are shown.\n */\nconst MARGIN_TOP: Record<StreamEvent['kind'], number> = {\n 'separator': 0,\n 'user-prompt': 1,\n 'info': 1,\n 'thinking': 0,\n 'tool': 1,\n 'tool-result': 0,\n 'error': 1,\n 'markdown': 1,\n 'spawn-start': 1,\n 'spawn-end': 0,\n}\n\nconst TOOL_KINDS: ReadonlySet<StreamEvent['kind']> = new Set(['tool', 'tool-result'])\n\n/**\n * Resolve the top margin for an event given the one rendered just before it.\n *\n * Context-aware rules:\n *\n * - A `tool` / `tool-result` event right after another `tool` / `tool-result`\n * collapses to a tight list — call→result pairs and back-to-back calls\n * read as one logical block.\n * - A parent-level event (`depth === 0`) right after a subagent event\n * (`depth > 0`) collapses too. The subagent's `🌳` end marker (and, in\n * show mode, the subagent box's bottom border) already provides the\n * separation; adding the event's default `marginTop` on top would\n * produce the visible \"line jump\" between a subagent's outcome and the\n * parent's follow-up. Either form of marker is enough — we don't want\n * both.\n *\n * Exported so the spacing matrix can be unit-tested without rendering.\n */\nexport function marginTopFor(event: StreamEvent, previous: StreamEvent | undefined): number {\n if (TOOL_KINDS.has(event.kind) && previous && TOOL_KINDS.has(previous.kind))\n return 0\n const eventDepth = event.depth ?? 0\n const previousDepth = previous?.depth ?? 0\n if (eventDepth === 0 && previousDepth > 0)\n return 0\n return MARGIN_TOP[event.kind] ?? 0\n}\n\nfunction EventLineImpl({ event, depthOffset = 0 }: {\n event: StreamEvent\n depthOffset?: number\n}) {\n const COLOR = useColors()\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} />\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 return (\n <box style={row}>\n <text fg={child ? COLOR.dim : COLOR.model}>\n <span fg={COLOR.mute}>↳ </span>\n {safeText}\n </text>\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 <MarkdownBlock text={event.text} streaming={event.streaming ?? false} dim={child} />\n </box>\n )\n case 'spawn-start':\n return (\n <box style={row}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>🌱 </span>\n <span fg={COLOR.dim}>{`[${event.childId ?? 'child'}] `}</span>\n <span fg={COLOR.dim}>{safeText}</span>\n </text>\n </box>\n )\n case 'spawn-end':\n // Use a 2-cell emoji (like the 🌱 sprout on spawn-start) so the `[…]`\n // label lands in the same column on every row of a subagent's life\n // cycle — the previous `✓` glyph is 1 cell wide and shifted the\n // label one column left of the start marker.\n return (\n <box style={row}>\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>🌳 </span>\n <span fg={COLOR.dim}>{`[${event.childId ?? 'child'}] `}</span>\n <span fg={COLOR.mute}>{safeText}</span>\n </text>\n </box>\n )\n default:\n return <text>{safeText}</text>\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}: {\n text: string\n refs?: readonly { start: number, end: number, providerId: string }[]\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 if (!refs || refs.length === 0) {\n return (\n <box style={boxStyle}>\n <text fg={COLOR.brand}>\n <span fg={COLOR.brand}>{USER_PROMPT_PREFIX}</span>\n {text}\n </text>\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} fg={COLOR.brand}>{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 </box>\n )\n}\n\n/**\n * Markdown block. Renders either live-streaming markdown (with `streaming`\n * on, while deltas are still appending) or finalized markdown (after\n * `turn:after`, or every entry on a reloaded transcript).\n *\n * `internalBlockMode` is the load-bearing knob for layout: the OpenTUI\n * default (`\"coalesced\"`) fuses adjacent top-level blocks into one render\n * block, which is the right tradeoff for finalized markdown — fewer flex\n * children, fewer layout passes, the parser already knows the final shape.\n * During streaming, that same coalescing makes earlier paragraphs visually\n * re-flow on every token, so we switch to `\"top-level\"` (each block its\n * own renderable, only the trailing one is unstable).\n *\n * `internalBlockMode` is set only at construction by OpenTUI — there's no\n * setter — so a `<MarkdownBlock>` keeps whichever mode it was born with.\n * Live blocks start `streaming=true` → top-level; reloaded blocks start\n * `streaming=false` → coalesced. Each variant stays optimal for its\n * lifecycle.\n *\n * Note: we don't pre-process unclosed delimiters. OpenTUI's markdown\n * parser already renders partial input reasonably during streaming (the\n * trailing block reflows as tokens close), and the simplicity is worth\n * accepting a brief literal `**` before the closer arrives. Persisted\n * reloads come from completed assistant turns whose markdown is closed.\n */\nfunction MarkdownBlock({ text, streaming, dim }: { text: string, streaming: boolean, dim: boolean }) {\n const COLOR = useColors()\n const mdStyle = useMdStyle()\n return (\n <markdown\n content={text}\n syntaxStyle={mdStyle}\n streaming={streaming}\n internalBlockMode={streaming ? 'top-level' : 'coalesced'}\n fg={dim ? COLOR.dim : undefined}\n />\n )\n}\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 const lines = text.split('\\n')\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","/** @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}: {\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 const COLOR = useColors()\n const { enabledSet, toggle } = useEnabledToggleSet<T>({ catalog, keyOf, settingKey })\n const [cursor, setCursorRaw] = useState(0)\n\n const setCursor = useCallback(\n (update: (c: number) => number) =>\n setCursorRaw(prev => Math.min(Math.max(0, update(prev)), Math.max(0, catalog.length - 1))),\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 setCursor(c => c - 1)\n }\n else if (key.name === 'down' || (key.ctrl && key.name === 'n')) {\n setCursor(c => c + 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 })\n\n if (catalog.length === 0) {\n return (\n <Modal title={title}>\n {emptyState}\n <text fg={COLOR.mute}>\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 <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 <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { DiscoveredMcp } from '../chat/mcps-discovery'\nimport { useColors } from '../chat/theme-context'\nimport { ToggleListModal } from './toggle-list-modal'\n\n/**\n * List + toggle modal for MCP servers discovered from `.{prefix}/mcps.json`\n * / `.agents/mcps.json` (project + user). State machine lives in\n * `<ToggleListModal>` (shared with the Skills picker); this file supplies\n * the transport/command detail column and the empty-state copy.\n *\n * Toggling does NOT restart the active agent — the change applies on the\n * next session activation (the app rebuilds the agent there), keeping\n * current runs stable.\n */\nexport function McpsSettingsModal({\n catalog,\n}: {\n catalog: readonly DiscoveredMcp[]\n}) {\n const COLOR = useColors()\n return (\n <ToggleListModal<DiscoveredMcp>\n catalog={catalog}\n keyOf={d => d.config.name}\n settingKey=\"enabledMcps\"\n title=\"mcp servers\"\n renderDetail={(entry) => {\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 emptyState={(\n <>\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 ). Array of\n <span fg={COLOR.model}>{' McpServerConfig '}</span>\n or\n <span fg={COLOR.model}>{' { \"mcpServers\": { ... } } '}</span>\n .\n </text>\n </>\n )}\n />\n )\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ModelInfo } from '../chat/providers'\nimport { useMemo } from 'react'\nimport { fmtTokens } from '../chat/format'\nimport { useColors, useSelectStyle } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n/** Cap the visible scroll window so a 30-model list doesn't push the modal off-screen. */\nconst VISIBLE_ROW_CAP = 12\n\n/**\n * Modal that lists the available models for the current provider and lets\n * the user pick one. Options come from the active `ProviderDescriptor` —\n * either its declared `models` list or, when absent, pi-ai's built-in\n * registry looked up via `piProviderId`.\n *\n * Each row shows: `● selected · name (ctx N · reasoning · vision)`.\n */\nexport function ModelPickerModal({\n models,\n currentModelId,\n onPick,\n}: {\n models: readonly ModelInfo[]\n currentModelId: string\n onPick: (modelId: string) => void\n}) {\n const COLOR = useColors()\n const SELECT_THEME = useSelectStyle()\n // Hooks must always run in the same order — keep them above any conditional\n // return. The \"no models\" branch becomes JSX below.\n const initialIndex = useMemo(\n () => models.findIndex(m => m.id === currentModelId),\n [models, currentModelId],\n )\n\n const options = useMemo(\n () => models.map(m => ({\n name: `${m.id === currentModelId ? '● ' : ' '}${m.name ?? m.id}`,\n description: describeModel(m),\n value: m.id,\n })),\n [models, currentModelId],\n )\n\n if (models.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 previously-saved model isn't\n // available in the current registry.\n const safeIndex = currentMissing ? 0 : initialIndex\n\n return (\n <Modal title=\"select model\">\n {currentMissing && (\n <text fg={COLOR.warn}>\n {`Current model \"${currentModelId}\" is not in this registry — pick one below to switch.`}\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 model\">\n <text fg={COLOR.dim}>No models available for this provider.</text>\n <text fg={COLOR.mute}>\n Set\n <span fg={COLOR.model}> models </span>\n on the provider descriptor (or a\n <span fg={COLOR.model}> piProviderId </span>\n that pi-ai recognizes) to populate this list.\n </text>\n </Modal>\n )\n}\n\n/** \"ctx 200k · reasoning · vision\" — compact per-model description. */\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 */\nimport type { InputRenderable, KeyEvent, TextareaRenderable } from '@opentui/core'\nimport type { ReactNode } from 'react'\nimport type { ProviderAuth } from '../chat/auth'\nimport type { CompletionProvider, CompletionReference } from '../chat/completion'\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 { Hint, MetaSegment } from './components'\nimport { defaultTextareaKeyBindings } 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 { runOAuthLogin, supportsOAuth } from '../chat/oauth'\nimport { suggestSafelistEntry } from '../chat/safe-mode'\nimport { useColors, useSelectStyle } from '../chat/theme-context'\nimport { CompletionPopup } from './completion-popup'\nimport { hintsLength, renderHintSpans, Spinner, TitleOverlay, Transcript } from './components'\nimport { useModalAwareFocus } from './modal'\nimport { useChipHighlights, useChipStyle } from './theme'\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(err instanceof Error ? err.message : String(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\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 [url, setUrl] = useState<string | null>(null)\n const [status, setStatus] = useState('starting browser…')\n const COLOR = useColors()\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('waiting for browser callback…')\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 const message = err instanceof Error ? err.message : String(err)\n onError(message)\n }\n })()\n\n return () => { cancelled = true; ac.abort() }\n }, [descriptor, dataDir, onSuccess, onError])\n\n return (\n <WizardPanel title={`configure ${descriptor.label} — OAuth`}>\n <WizardEscHint />\n <Spinner label={status} />\n {url && (\n <box style={{ flexDirection: 'column', gap: 0 }}>\n <text fg={COLOR.dim}>If the browser didn't open, visit:</text>\n <text fg={COLOR.model}>{url}</text>\n </box>\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\n const rows = useMemo<readonly SessionRowItem[]>(() => [\n { kind: 'new', rowId: NEW_SESSION_ROW_ID, meta: null },\n ...sessions.map<SessionRowItem>(meta => ({ kind: 'session', rowId: meta.id, meta })),\n ], [sessions])\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 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 === 'home') {\n moveCursor(0)\n return\n }\n if (key.name === 'end') {\n moveCursor(rows.length - 1)\n return\n }\n if (key.name === 'return') {\n commitCurrent()\n }\n })\n\n // `meta` summarizes the catalog state — \"no sessions\" tells the user\n // why only the \"+ new\" row is visible; \"N sessions\" rides the dim\n // top-right slot once there's actual history. Dropping it on a narrow\n // terminal is handled by `TitleOverlay` automatically. The count is\n // `warn`-tinted so the volume of history reads at a glance, matching\n // the colored turn-count treatment on the chat screen's title bar.\n const titleMeta = useMemo<readonly MetaSegment[]>(() => {\n if (sessions.length === 0)\n return [{ text: 'no sessions yet', color: COLOR.mute }]\n return [\n { text: String(sessions.length), color: COLOR.warn },\n { text: ` session${sessions.length === 1 ? '' : 's'}` },\n ]\n }, [sessions.length, COLOR])\n\n // cwd header — left-truncates to the box's interior width minus\n // the `cwd ` prefix + 1 trailing cell of safety. Computed against\n // the live terminal width via `useTerminalDimensions()`. The full-\n // height bordered box eats 2 cells of horizontal padding (`padding:\n // 1` on each side) plus 2 for the border, so the inner content\n // width is `termWidth - 4`. We reserve 4 more cells for `cwd ` so\n // the path itself gets `termWidth - 8`.\n const { width: termWidth } = useTerminalDimensions()\n const cwdMaxWidth = Math.max(16, termWidth - 8)\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 cwd header — tells the user which project the list is\n filtered to (or that they're in the cross-project view).\n Own line, left-truncated, dimmed so it sits below the\n rows in the visual hierarchy. Always shown so users always\n know where they are; the `all projects` suffix only\n appears when the filter is off.\n */}\n {currentProjectRoot && (\n <box style={{ flexDirection: 'column', flexShrink: 0, marginBottom: 1 }}>\n <text wrapMode=\"none\">\n <span fg={COLOR.mute}>cwd </span>\n <span fg={COLOR.dim}>{compactPath(currentProjectRoot, cwdMaxWidth)}</span>\n {showAllProjects && (\n <>\n <span fg={COLOR.mute}>{' · '}</span>\n <span fg={COLOR.accent}>all projects</span>\n </>\n )}\n </text>\n </box>\n )}\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 </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\nexport function ChatScreen({\n events,\n busy,\n settings,\n onSubmit,\n session,\n pending,\n onApproval,\n completionProviders,\n onPopupOpenChange,\n selectedTurnId,\n promptTriggerHints,\n}: {\n events: StreamEvent[]\n busy: boolean\n settings: Settings\n /**\n * Submit handler — receives the raw prompt text and the parsed references\n * (skills, files, …) so the App can act on them (e.g. activate the\n * referenced skill before `agent.run()`).\n */\n onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[]) => 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 * 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 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 const showSessionShortcut = !!session && !busy && !pending\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 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 return segments\n }, [session, userMessageCount, COLOR, showSessionShortcut])\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 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 }}\n >\n <Transcript events={events} settings={settings} selectedTurnId={selectedTurnId ?? null} />\n </box>\n\n {/*\n Priority: pending approval wins over busy (a paused run still has\n `busy === true` while the gate awaits). The picker takes the prompt\n slot so the user can decide without losing context.\n */}\n {pending\n ? <ApprovalBlock request={pending} onPick={onApproval} />\n : busy\n ? <BusyBlock />\n : (\n <PromptBlock\n userPrompts={userPrompts}\n onSubmit={onSubmit}\n completionProviders={completionProviders}\n onPopupOpenChange={onPopupOpenChange}\n selectMode={selectedTurnId != null}\n triggerHints={promptTriggerHints}\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 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 + 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\nfunction BusyBlock() {\n const COLOR = useColors()\n return (\n <box\n style={{\n border: true,\n borderColor: COLOR.warn,\n paddingLeft: 1,\n paddingRight: 1,\n height: 3,\n }}\n >\n <Spinner label=\"streaming response — esc to abort\" />\n </box>\n )\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 = false,\n triggerHints,\n}: {\n userPrompts: string[]\n onSubmit: (prompt: string, references: readonly CompletionReference<unknown>[]) => void\n completionProviders?: readonly CompletionProvider<unknown>[]\n onPopupOpenChange?: (open: boolean) => void\n /**\n * True while the user is in select-turn mode. Unfocuses the textarea so\n * up/down/return reach the parent's keyboard handler, and swaps the\n * overlay hints to the navigation set.\n */\n selectMode?: boolean\n /** Optional trigger hints appended to the right overlay when there's room. */\n triggerHints?: readonly Hint[]\n}) {\n const focused = useModalAwareFocus()\n const COLOR = useColors()\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 /**\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 setContentLines(Math.max(MIN_CONTENT_LINES, ta.lineCount))\n }, [])\n\n const submit = useCallback(() => {\n const value = textareaRef.current?.plainText ?? ''\n if (!value.trim())\n return\n onSubmit(value, completion.references)\n textareaRef.current?.clear()\n historyRef.current = null\n setBufferState({ text: '', cursor: 0 })\n setContentLines(MIN_CONTENT_LINES)\n }, [onSubmit, completion.references])\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 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 const cursorRow = buffer.logicalCursor.row\n if (event.name === 'up' && cursorRow === 0) {\n cycleHistory(-1)\n event.preventDefault()\n }\n else if (event.name === 'down' && cursorRow === buffer.lineCount - 1) {\n cycleHistory(1)\n event.preventDefault()\n }\n }, [popupOpen, completion, commitCompletion, cycleHistory])\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 <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 placeholder={selectMode ? '— turn-select mode — press ⎋ to resume typing —' : 'Ask zidane…'}\n syntaxStyle={chipStyle}\n style={{ flexGrow: 1, height: '100%' }}\n onSubmit={submit}\n onContentChange={syncBuffer}\n onKeyDown={onKeyDown}\n />\n </box>\n <PromptHints selectMode={selectMode} triggerHints={triggerHints} />\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 <box style={{ position: 'absolute', bottom: '100%', left: 0, right: 0, flexDirection: 'column' }}>\n <CompletionPopup state={completion} />\n </box>\n )}\n </box>\n )\n}\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/** Prompt-box shortcuts in select-turn mode — only the selection actions are valid. */\nconst PROMPT_HINTS_SELECT: readonly Hint[] = [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\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 }: {\n selectMode: boolean\n triggerHints?: readonly Hint[]\n}) {\n const COLOR = useColors()\n const { width: termWidth } = useTerminalDimensions()\n const primary = selectMode ? PROMPT_HINTS_SELECT : PROMPT_HINTS_NORMAL\n const hints = useMemo<readonly Hint[]>(() => {\n if (selectMode || !triggerHints || triggerHints.length === 0)\n return primary\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 primaryLen = hintsLength(primary)\n // Combined length = primary + \" · \" join + triggers.\n const triggerLen = hintsLength(triggerHints) + 3\n if (primaryLen + triggerLen > budget)\n return primary\n return [...primary, ...triggerHints]\n }, [selectMode, primary, triggerHints, termWidth])\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","/**\n * Write text to the system clipboard via the terminal's OSC 52 escape\n * sequence — `\\x1b]52;c;<base64>\\x07`. Modern terminals (iTerm2, kitty,\n * alacritty, wezterm, recent xterm, recent macOS Terminal) honor this;\n * tmux/screen pass it through with the right config. Older or stripped\n * terminals silently drop it — we have no way to detect that in-band.\n *\n * Why OSC 52 over `pbcopy` / `xclip` / `clip.exe`: zero dependencies, no\n * shell-out, works equally over SSH (which is where copy/paste is most\n * painful — the user's clipboard, not the remote box's clipboard).\n *\n * Returns `true` on a successful write to stdout, `false` otherwise.\n * Callers should reflect the result in UX (toast / inline message) but\n * not treat `false` as a hard failure — it just means \"stdout is not a\n * TTY\" or the terminal swallowed the sequence.\n */\n\nimport { Buffer } from 'node:buffer'\n\nexport function writeToClipboard(text: string): boolean {\n if (typeof process === 'undefined' || !process.stdout?.write)\n return false\n // Reject non-TTY stdouts so `bun run app | tee log.txt` doesn't claim\n // success while the OSC 52 escape lands in the pipe as literal bytes.\n // The terminal needs to be on the receiving end of stdout for the\n // sequence to reach the OSC handler.\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","/** @jsxImportSource @opentui/react */\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 { useColors } from '../chat/theme-context'\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\nexport function SessionDetailsModal({\n session,\n title,\n isCurrent,\n actions,\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}) {\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 // Abort handle for the in-flight title generation. Captured in a ref 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 // `mountedRef` flips false on unmount so the async `handleGenerate`\n // doesn't push state into a tree React already tore down. The abort\n // covers the provider call; 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 }, [])\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' | '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 }\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(err instanceof Error ? err.message : String(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(err instanceof Error ? err.message : String(err))\n setTitleStatus('failed')\n }\n finally {\n if (generationAbortRef.current === ac)\n generationAbortRef.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. Both title generation and export run on the same lock.\n if (titleStatus === 'loading' || exportStatus === 'writing')\n return\n if (key.name === 'escape' && pending) {\n setPending(null)\n return\n }\n if (key.name === 'd') {\n if (pending === 'delete')\n commitDelete()\n else\n setPending('delete')\n return\n }\n if (key.name === 'c') {\n setPending(null)\n handleCopy()\n return\n }\n if (key.name === 'g' && hasGenerate) {\n setPending(null)\n void handleGenerate()\n return\n }\n if (key.name === 'e' && hasExport) {\n setPending(null)\n void handleExport('markdown')\n return\n }\n if (key.name === 'j' && hasExport) {\n setPending(null)\n void handleExport('json')\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={titleStatus === 'loading' || exportStatus === 'writing' || pending !== null}\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 />\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}: {\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}) {\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 (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}>g</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}>e</span>\n {' / '}\n <span fg={COLOR.warn}>j</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}>d</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 (copyStatus === 'copied') {\n return (\n <text fg={COLOR.dim}>\n <span fg={COLOR.accent}>✓ session id copied</span>\n {' · '}\n <span fg={COLOR.warn}>d</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}>g</span>\n {' generate title · '}\n </>\n )}\n {hasExport && (\n <>\n <span fg={COLOR.warn}>e</span>\n /\n <span fg={COLOR.warn}>j</span>\n {' export md/json · '}\n </>\n )}\n <span fg={COLOR.warn}>d</span>\n {' delete · '}\n <span fg={COLOR.warn}>c</span>\n {' copy id · '}\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 { BooleanSettingKey } from '../chat/settings-context'\nimport type { Settings } from '../chat/types'\nimport { useKeyboard } from '@opentui/react'\nimport { useCallback, useMemo, useState } from 'react'\nimport { SETTINGS_CHOICES, SETTINGS_TOGGLES, useSettings } from '../chat/settings-context'\nimport { useColors } from '../chat/theme-context'\nimport { Modal } from './modal'\n\n// ---------------------------------------------------------------------------\n// SettingsModal — vertical list. Up/Down navigates, Enter/Space activates the\n// focused row (toggles a flag, cycles a choice, or invokes an action). Esc\n// dismisses (handled by the parent <Modal>).\n//\n// Three row kinds:\n// - `toggle` — a boolean stored under `settings[key]` (see SETTINGS_TOGGLES)\n// - `choice` — a string stored under `settings[key]` cycling through a fixed\n// options list (see SETTINGS_CHOICES — e.g. the theme picker)\n// - `action` — a one-shot callback (e.g. \"Re-configure providers\")\n//\n// Hosts that mount the modal pass `actions` to wire in app-level actions\n// like re-opening the auth panel. Toggle and choice rows are static —\n// sourced from the corresponding tables in `chat/settings-context.tsx` so a\n// GUI settings panel can share the same labels.\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 SettingsItem = ToggleItem | ChoiceItem | ActionItem\n\nexport interface SettingsActions {\n /**\n * Re-open the auth screen so the user can switch providers or run the\n * wizard for a new/existing one. Wiring this callback adds a\n * \"Re-configure providers\" row to the modal.\n */\n onReauth?: () => void\n /**\n * Open the Skills list + toggle modal. Adds a \"Skills\" row when wired.\n */\n onOpenSkills?: () => void\n /**\n * Open the MCP servers list + toggle modal. Adds an \"MCP servers\" row\n * when wired.\n */\n onOpenMcps?: () => void\n}\n\nexport function SettingsModal({ actions }: { actions?: SettingsActions } = {}) {\n const { settings, toggle, setSetting } = useSettings()\n const [cursor, setCursorRaw] = useState(0)\n const COLOR = useColors()\n\n // Build the row list dynamically so the modal only shows action rows whose\n // callbacks are wired. Toggle/choice rows are always present.\n const items: readonly SettingsItem[] = useMemo(() => {\n const toggleItems: ToggleItem[] = SETTINGS_TOGGLES.map(t => ({ kind: 'toggle' as const, ...t }))\n const choiceItems: ChoiceItem[] = SETTINGS_CHOICES.map(c => ({ kind: 'choice' as const, ...c }))\n const actionItems: ActionItem[] = []\n if (actions?.onOpenSkills) {\n actionItems.push({\n kind: 'action',\n id: 'skills',\n label: 'Skills',\n description: 'discover + toggle slash-command skills',\n onPick: actions.onOpenSkills,\n })\n }\n if (actions?.onOpenMcps) {\n actionItems.push({\n kind: 'action',\n id: 'mcps',\n label: 'MCP servers',\n description: 'enable / disable discovered servers',\n onPick: actions.onOpenMcps,\n })\n }\n if (actions?.onReauth) {\n actionItems.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 [...toggleItems, ...choiceItems, ...actionItems]\n }, [actions])\n\n // The modal re-mounts on each open via `modal.open(<SettingsModal/>)`, but\n // if the host swaps `actions` while the modal is open (e.g. tears down the\n // re-auth wiring mid-session), the cursor might now point past the end of\n // the truncated row list. Clamp on read so we never index out of bounds.\n const safeCursor = Math.min(cursor, items.length - 1)\n const setCursor = useCallback(\n (update: (c: number) => number) =>\n setCursorRaw(prev => Math.min(Math.max(0, update(prev)), items.length - 1)),\n [items.length],\n )\n\n useKeyboard((key) => {\n if (key.name === 'up' || (key.ctrl && key.name === 'p')) {\n setCursor(c => c - 1)\n }\n else if (key.name === 'down' || (key.ctrl && key.name === 'n')) {\n setCursor(c => c + 1)\n }\n else if (key.name === 'return' || key.name === 'space') {\n const item = items[safeCursor]\n if (!item)\n return\n if (item.kind === 'toggle') {\n toggle(item.key)\n }\n else if (item.kind === 'choice') {\n // Cycle to the next option, wrapping. Single-option choice = no-op\n // (find returns the only entry; (idx+1)%1 === 0 → same value).\n const current = settings[item.key]\n const idx = item.options.findIndex(o => o.value === current)\n const next = item.options[(idx + 1) % item.options.length]\n if (next)\n setSetting(item.key, next.value as Settings[typeof item.key])\n }\n else {\n item.onPick()\n }\n }\n })\n\n // Index where the post-toggle block starts — separator divides transcript\n // filter toggles (top) from app-level settings (theme picker, auth, …).\n // Falls back to -1 when only toggles exist.\n const firstNonToggleIndex = items.findIndex(i => i.kind !== 'toggle')\n\n return (\n <Modal title=\"settings\">\n <box style={{ flexDirection: 'column' }}>\n {items.map((item, i) => (\n <box\n key={item.kind === 'action' ? item.id : item.key}\n style={{ flexDirection: 'column' }}\n >\n {/*\n Full-width separator between transcript-filter toggles and the\n app-level block (theme picker, authentication, …).\n `border={['top']}` draws only the top edge, which yoga stretches\n to the parent's content width — no need to measure the modal\n manually. Vertical margin gives the rule breathing room from\n the rows it separates.\n */}\n {i === firstNonToggleIndex && i > 0 && (\n <box\n style={{\n border: ['top'],\n borderColor: COLOR.mute,\n height: 1,\n marginTop: 1,\n marginBottom: 1,\n }}\n />\n )}\n {item.kind === 'toggle' && (\n <ToggleRow\n label={item.label}\n description={item.description}\n enabled={settings[item.key]}\n focused={i === safeCursor}\n />\n )}\n {item.kind === 'choice' && (\n <ChoiceRow\n label={item.label}\n description={item.description}\n value={\n item.options.find(o => o.value === settings[item.key])?.label\n ?? String(settings[item.key])\n }\n cyclable={item.options.length > 1}\n focused={i === safeCursor}\n />\n )}\n {item.kind === 'action' && (\n <ActionRow\n label={item.label}\n description={item.description}\n focused={i === safeCursor}\n />\n )}\n </box>\n ))}\n </box>\n <text fg={COLOR.mute}>\n <span fg={COLOR.warn}>↑↓</span>\n {' navigate · '}\n <span fg={COLOR.warn}>↵</span>\n {' toggle/cycle/select · '}\n <span fg={COLOR.warn}>esc</span>\n {' close'}\n </text>\n </Modal>\n )\n}\n\n/**\n * Toggle row — `▶` marker · checkbox · label · description.\n *\n * Rendered as one `<text>` so OpenTUI's word-wrap handles narrow terminals\n * automatically: on wide screens everything sits on one line; on narrow ones\n * the trailing description wraps under the label without breaking the row.\n */\nfunction ToggleRow({\n label,\n description,\n enabled,\n focused,\n}: {\n label: string\n description: string\n enabled: boolean\n focused: boolean\n}) {\n const COLOR = useColors()\n return (\n <text 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}>{label}</span>\n <span fg={COLOR.mute}>{` ${description}`}</span>\n </text>\n )\n}\n\n/**\n * Choice row — `▶` marker · label · `:` · current value · description.\n *\n * Cycles through `options` on enter/space. When only one option is\n * available (`cyclable=false`) the row still renders with the current\n * value but the enter handler is a no-op — we surface this via the absence\n * of the trailing `›` affordance so it visually reads as informational.\n */\nfunction ChoiceRow({\n label,\n description,\n value,\n cyclable,\n focused,\n}: {\n label: string\n description: string\n value: string\n cyclable: boolean\n focused: boolean\n}) {\n const COLOR = useColors()\n return (\n <text fg={focused ? COLOR.brand : COLOR.dim}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</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 <span fg={COLOR.mute}>{` ${description}`}</span>\n {focused && cyclable && <span fg={COLOR.brand}>{' ↻'}</span>}\n </text>\n )\n}\n\n/**\n * Action row — cursor marker · label · description · (focus-only) trailing arrow.\n *\n * The label sits in the same column as a toggle row's `[✓]` checkbox (right\n * after the 2-col cursor slot). The trailing `›` only renders when focused\n * so it reads as a \"this row runs\" affordance, not a static decoration on\n * every action.\n */\nfunction ActionRow({\n label,\n description,\n focused,\n}: {\n label: string\n description: string\n focused: boolean\n}) {\n const COLOR = useColors()\n return (\n <text fg={focused ? COLOR.brand : COLOR.dim}>\n <span fg={focused ? COLOR.brand : COLOR.mute}>{focused ? '▶ ' : ' '}</span>\n <span fg={focused ? COLOR.brand : COLOR.accent}>{label}</span>\n <span fg={COLOR.mute}>{` ${description}`}</span>\n {focused && <span fg={COLOR.brand}>{' ›'}</span>}\n </text>\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 */\nexport function SkillsSettingsModal({\n catalog,\n}: {\n catalog: readonly SkillConfig[]\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 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 { SessionTurn } from '../types'\nimport { useKeyboard } from '@opentui/react'\nimport { useState } from 'react'\nimport { ageString, shortId } from '../chat/format'\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// - 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\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}\n\nexport function TurnDetailsModal({\n turn,\n index,\n total,\n actions,\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}) {\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\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\n useKeyboard((key) => {\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 (key.name === 'f') {\n setCopyStatus('idle')\n if (pending === 'fork')\n commitFork()\n else\n setPending('fork')\n return\n }\n if (key.name === 'd') {\n setCopyStatus('idle')\n if (pending === 'delete')\n commitDelete()\n else\n setPending('delete')\n return\n }\n if (key.name === 'c') {\n setPending(null)\n handleCopy()\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={`turn ${index} / ${total} · ${turn.role}`}\n bottomTitle={bottomTitle}\n maxHeight={MAX_MODAL_HEIGHT}\n disableEscape={pending !== null}\n >\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 <text fg={COLOR.dim}>\n <span fg={COLOR.mute}>blocks </span>\n <span fg={COLOR.dim}>{summary}</span>\n </text>\n\n {/*\n Preview pane — bordered + scrollable so a long assistant reply\n doesn't spill over the modal's geometry. `flexGrow: 1` lets the\n scrollbox absorb the variable remaining space between the info\n header above and the action footer below.\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 <ActionRow\n pending={pending}\n copyStatus={copyStatus}\n canCopy={fullText.length > 0}\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}: {\n pending: 'fork' | 'delete' | null\n copyStatus: 'idle' | 'copied' | 'failed'\n canCopy: boolean\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}>f</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}>d</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}>f</span>\n {' fork · '}\n <span fg={COLOR.warn}>d</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 <span fg={COLOR.warn}>f</span>\n {' fork · '}\n <span fg={COLOR.warn}>d</span>\n {' delete · '}\n <span fg={canCopy ? COLOR.warn : COLOR.mute}>c</span>\n {canCopy ? ' copy · ' : ' (nothing to copy) · '}\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 { FileEntry } from '../chat/files-discovery'\nimport type { DiscoveredMcp } from '../chat/mcps-discovery'\nimport type { SessionExportFormat } from '../chat/session-export'\nimport type { Picked, Screen, SessionMeta, Settings, StreamEvent } from '../chat/types'\nimport type { Session, SessionData } from '../session'\nimport type { SkillConfig } from '../skills'\nimport type { SessionTurn } from '../types'\nimport type { ContextUsage, Hint } from './components'\nimport { useKeyboard, useRenderer } from '@opentui/react'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { createAgent } from '../agent'\nimport { createFilesCompletionProvider } from '../chat/completion-files'\nimport { createSkillsCompletionProvider, uniqueSkillNamesFromReferences } from '../chat/completion-skills'\nimport { ConfigProvider, useConfig } from '../chat/config-context'\nimport { listProjectFiles } from '../chat/files-discovery'\nimport { generateSessionTitle } from '../chat/generate-title'\nimport { buildMcpServers, discoverProjectMcps } from '../chat/mcps-discovery'\nimport { findGitRoot } from '../chat/project-root'\nimport { getContextWindow } 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 { DEFAULT_SETTINGS, SettingsProvider, useSettings } from '../chat/settings-context'\nimport { buildSkillsConfig, defaultSkillScanPaths, discoverProjectSkills } from '../chat/skills-discovery'\nimport {\n deriveSessionTitle,\n eventsFromTurns,\n lastContextSizeFromTurns,\n listSessionMeta,\n selectableTurnIds,\n stripSpawnTokensLine,\n toolCallPreview,\n toolResultText,\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 { createSession, loadSession } from '../session'\nimport { formatTokenUsage } from '../stats'\nimport { accentColor } from './agent-picker'\nimport { Footer } from './components'\nimport { McpsSettingsModal } from './mcps-settings'\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 { SkillsSettingsModal } from './skills-settings'\nimport { ChipStyleProvider, MdStyleProvider } from './theme'\nimport { TurnDetailsModal } from './turn-details-modal'\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 * 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 <ModalRoot>\n <AppShell />\n </ModalRoot>\n </SafeModeProvider>\n </ChipStyleProvider>\n </MdStyleProvider>\n </ThemeProvider>\n )\n}\n\nfunction AppShell() {\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 // `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\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 } = config\n const lastResumedSessionId = initialState.lastSessionId\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 // `process.cwd()` is the project key in `projects.json` (the\n // safelist). Captured once per AppShell mount with `useState` lazy\n // init — `useMemo([])` would technically allow React to recompute\n // under memory pressure, `useState` is a hard guarantee.\n const [projectDir] = useState(() => process.cwd())\n // Project root that scopes the SESSIONS list — git root walked up\n // from `cwd` if we're in a repo, else cwd itself. Two sessions\n // launched from `repo/` and `repo/subdir/foo` therefore land in the\n // same scope, which is what the user expects (\"show me sessions for\n // THIS project, not THIS directory\").\n //\n // Distinct from `projectDir` above (which still keys the safelist by\n // exact cwd — that's a different concept and intentionally not\n // git-walked).\n const [sessionProjectRoot] = useState(() => findGitRoot(process.cwd()) ?? process.cwd())\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 // Skills + MCPs discovery — runs once per `prefix + projectDir`.\n //\n // Skills: parsed `SkillConfig[]` from the project + user scan paths. The\n // catalog is read-only state; user-toggled allowlist lives in\n // `settings.enabledSkills` and is composed into the agent's\n // `SkillsConfig` at `buildAgent` time.\n //\n // MCPs: parsed `DiscoveredMcp[]` from `.{prefix}/mcps.json` /\n // `.agents/mcps.json`. Same shape — `settings.enabledMcps` filters the\n // discovered list before the agent receives `mcpServers`.\n //\n // Errors during discovery are swallowed (logged under ZIDANE_DEBUG) so a\n // single bad SKILL.md or mcps.json doesn't keep the user out of the TUI.\n const [skillsCatalog, setSkillsCatalog] = useState<readonly SkillConfig[]>([])\n const [mcpsCatalog, setMcpsCatalog] = useState<readonly DiscoveredMcp[]>([])\n const [filesCatalog, setFilesCatalog] = useState<readonly FileEntry[]>([])\n useEffect(() => {\n const ac = new AbortController()\n let cancelled = false\n void (async () => {\n try {\n const skills = await discoverProjectSkills({ cwd: projectDir, prefix: config.prefix })\n if (!cancelled)\n setSkillsCatalog(skills)\n }\n catch (err) {\n debugLog('discoverProjectSkills failed', err)\n }\n })()\n void (async () => {\n try {\n const files = await listProjectFiles({ cwd: projectDir, signal: ac.signal })\n if (!cancelled)\n setFilesCatalog(files)\n }\n catch (err) {\n debugLog('listProjectFiles failed', err)\n }\n })()\n try {\n setMcpsCatalog(discoverProjectMcps({ cwd: projectDir, prefix: config.prefix }))\n }\n catch (err) {\n debugLog('discoverProjectMcps failed', err)\n }\n return () => {\n cancelled = true\n ac.abort()\n }\n }, [projectDir, config.prefix])\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 const completionProviders = useMemo(\n () => [\n createSkillsCompletionProvider({\n getCatalog: () => skillsCatalogRef.current,\n getEnabled: () => enabledSkillsRef.current,\n }),\n createFilesCompletionProvider({\n getCatalog: () => filesCatalogRef.current,\n }),\n ] as const,\n [],\n )\n\n /**\n * Single source of truth for \"should this call execute?\". Returns true to\n * let the call through, false to refuse it. Handles 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\").\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 (tool: string, input: Record<string, unknown>): Promise<boolean> => {\n if (!safeModeEnabledRef.current)\n return true\n if (isOnSafelist(readSafelist(), tool, input))\n return true\n const decision = await requestApproval(tool, input)\n if (decision === 'deny')\n return false\n if (decision === 'accept-safelist') {\n const entry = suggestSafelistEntry(tool, input)\n addToSafelist(dataDir, projectDir, entry)\n safelistRef.current = null // force re-read on next gate\n }\n return true\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 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 /** Token count from the most recent assistant turn (caching-aware). */\n const [lastInputTokens, setLastInputTokens] = useState(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 const agentRef = useRef<Agent | null>(null)\n const sessionRef = useRef<Session | null>(null)\n\n const stream = useStreamBuffer(setEvents)\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 return { provider, model }\n }, [providerRegistry, initialState])\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 const agent = createAgent({\n ...profile.preset,\n skills: { ...skillsConfig, ...(profile.preset.skills ?? {}) },\n mcpServers: [...projectMcps, ...(profile.preset.mcpServers ?? [])],\n provider: descriptor.factory(),\n session,\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: …` and adapts.\n const applyGate = async (\n name: string,\n input: Record<string, unknown>,\n ctx: { block: boolean, reason: string },\n ): Promise<void> => {\n if (ctx.block) // already refused by a higher-priority gate (skills/budgets/dedup)\n return\n if (!(await gateDecision(name, input))) {\n ctx.block = true\n ctx.reason = 'User denied this tool call'\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 // 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', ({ name, input, turnId }) => {\n stream.appendImmediate({ kind: 'tool', text: toolCallPreview(name, input), tool: name, turnId })\n })\n agent.hooks.hook('tool:after', ({ name, result, turnId }) => {\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, turnId })\n })\n agent.hooks.hook('mcp:tool:after', ({ displayName, result, turnId }) => {\n stream.appendImmediate({ kind: 'tool-result', text: toolResultText(result), tool: displayName, turnId })\n })\n agent.hooks.hook('turn:after', ({ usage }) => {\n if (usage)\n setLastInputTokens(turnContextSize(usage))\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n })\n\n // Subagent streams ----------------------------------------------------\n agent.hooks.hook('spawn:before', ({ id, task, depth }) => {\n const taskPreview = task.length > 80 ? `${task.slice(0, 80)}…` : task\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', ({ name, input, childId, depth, turnId }) => {\n stream.appendImmediate({\n kind: 'tool',\n text: toolCallPreview(name, input),\n tool: name,\n childId,\n depth,\n turnId,\n })\n })\n agent.hooks.hook('child:tool:after', ({ name, result, childId, depth, turnId }) => {\n stream.appendImmediate({\n kind: 'tool-result',\n text: toolResultText(result),\n tool: name,\n childId,\n depth,\n turnId,\n })\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 return agent\n }, [providerRegistry, stream, gateDecision, projectDir, config.prefix])\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: sessionProjectRoot }\n const list = await listSessionMeta(store, filter)\n setSessions(list)\n return list\n }, [store, settings.showAllProjects, sessionProjectRoot])\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 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 }, [stream, denyAll])\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: sessionProjectRoot, ...(id ? { id } : {}) })\n\n sessionRef.current = session\n agentRef.current = buildAgent(session, key)\n\n setEvents(eventsFromTurns(session.turns, session.runs))\n setLastInputTokens(lastContextSizeFromTurns(session.turns, 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 }, [teardown, buildAgent, store, stateStore, sessionProjectRoot])\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 if (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 === sessionProjectRoot)\n if (data && sessionMatchesProject) {\n await activateSession(lastResumedSessionId, resumeProvider.key)\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 else\n setScreen('sessions')\n })()\n return () => { cancelled = true }\n }, [activateSession, refreshSessions, resumeProvider, lastResumedSessionId, store, settings.showAllProjects, sessionProjectRoot])\n\n // -------------------------------------------------------------------------\n // Screen actions.\n // -------------------------------------------------------------------------\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 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])\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 any pending approvals so the run can unwind cleanly; otherwise\n // `agent.abort()` only flips the run's signal and our gate handlers\n // would still hang in `await requestApproval(...)`.\n denyAll()\n agentRef.current?.abort()\n }, [denyAll])\n\n const onPickModel = useCallback((modelId: string) => {\n setPicked((prev) => {\n if (!prev)\n return prev\n // Remember per-provider so the next launch resumes the right model.\n const prior = stateStore.load()\n stateStore.save({\n ...prior,\n lastModelByProvider: { ...prior.lastModelByProvider, [prev.provider.key]: modelId },\n })\n return { ...prev, model: modelId }\n })\n modal.close()\n }, [modal, stateStore])\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 const onSubmitPrompt = useCallback(async (prompt: string, references: readonly CompletionReference<unknown>[]) => {\n const agent = agentRef.current\n const session = sessionRef.current\n if (!agent || !session || !picked || !prompt.trim())\n return\n\n if (eventsLengthRef.current > 0)\n stream.appendImmediate({ kind: 'separator', text: '' })\n // Skill / file refs highlight chips in the echoed prompt. Both the\n // text and the offsets stay raw — the renderer prepends the `❯ `\n // chevron without touching ref spans.\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 stream.appendImmediate({\n kind: 'user-prompt',\n text: prompt,\n ...(refSpans.length > 0 ? { refs: refSpans } : {}),\n })\n setBusy(true)\n\n // Activate every skill referenced via slash-commands BEFORE the run\n // so the catalog injects the body into the system prompt on the\n // first turn. Idempotent — `activateSkill` is safe on already-active\n // skills, and auto-resolves the catalog on first call.\n //\n // The popup-driven path can't produce unknown-skill names (the\n // completion catalog gates which `/name` tokens become refs), and\n // skills are not disabled at runtime — so failure here is exotic\n // (e.g. `maxActive` cap). Log under ZIDANE_DEBUG; don't pollute the\n // transcript with errors the user can't act on.\n const skillNames = uniqueSkillNamesFromReferences(references)\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 await agent.run({ model: picked.model, prompt })\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 }\n catch (err) {\n stream.appendImmediate({ kind: 'error', text: err instanceof Error ? err.message : String(err) })\n }\n finally {\n stream.flushAndUpdate(finalizeStreamingMarkdown)\n setBusy(false)\n }\n }, [picked, stream])\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 // Head of the safe-mode approval queue. `null` means \"nothing pending\"\n // and the chat screen shows the prompt input as usual. Defined here so the\n // keyboard handler and the footer hints both see it.\n const pendingApproval = queue[0] ?? null\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 // Settings → Skills / MCPs sub-modals. Both reuse `modal.open` so the\n // single overlay slot stays consistent (esc closes whichever is on top\n // by replacement, not by stacking).\n const onOpenSkillsSettings = useCallback(() => {\n modal.open(<SkillsSettingsModal catalog={skillsCatalog} />)\n }, [modal, skillsCatalog])\n const onOpenMcpsSettings = useCallback(() => {\n modal.open(<McpsSettingsModal catalog={mcpsCatalog} />)\n }, [modal, mcpsCatalog])\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 const turnIds = useMemo(() => selectableTurnIds(events), [events])\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 const nextIdx = Math.max(0, Math.min(turnIds.length - 1, idx + direction))\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 setLastInputTokens(lastContextSizeFromTurns(session.turns, 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 // -------------------------------------------------------------------------\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 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])\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 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 actions={{\n onDelete: onDeleteSession,\n onExport: onExportSession,\n ...(picked ? { onGenerateTitle } : {}),\n }}\n />,\n )\n }, [modal, store, currentSession, onDeleteSession, picked, onGenerateTitle, onExportSession])\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 }}\n />,\n )\n }, [modal, selectedTurnId, turnIds, onForkTurn, onDeleteTurn])\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+, doesn't open Settings mid-\n // selection — the user can exit first if they want a global action.\n return\n }\n if (key.ctrl && key.name === ',' && screen !== 'auth') {\n modal.open(<SettingsModal actions={{ onReauth, onOpenSkills: onOpenSkillsSettings, onOpenMcps: onOpenMcpsSettings }} />)\n return\n }\n // Ctrl+X — open the session details modal. Targets the active\n // session on the chat screen, or the focused row in the sessions\n // list. Gated on the chat side by `!busy && !pendingApproval` so\n // a live run isn't interrupted mid-stream by the modal stealing\n // focus + handlers. On the sessions screen the modal is read-mostly\n // (delete confirms first) so no run-state gate is needed there.\n if (key.ctrl && key.name === 'x') {\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 (key.ctrl && key.name === 'm' && screen === 'chat' && picked && !busy) {\n modal.open(\n <ModelPickerModal\n models={modelsFor(picked.provider.key)}\n currentModelId={picked.model}\n onPick={onPickModel}\n />,\n )\n return\n }\n // Ctrl+S — enter select-turn mode. Only on the chat screen, only when\n // idle (a streaming turn would have a moving `turnIds` tail under our\n // feet — disrupting the muscle memory of \"I just selected this turn\").\n // No-op when the session has no turns yet.\n if (key.ctrl && key.name === 's' && screen === 'chat' && !busy && !pendingApproval) {\n enterSelectMode()\n return\n }\n // Shift+Tab — quick-cycle through agent profiles. Gated on chat screen,\n // idle state, and a multi-profile registry so the textarea never sees it\n // as input and single-agent setups don't get a no-op binding. Ctrl+A is\n // intentionally NOT bound at the global level: it's the textarea's\n // select-all (see `makeSubmitBindings` in screens.tsx) — same convention\n // every modern editor follows.\n if (key.shift && key.name === 'tab' && screen === 'chat' && hasMultipleAgents && !busy) {\n void onCycleAgent()\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 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 const hints: Hint[] = useMemo(\n () => buildHints({\n screen,\n busy,\n pending: !!pendingApproval,\n currentSession,\n hasMultipleAgents,\n modelLabel: picked?.model ?? null,\n modelColor: COLOR.model,\n agentLabel: pickedAgent.label,\n agentColor: accentColor(pickedAgent.accent, COLOR),\n }),\n [screen, busy, pendingApproval, currentSession, hasMultipleAgents, picked, pickedAgent, COLOR],\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 const promptTriggerHints: Hint[] = useMemo(() => {\n const out: Hint[] = []\n if (filesCatalog.length > 0) {\n out.push({\n key: '@',\n label: 'files',\n keyColor: resolveChipColor(SURFACE.chips, 'files').bg,\n })\n }\n if (skillsCatalog.length > 0) {\n out.push({\n key: '/',\n label: 'skills',\n keyColor: resolveChipColor(SURFACE.chips, 'skills').bg,\n })\n }\n return out\n }, [filesCatalog, skillsCatalog, 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 // Drop the agent + clear timers when the app unmounts.\n useEffect(() => () => { void teardown() }, [teardown])\n\n return (\n <box style={{ flexDirection: 'column', flexGrow: 1 }}>\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={sessionProjectRoot}\n />\n )}\n {screen === 'chat' && (\n <ChatScreen\n events={events}\n busy={busy}\n settings={settings}\n onSubmit={onSubmitPrompt}\n session={currentSession}\n pending={pendingApproval}\n onApproval={resolveHead}\n completionProviders={completionProviders}\n onPopupOpenChange={onPopupOpenChange}\n selectedTurnId={selectedTurnId}\n promptTriggerHints={promptTriggerHints}\n />\n )}\n </box>\n <Footer hints={hints} context={contextUsage} />\n </box>\n )\n}\n\n/**\n * Build the footer's shortcut hints for the current screen. On the chat\n * screen the model id rides next to its `ctrl+m` shortcut and the agent\n * label rides next to `shift+tab`, each in its accent color — the bar\n * doubles as the status display without needing separate badges.\n */\nfunction buildHints({\n screen,\n busy,\n pending,\n currentSession,\n hasMultipleAgents,\n modelLabel,\n modelColor,\n agentLabel,\n agentColor,\n}: {\n screen: Screen\n busy: boolean\n pending: boolean\n currentSession: SessionMeta | null\n hasMultipleAgents: boolean\n modelLabel: string | null\n modelColor: string\n agentLabel: string\n agentColor: string\n}): Hint[] {\n if (pending)\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'abort run' }]\n if (busy)\n return [{ key: 'esc', label: 'abort' }]\n if (screen === 'auth')\n return [{ key: '↑↓', label: 'navigate' }, { key: '↵', label: 'select' }, { key: 'esc', label: 'exit' }]\n if (screen === 'sessions') {\n return [\n { key: '↑↓', label: 'navigate' },\n { key: '↵', label: 'open' },\n { key: 'ctrl+x', label: 'session' },\n { key: 'ctrl+,', label: 'settings' },\n { key: 'esc', label: currentSession ? 'back' : 'exit' },\n ]\n }\n // Chat screen. Prompt-input shortcuts (`↵ send`, `shift+↵ newline`,\n // `↑↓ history`) live in the prompt box's overlay title — the bottom\n // bar only carries agent / model / global affordances. `shift+tab`\n // cycles agent profiles with the active label inline; `ctrl+m` opens\n // the model picker with the active model id inline. Ctrl+A opens the\n // descriptive agent picker for power users but isn't advertised here\n // to keep the hint row short. `shift+tab` is suppressed when there's\n // only one profile to switch between. `ctrl+x` opens the active\n // session's details / actions modal — only meaningful once a session\n // exists, hence the `currentSession` gate.\n return [\n ...(hasMultipleAgents ? [{ key: 'shift+tab', label: agentLabel, labelColor: agentColor }] : []),\n ...(modelLabel ? [{ key: 'ctrl+m', label: modelLabel, labelColor: modelColor }] : []),\n ...(currentSession ? [{ key: 'ctrl+x', label: 'session' }] : []),\n { key: 'ctrl+,', label: 'settings' },\n { key: 'esc', label: 'sessions' },\n ]\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 *\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\nlet registered = false\n\n/**\n * Register the extra Tree-sitter parsers + start the worker. Idempotent —\n * subsequent calls are no-ops. Safe to invoke from `runTui()` and from\n * composition hosts that mount `<App>` directly.\n */\nexport async function setupTreeSitter(): Promise<void> {\n if (registered)\n return\n registered = true\n addDefaultParsers(EXTRA_PARSERS)\n // Initialize the global client. The actual grammar `.wasm` downloads are\n // lazy — they happen on the first fence of each language — but the worker\n // needs to be alive before any markdown renders.\n await getTreeSitterClient().initialize()\n}\n","/** @jsxImportSource @opentui/react */\nimport type { ChatOptions } from '../chat/config'\nimport { createCliRenderer } from '@opentui/core'\nimport { createRoot } from '@opentui/react'\nimport { resolveConfig } from '../chat/config'\nimport { createTuiStore } from '../session/sqlite'\nimport { App } from './app'\nimport { setupTreeSitter } 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 *\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\n // Register extra Tree-sitter parsers (python, bash, json, rust, go,\n // yaml, html, css) so fenced code blocks in those languages get\n // syntax highlighting. The grammar `.wasm` files are downloaded\n // lazily on first use and cached under OpenTUI's data path. Swallow\n // init errors — if the worker can't start (offline, sandboxed FS),\n // the renderer still works, those fences just render as plain text.\n await setupTreeSitter().catch((err) => {\n const cause = err instanceof Error ? err.message : String(err)\n process.stderr.write(`[zidane/tui] tree-sitter setup failed: ${cause}\\n`)\n })\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\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 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 })\n\n createRoot(renderer).render(<App config={config} />)\n\n await exited\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// `splitPromptSegments` + `PromptSegment` live in `zidane/chat` so a GUI\n// shell can reuse the chip splitter without pulling OpenTUI. Re-exported\n// here for back-compat with TUI consumers that imported it from `zidane/tui`.\nexport { type PromptSegment, type PromptSegmentRef, splitPromptSegments } from '../chat/prompt-segments'\nexport { accentColor, AgentPickerModal } from './agent-picker'\nexport { App } from './app'\nexport { CompletionPopup } from './completion-popup'\nexport {\n type ContextUsage,\n Footer,\n type Hint,\n hintsLength,\n isVisible,\n marginTopFor,\n onInputSubmit,\n renderHintSpans,\n Spinner,\n Transcript,\n} from './components'\nexport { McpsSettingsModal } from './mcps-settings'\nexport { Modal, type ModalProps, ModalRoot, useModal, useModalAwareFocus } from './modal'\nexport { ModelPickerModal } from './model-picker'\nexport { AuthScreen, ChatScreen, SessionsScreen } from './screens'\nexport { type SettingsActions, SettingsModal } from './settings-modal'\nexport { SkillsSettingsModal } from './skills-settings'\nexport { buildMdStyle, useMdStyle } from './theme'\nexport { ToggleListModal } from './toggle-list-modal'\n"],"mappings":"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"}
|