Show the model and mode the session has, not the ones it was asked for

Picking either from the phone wrote the choice straight into the
session's state and then sent the request. Asking and having are
different things, and the difference is not rare: `auto` is a permission
mode the CLI accepts on the command line, silently resolves to
`default`, and refuses outright over the control channel -- "auto mode
unavailable for this model" -- so a session spawned in auto was in
default and one switched to auto stayed where it was, with the phone
reporting auto in both cases.

So the drivers report what they are set to and the manager follows that.
Measured, because the confirmations are not uniform: a model change
answers success with no value, so what was asked is remembered until the
answer arrives; a mode change echoes the mode it became, and that answer
wins over the request; and `init` names both -- resolving `haiku` to
claude-haiku-4-5-20251001 -- which also covers a session adopted from a
terminal that set them outside this app. A driver that cannot change
either already says so with an error, and now that error is the whole
story rather than a note beside a display that changed anyway.

The config keeps the requested value, deliberately: that answers a
different question, which is what to launch this session with next time.

Two things fall out. Control request ids are random rather than the
clock, because two in the same second shared an id and something now
looks them up. And the phone shortens a resolved name for the button --
`haiku-4-5` -- since the full one is what the CLI reports and roughly
twice the room that row has once Stop is in it.
This commit is contained in:
iris committed 2026-08-29 15:36:53 -04:00
1 parent 404066fa7d
commit 3eccf7e443
8 files changed
+359 -29

No files matched your search

+30 -6
View File
@@ -61,7 +61,7 @@ use super::driver::{Driver, Event, EventSink, ImageRef, SessionStatus};
use super::process;
use super::transport::{Launch, Streams, Transport};
use crate::config::{ProviderConfig, SessionConfig};
use translate::{AnswerOutcome, Translator};
use translate::{AnswerOutcome, Setting, Translator};
/// How much of a failing process's stderr the exit report carries.
///
@@ -387,8 +387,26 @@ impl ClaudeDriver {
let _ = self.to_child.send(line);
}
fn send_control(&self, request: Value) {
let id = format!("req-{}", super::now() as u64);
/// Sends a control request, remembering what it asked for.
///
/// `confirms` is the setting this request will have made if the CLI
/// answers success -- see [`Translator::expect_setting`], and
/// `Driver::set_model` for why a request is not a confirmation.
/// `None` for the ones that change no setting, like an interrupt.
///
/// The id is random rather than the clock it used to be: two requests
/// in the same second shared an id, which was harmless while nothing
/// looked one up and is not any more.
fn send_control(&self, request: Value, confirms: Option<Setting>) {
let id = format!("req-{}", super::random_hex());
if let Some(setting) = confirms {
// Before the line goes out: the reader thread is already
// running, and a fast answer to a slow lock arrives first.
self.state
.lock()
.unwrap()
.expect_setting(id.clone(), setting);
}
self.send_line(
json!({"type": "control_request", "request_id": id, "request": request}).to_string(),
);
@@ -470,15 +488,21 @@ impl Driver for ClaudeDriver {
/// typed deliberately, and dropping it would lose a message that never
/// reached the transcript, with nothing on screen to say so.
fn interrupt(&self) {
self.send_control(json!({"subtype": "interrupt"}));
self.send_control(json!({"subtype": "interrupt"}), None);
}
fn set_permission_mode(&self, mode: &str) {
self.send_control(json!({"subtype": "set_permission_mode", "mode": mode}));
self.send_control(
json!({"subtype": "set_permission_mode", "mode": mode}),
Some(Setting::PermissionMode(mode.to_string())),
);
}
fn set_model(&self, model: &str) {
self.send_control(json!({"subtype": "set_model", "model": model}));
self.send_control(
json!({"subtype": "set_model", "model": model}),
Some(Setting::Model(model.to_string())),
);
}
fn compact(&self) {