ask_user: structured mid-turn questions with option answers (plan 163).
Purpose
-------
Provides :func:`ask_user` so a worker can ask the operator a question
MID-turn and continue on the answer — the Claude-style question flow,
in-terminal. The answer returns as the tool result; the same ReAct
iteration continues.
The tool reads through a bridge (:func:`bind_interaction`) that the
orchestrator wires to the prompt driver at REPL startup. When unbound
(batch runner, benchmarks, tests) it returns an immediate refusal so
headless runs never block.
Responsibilities
----------------
- :func:`ask_user` — present a question with optional numbered options,
read one answer, validate, and return a structured dict.
- :func:`bind_interaction` — the bridge seam; stores a
``Callable[[str], str | None]`` that the orchestrator wires to
``prompt_driver.read_user_input`` at merge.
- Prompt rendering is internal; the operator sees a single-line menu
prompt (Enter-submits, no multiline chat contract).
Diagnostics
-----------
Domain: OC_CHAT.TOOLS
Levels:
L2 — lifecycle events
L3 — semantic details
L4 — deep tracing
Contracts
---------
- **Unbound → immediate refusal:** when ``_read is None``, returns
``{"answered": False, "reason": "no interactive operator in this
session mode"}`` without blocking. A tool that blocks a headless run
is a regression.
- **Option picks are 1-based:** the operator types a number; the result
carries ``choice_index`` (1-based) and ``choice`` (the option text).
- **Max 3 validation attempts:** an out-of-range pick re-prompts; after
3 failures the tool returns ``{"answered": False, "reason": "no
valid answer (max attempts exhausted)"}``. The model must handle a
refusal — never loop forever.
- **Ctrl+C → ``answered: False``:** when the bound ``read_fn`` returns
``None`` (the driver's cancel contract), the tool returns immediately
with ``reason: "operator cancelled (Ctrl+C)"``.
- **Free text when no options:** returns ``text`` in the result.
- **Free text with options + ``allow_free_text=True``:** a non-numeric
answer is treated as free text rather than an invalid pick.
- **The REPL wiring line is NOT this module's responsibility** (F14
pattern — the orchestrator wires at merge).
Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
See Also
--------
- oc_chat/docs/conventions/TOOL_CONTRACT.md §5 — per-tool reference.
- oc_chat/docs/conventions/CONTRACTS.md §3 — TOOLS / SCHEMAS shape.
- oc_ui/terminal/input.py:PromptDriver.read_user_input — the menu-path
signature this tool's bridge is wired to at merge.