2552579912
* fix(tui_gateway): ask before queueing a guarded model picked mid-turn config.set model on a running session cannot swap the agent in place, so it stashes the pick in session["pending_model_switch"] and applies it at the next turn start. That branch answered confirm_required=False without ever running the selection guards. A client that implements the confirm round-trip was therefore told no consent was needed and never prompted. One turn later _apply_pending_model_switch ran the guards with the stashed (unconfirmed) flag, saw the warning, and dropped the switch by design. The model reverted with no confirm ever offered, because the only moment a round-trip was possible had already passed. Evaluate the guards before stashing, where the client still has a live response to turn into a prompt. Nothing is queued for an unconfirmed guarded pick, so the session is left exactly as it was and the re-send carrying confirm_expensive_model queues it for real. The apply-time check stays as the backstop for guards that can only decide after resolution. The data-policy guard keys on the model id alone, which is all this branch can see before resolution. The cost guard returns None when pricing is unknown and its models.dev lookup is allow_network=False, so calling it early can only under-fire and never blocks the RPC thread. * test(tui_gateway): pin provider forwarding, name the canonical confirm field Two review follow-ups, no behavior change. _pending_switch_selection_warning forwards `provider=provider or None`, but nothing asserted it: a guarded model id fires the data-policy guard on the model alone, so the existing tests passed with `provider` dropped entirely. Record the kwargs instead. Dropping the argument fails the first test; removing the `or None` normalization fails the second. The confirm responses carry `warning` and `confirm_message` with identical text, which reads like an accident. Name which one clients should read (`confirm_message`; `warning` is the pre-confirm-era alias that _apply_pending_model_switch already treats as a fallback) so the two do not drift apart later. Both raised by @Enough1122 in review.