Skip to content

feat(tui): reach the model picker without wrecking your draft, and switch mid-turn - #74756

Merged
OutThisLife merged 2 commits into
mainfrom
bb/tui-model-hotkey
Jul 30, 2026
Merged

feat(tui): reach the model picker without wrecking your draft, and switch mid-turn#74756
OutThisLife merged 2 commits into
mainfrom
bb/tui-model-hotkey

Conversation

@OutThisLife

Copy link
Copy Markdown
Collaborator

Two friction points when changing model in the TUI: you had to type /model (wiping whatever you'd drafted) to open the picker, and picking a model mid-turn was rejected outright with a session busy error.

Summary

  • Ctrl+O opens the model picker directly, leaving your composer draft intact — no more clearing what you typed just to run /model. Added to the textInput pass-through allowlist so the composer doesn't eat it, mirroring the existing Ctrl+X session-switcher.
  • A model picked mid-turn now applies at the next turn instead of hitting 4009 session busy. switch_model() mutates the live agent's model/provider/base_url/client in place, which races the worker thread mid-request — so instead of rejecting, the pick is queued (session["pending_model_switch"]) and applied on the turn thread at the next turn start, before any model call. No race, no interrupt, no waiting on the client rebuild. The pick paints optimistically with an (applies next turn) note.

Binding is Ctrl+O (not the desktop's Cmd+Shift+M): Cmd and Ctrl+Shift chords don't reach Terminal.app, plain Ctrl+letter does — same reason voice defaults to Ctrl+B.

Test plan

  • test_config_set_model_defers_while_running — mid-turn pick queues, doesn't switch in place
  • test_apply_pending_model_switch_runs_queued_pick — queued pick applied once at turn start, then cleared
  • test_config_set_model_allowed_when_idle — idle switch still immediate
  • textInput pass-through + useInputHandlers vitest suites green
  • tsc + full test_tui_gateway_server.py (495) green

Reaching the model picker meant typing /model, which forces you to wipe
whatever you'd already drafted. Bind Ctrl+O to open the same picker overlay
directly, leaving the composer untouched. Ctrl+O is added to the textInput
pass-through allowlist so the composer doesn't swallow it, mirroring the
existing Ctrl+X session-switcher path.
…rejecting it

Picking a model while a turn was streaming hit a 4009 'session busy' reject:
switch_model() mutates the agent's model/provider/base_url/client in place and
the worker thread reads those every iteration. Now config.set queues the pick
in session[pending_model_switch] and _apply_pending_model_switch applies it on
the turn thread at the next turn start, before any model call — no race, no
interrupt, no waiting on the client rebuild. The TUI paints the pick optimistically
and notes '(applies next turn)'.
@OutThisLife
OutThisLife enabled auto-merge (squash) July 30, 2026 10:10
@github-actions

github-actions Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

૮ >ﻌ< ა ci review

ran on 88c31fe

ℹ️ Info

Desktop E2E visual evidence · View test artifacts · View job

1 visual diff.

inline evidence is publishing...

@OutThisLife
OutThisLife merged commit f27d45e into main Jul 30, 2026
45 checks passed
@OutThisLife
OutThisLife deleted the bb/tui-model-hotkey branch July 30, 2026 10:18
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/tui Terminal UI (ui-tui/ + tui_gateway/) labels Jul 30, 2026
OutThisLife added a commit that referenced this pull request Jul 30, 2026
The gateway now queues a model switch made during a turn and applies it
at the next turn start (#74756), but the desktop still bounced the pill
back to the old model: the post-switch refetch answered with the model
still running and repainted over the pick.

Skip that refetch when the switch was deferred — the apply publishes
session.info when it lands, and that is what re-syncs every surface.
An older gateway that still refuses with 4009 keeps the pick too rather
than rolling back and toasting at a user who did nothing wrong; it is
what the next turn runs anyway. Real failures still roll back and report.

The 4009 predicate lives beside the other gateway-compat probes in
lib/gateway-rpc.
OutThisLife added a commit that referenced this pull request Jul 30, 2026
)

The gateway now queues a model switch made during a turn and applies it
at the next turn start (#74756), but the desktop still bounced the pill
back to the old model: the post-switch refetch answered with the model
still running and repainted over the pick.

Skip that refetch when the switch was deferred — the apply publishes
session.info when it lands, and that is what re-syncs every surface.
An older gateway that still refuses with 4009 keeps the pick too rather
than rolling back and toasting at a user who did nothing wrong; it is
what the next turn runs anyway. Real failures still roll back and report.

The 4009 predicate lives beside the other gateway-compat probes in
lib/gateway-rpc.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…itch mid-turn (NousResearch#74756)

* feat(tui): Ctrl+O opens the model picker without clearing your draft

Reaching the model picker meant typing /model, which forces you to wipe
whatever you'd already drafted. Bind Ctrl+O to open the same picker overlay
directly, leaving the composer untouched. Ctrl+O is added to the textInput
pass-through allowlist so the composer doesn't swallow it, mirroring the
existing Ctrl+X session-switcher path.

* feat(tui): apply a mid-turn model switch at the next turn instead of rejecting it

Picking a model while a turn was streaming hit a 4009 'session busy' reject:
switch_model() mutates the agent's model/provider/base_url/client in place and
the worker thread reads those every iteration. Now config.set queues the pick
in session[pending_model_switch] and _apply_pending_model_switch applies it on
the turn thread at the next turn start, before any model call — no race, no
interrupt, no waiting on the client rebuild. The TUI paints the pick optimistically
and notes '(applies next turn)'.
randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…sResearch#74759)

The gateway now queues a model switch made during a turn and applies it
at the next turn start (NousResearch#74756), but the desktop still bounced the pill
back to the old model: the post-switch refetch answered with the model
still running and repainted over the pick.

Skip that refetch when the switch was deferred — the apply publishes
session.info when it lands, and that is what re-syncs every surface.
An older gateway that still refuses with 4009 keeps the pick too rather
than rolling back and toasting at a user who did nothing wrong; it is
what the next turn runs anyway. Real failures still roll back and report.

The 4009 predicate lives beside the other gateway-compat probes in
lib/gateway-rpc.
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…itch mid-turn (NousResearch#74756)

* feat(tui): Ctrl+O opens the model picker without clearing your draft

Reaching the model picker meant typing /model, which forces you to wipe
whatever you'd already drafted. Bind Ctrl+O to open the same picker overlay
directly, leaving the composer untouched. Ctrl+O is added to the textInput
pass-through allowlist so the composer doesn't swallow it, mirroring the
existing Ctrl+X session-switcher path.

* feat(tui): apply a mid-turn model switch at the next turn instead of rejecting it

Picking a model while a turn was streaming hit a 4009 'session busy' reject:
switch_model() mutates the agent's model/provider/base_url/client in place and
the worker thread reads those every iteration. Now config.set queues the pick
in session[pending_model_switch] and _apply_pending_model_switch applies it on
the turn thread at the next turn start, before any model call — no race, no
interrupt, no waiting on the client rebuild. The TUI paints the pick optimistically
and notes '(applies next turn)'.
33hodl pushed a commit to 33hodl/hermes-agent that referenced this pull request Aug 12, 2026
…sResearch#74759)

The gateway now queues a model switch made during a turn and applies it
at the next turn start (NousResearch#74756), but the desktop still bounced the pill
back to the old model: the post-switch refetch answered with the model
still running and repainted over the pick.

Skip that refetch when the switch was deferred — the apply publishes
session.info when it lands, and that is what re-syncs every surface.
An older gateway that still refuses with 4009 keeps the pick too rather
than rolling back and toasting at a user who did nothing wrong; it is
what the next turn runs anyway. Real failures still roll back and report.

The 4009 predicate lives beside the other gateway-compat probes in
lib/gateway-rpc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/tui Terminal UI (ui-tui/ + tui_gateway/) P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants