Input layer for the OC Chat REPL (Phase 2 PR 2.A + 2.B).
Purpose
-------
:class:`PromptDriver` is the single entry point for reading the next
user line. At construction it tries to import ``prompt_toolkit``; if
that succeeds (and ``OC_CHAT_LEGACY_PROMPT`` is not set), it builds a
:class:`prompt_toolkit.PromptSession` with file-backed history,
context-aware tab completion (see
:class:`~oc_ui.terminal.completers.OCChatCompleter`), an
``Alt+Enter`` keybinding that inserts a literal newline, a
``bottom_toolbar`` showing live session stats, an ``rprompt`` showing
token count, and a ``Ctrl+P`` binding that opens a fuzzy command
palette. Otherwise it falls back to plain :func:`input` — same
prompt format, no behaviour change.
The driver runs **between turns only**. Rich's ``Live`` region owns
the terminal during streaming; ``prompt_toolkit`` owns it during
input. They never overlap, so the two libraries coexist without
fighting over the screen.
Responsibilities
----------------
- :class:`PromptDriver` — wraps prompt-toolkit (or legacy
:func:`input`) and exposes a single ``read_line`` call.
- :func:`build_prompt_prefix` — formats the ``[<model>] You: `` string used by
both paths. The session NAME is deliberately not in the typing line; it is
shown on the bottom status toolbar instead (:func:`_build_bottom_toolbar`).
Both surfaces report whichever session ``read_user_input`` is handed, so the
caller must pass the CURRENT session — ``ctx.session``, never a loop-local
bound at startup, which a slash command may since have replaced (plan 152 D7).
- :class:`TurnStats` — thread-safe shared state used to communicate
token counts from the streaming thread to the rprompt.
Diagnostics
-----------
Domain: OC_UI.TERMINAL
Levels:
L2 — lifecycle events
L3 — semantic details
L4 — deep tracing
Contracts
---------
- ``PromptDriver.read_line`` is blocking; the caller is responsible
for backgrounding it if needed.
- **Chat prompts are MULTI-LINE (multiline-prompt fix, 2026-07-18):**
when ``read_user_input`` is called without a ``prompt_text`` override
(the main chat prompt), Enter inserts a newline and submission is
**Ctrl+Enter** (arrives as ``c-j`` on Windows consoles — incl.
Windows Terminal / VS Code via ConPTY), with ``Ctrl+J`` and
``Alt+Enter`` as the portable equivalents on terminals that cannot
distinguish Ctrl+Enter from Enter. A multi-line paste therefore lands
in the buffer as ONE prompt — nothing auto-submits. Up/Down move the
cursor within a multi-line buffer and navigate history from the
first/last line (prompt_toolkit ``auto_up``/``auto_down`` defaults).
- **Menu/startup prompts stay single-line:** any call passing
``prompt_text=`` keeps the classic Enter-submits behaviour, so
numbered menus answer on a single keypress+Enter as before.
- ``TurnStats`` mutations are all guarded by an internal lock; reads
return a consistent snapshot.
- The legacy ``input()`` fallback path remains SINGLE-line (Python's
``input`` cannot compose multi-line); it is otherwise functionally
equivalent.
- ``OC_CHAT_LEGACY_PROMPT`` is honoured at construction; toggling it
mid-session has no effect.
Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_ui.terminal.statusline (StatusBarState, render_status_line and token formatting for the bottom toolbar and rprompt)
- oc_ui.terminal.turn_layout (TURN_INDENT for the prompt prefix)
- oc_chat.core.cache_usage (lazy; cache hit ratio and prompt cache hit/miss split for turn stats)
- oc_chat.core.commands (lazy; COMMANDS for the Ctrl+P command palette)
- oc_chat.core.persistence (lazy; list_sessions for the Ctrl+P command palette)
- oc_chat.core.session (lazy; ChatSession type hint for the prompt prefix and read_user_input)
- oc_ui.terminal.completers (lazy; OCChatCompleter for tab completion)
- prompt_toolkit (lazy; PromptSession for the interactive prompt)
- prompt_toolkit.completion (lazy; FuzzyCompleter and WordCompleter for the Ctrl+P palette)
- prompt_toolkit.filters (lazy; Condition gating the multi-line chat key bindings)
- prompt_toolkit.formatted_text (lazy; FormattedText for the coloured chat prompt)
- prompt_toolkit.history (lazy; FileHistory for persistent input history)
- prompt_toolkit.key_binding (lazy; KeyBindings for the newline, submit, cancel and palette keys)