From 899074f4e9897d5120c8025ffc466b46a4cb12a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 04:27:54 +0000 Subject: [PATCH 01/27] Sonny: host calls give up after 10 s; Herdr's small split above or left is small Every Rex, Herdr and cmux call Sonny makes waited on its CLI with no limit, from the lead's screen loop: a host that stopped answering froze Sonny. They now go through mux::output, which stops the CLI after 10 s. Herdr's ratio is the first pane's share and a swap keeps the shares in place, so a small split above or to the left came out at 65%. It now asks for 0.35 when it swaps. Usage text and comments name cmux beside Rex and Herdr. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/cmux.rs | 5 +-- sonny/src/herdr.rs | 73 ++++++++++++++++++++++++++++---------- sonny/src/main.rs | 14 ++++---- sonny/src/mux.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++++ sonny/src/rex.rs | 15 ++++---- 5 files changed, 157 insertions(+), 37 deletions(-) diff --git a/sonny/src/cmux.rs b/sonny/src/cmux.rs index 6e467a1da..126afe8a2 100644 --- a/sonny/src/cmux.rs +++ b/sonny/src/cmux.rs @@ -60,10 +60,7 @@ fn rpc(bin: &Path, method: &str, params: &Value) -> Result { for name in PLACE_VARS { command.env_remove(name); } - let out = command - .args(["rpc", method, ¶ms.to_string()]) - .stdin(Stdio::null()) - .output() + let out = crate::mux::output(command.args(["rpc", method, ¶ms.to_string()])) .map_err(|e| format!("cmux: {e}"))?; if !out.status.success() { // cmux says why, among others that its socket would not take this diff --git a/sonny/src/herdr.rs b/sonny/src/herdr.rs index 8af047303..9246a2381 100644 --- a/sonny/src/herdr.rs +++ b/sonny/src/herdr.rs @@ -39,17 +39,18 @@ pub fn pane() -> Option { /// reaches. pub fn has_lead(bin: &Path, project: &str) -> bool { let lead = format!("{project}.lead"); - Command::new(bin) - .env_remove("HERDR_PANE_ID") - .args(["pane", "list"]) - .output() - .ok() - .filter(|out| out.status.success()) - .and_then(|out| serde_json::from_slice::(&out.stdout).ok()) - .is_some_and(|reply| { - (reply["result"]["panes"].as_array().into_iter().flatten()) - .any(|p| p["label"] == lead.as_str()) - }) + crate::mux::output( + Command::new(bin) + .env_remove("HERDR_PANE_ID") + .args(["pane", "list"]), + ) + .ok() + .filter(|out| out.status.success()) + .and_then(|out| serde_json::from_slice::(&out.stdout).ok()) + .is_some_and(|reply| { + (reply["result"]["panes"].as_array().into_iter().flatten()) + .any(|p| p["label"] == lead.as_str()) + }) } /// Sonny as an agent that supports Herdr (herdr.dev/docs/add-herdr-support), @@ -299,13 +300,14 @@ impl Herdr { /// The reply's `result`. Every call names what it acts on: the shell's /// own Herdr variables may name another pane (a daemon's). fn call(&self, args: &[&str]) -> Result { - let out = Command::new(&self.bin) - .env_remove("HERDR_PANE_ID") - .env_remove("HERDR_TAB_ID") - .env_remove("HERDR_WORKSPACE_ID") - .args(args) - .output() - .map_err(|e| format!("herdr: {e}"))?; + let out = crate::mux::output( + Command::new(&self.bin) + .env_remove("HERDR_PANE_ID") + .env_remove("HERDR_TAB_ID") + .env_remove("HERDR_WORKSPACE_ID") + .args(args), + ) + .map_err(|e| format!("herdr: {e}"))?; let what = args[..args.len().min(2)].join(" "); if !out.status.success() { let said = String::from_utf8_lossy(&out.stderr); @@ -499,8 +501,10 @@ impl Backend for Herdr { }; let cwd = cwd.to_string_lossy(); let mut args = vec!["pane", "split", anchor, "--direction", direction]; + // The ratio is the first pane's share, and a swap leaves the + // shares where they are: swapped, the new pane is the first. if small { - args.extend(["--ratio", "0.65"]); + args.extend(["--ratio", if swap { "0.35" } else { "0.65" }]); } if keep { args.extend(["--cwd", &cwd]); @@ -780,6 +784,37 @@ if out is not None: assert_eq!(labels(&dir), ["graft.lead", "graft.docs"]); } + #[test] + fn a_small_split_above_or_left_is_small_once_swapped() { + let (bin, dir) = fake( + "small", + json!({ + "n": 10, + "workspaces": [{"workspace_id": "w1", "label": "shop"}], + "panes": [ + {"pane_id": "w1:p1", "workspace_id": "w1", "tab_id": "w1:t1", "label": "shop.lead"}, + ], + }), + ); + let herdr = Herdr::open(bin, "shop", None, None).unwrap(); + let mut layout = Layout::new(Box::new(herdr)); + let serve = ["serve".to_owned()]; + layout + .split( + "shop.lead", + "server", + Path::new("/work"), + &serve, + Some("above"), + ) + .unwrap(); + let calls = calls(&dir); + let split = calls.iter().find(|c| c[..2] == ["pane", "split"]).unwrap(); + // Herdr gives the first pane the ratio; the new one is swapped there. + assert_eq!(split[3..7], ["--direction", "down", "--ratio", "0.35"]); + assert!(calls.iter().any(|c| c[..2] == ["pane", "swap"])); + } + #[test] fn sonny_finds_its_pane_after_the_pane_moved() { let (bin, dir) = fake( diff --git a/sonny/src/main.rs b/sonny/src/main.rs index b88fded71..8672b22f1 100644 --- a/sonny/src/main.rs +++ b/sonny/src/main.rs @@ -211,7 +211,7 @@ struct Options { new: bool, model: Option, workspace: Option, - /// Lay the project's tasks out in Rex or Herdr, whichever Sonny runs in. + /// Lay the project's tasks out in Rex, Herdr or cmux, whichever Sonny runs in. layout: bool, /// The Rex server the project's session lives on, when not this one. rex_host: Option, @@ -232,7 +232,7 @@ fn usage() -> String { \x20 [--agent NAME [--new [--workspace DIR]] | --lead NAME | --new-lead [NAME]]\n\ With no --agent, the folder's lead you last wrote to, its first made with --model the\n\ first time; --lead opens another of the folder's leads, --new-lead makes one. Its\n\ - tasks open beside it in Rex or Herdr, each in a git worktree of its own. Type /status\n\ + tasks open beside it in Rex, Herdr or cmux, each in a git worktree of its own. Type /status\n\ for the project's agents, /lead for the folder's leads.\n\ Agents start tasks, put windows and panes where the person looks, make projects\n\ and follow agents in the lookout with tools Sonny gives them, which its tool host answers.\n\ @@ -253,7 +253,7 @@ fn usage() -> String { /// asked. A task gets a window of its own unless told otherwise; `split` /// puts it beside the caller's block, or `beside` another bot's. Another /// project's lead gets its project's own home instead (a Rex session, a -/// Herdr workspace), running that lead's Sonny, unless it is there already. +/// Herdr or cmux workspace), running that lead's Sonny, unless it is there already. async fn show( caller: &str, bot: &str, @@ -988,8 +988,8 @@ async fn run() -> Result<(), String> { app.model = model.clone(); app.turn_model = Some(model.clone()); } - // Tasks are laid out in Rex when the person is in it (or names the - // Rex server to use); elsewhere they run out of sight, in /status. + // Tasks are laid out in the multiplexer the person runs Sonny in (or + // the Rex server named); elsewhere they run out of sight, in /status. if let Some(project) = &project && options.layout { @@ -2244,7 +2244,7 @@ struct App { suggestion: Option, } -/// The lead's tasks, laid out in the project's Rex session: a window for +/// The lead's tasks, laid out in the project's home in a multiplexer: a window for /// each task while it works, opened as its turn starts. It follows every /// bot on its own connection, so the lead's own view is not disturbed. struct Fleet { @@ -2264,7 +2264,7 @@ struct Fleet { leads: std::collections::HashSet, /// How a task's window runs Sonny on it, without the bot's name. command: Vec, - /// Said once in the status row when Rex is set up. + /// Said once in the status row when tasks open where this pane is not. note: Option, /// What each bot is doing, as its window says. doing: HashMap, diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index f7c1310e7..a87cf5392 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -6,9 +6,79 @@ //! says how, in its multiplexer's CLI. use std::{ collections::HashMap, + io::Read, path::{Path, PathBuf}, + process::{Command, Output, Stdio}, + sync::mpsc, + time::{Duration, Instant}, }; +/// How long a multiplexer's CLI may take to answer: one whose server has +/// stopped answering is given up on, rather than holding Sonny's screen. +const ANSWER: Duration = Duration::from_secs(10); + +/// `command`'s output, as `Command::output` gives it, unless the command +/// takes longer than [`ANSWER`]: then it is stopped, and that is the error. +pub fn output(command: &mut Command) -> std::io::Result { + output_within(command, ANSWER) +} + +fn output_within(command: &mut Command, limit: Duration) -> std::io::Result { + let mut child = command + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn()?; + // Each pipe read to its end on a thread of its own, so a full one + // never holds the other up. + fn read(pipe: Option) -> mpsc::Receiver> { + let (sent, read) = mpsc::channel(); + std::thread::spawn(move || { + let mut bytes = Vec::new(); + if let Some(mut pipe) = pipe { + let _ = pipe.read_to_end(&mut bytes); + } + let _ = sent.send(bytes); + }); + read + } + let (stdout, stderr) = (read(child.stdout.take()), read(child.stderr.take())); + let deadline = Instant::now() + limit; + let read = |pipe: &mpsc::Receiver>| { + pipe.recv_timeout(deadline.saturating_duration_since(Instant::now())) + .ok() + }; + let answered = 'answered: { + let (Some(stdout), Some(stderr)) = (read(&stdout), read(&stderr)) else { + break 'answered None; + }; + // Its output closed, the command has all but exited. + loop { + match child.try_wait() { + Ok(Some(status)) => { + break 'answered Some(Output { + status, + stdout, + stderr, + }); + } + Ok(None) if Instant::now() < deadline => { + std::thread::sleep(Duration::from_millis(1)) + } + _ => break 'answered None, + } + } + }; + answered.ok_or_else(|| { + let _ = child.kill(); + let _ = child.wait(); + std::io::Error::new( + std::io::ErrorKind::TimedOut, + format!("no answer in {limit:?}"), + ) + }) +} + /// A block as its multiplexer lists it. pub struct Block { pub id: String, @@ -568,6 +638,23 @@ mod tests { } } + #[test] + fn a_multiplexer_that_does_not_answer_is_given_up_on() { + let answered = + super::output(std::process::Command::new("sh").args(["-c", "echo hi; echo oh >&2"])) + .unwrap(); + assert!(answered.status.success()); + assert_eq!( + (&answered.stdout[..], &answered.stderr[..]), + (&b"hi\n"[..], &b"oh\n"[..]) + ); + let started = std::time::Instant::now(); + let mut hung = std::process::Command::new("sleep"); + let error = super::output_within(hung.arg("30"), std::time::Duration::from_millis(200)); + assert_eq!(error.unwrap_err().kind(), std::io::ErrorKind::TimedOut); + assert!(started.elapsed() < std::time::Duration::from_secs(5)); + } + #[test] fn a_project_keeps_the_place_it_was_laid_out_in() { let file = std::env::temp_dir().join(format!("sonny-place-{}.json", std::process::id())); diff --git a/sonny/src/rex.rs b/sonny/src/rex.rs index 7e70f60b0..8980944bd 100644 --- a/sonny/src/rex.rs +++ b/sonny/src/rex.rs @@ -46,13 +46,14 @@ impl Rex { fn call(&self, args: &[&str]) -> Result { // Every call names its session: the shell's own Rex variables may // name another (a daemon's), and rex would look a bare ID up there. - let out = Command::new(&self.bin) - .env_remove("REX_SESSION") - .env_remove("REX_BLOCK") - .args(&self.host) - .args(args) - .output() - .map_err(|e| format!("rex: {e}"))?; + let out = crate::mux::output( + Command::new(&self.bin) + .env_remove("REX_SESSION") + .env_remove("REX_BLOCK") + .args(&self.host) + .args(args), + ) + .map_err(|e| format!("rex: {e}"))?; if !out.status.success() { return Err(format!( "rex {}: {}", From c17e8596e2cb29efdaf1b25cd5d1f69a33a77708 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 04:28:31 +0000 Subject: [PATCH 02/27] FEATURES: host call limit and Herdr split (#489) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 0bf8b8912..2ba0f3ae6 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -62,7 +62,7 @@ per feature; group small things into the row they belong to. | Feature | Status | History | Why | Used? | |---|---|---|---|---| | Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166 | Watch and click into every agent from the multiplexer | ? | -| Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231 | George's other multiplexer | ? | +| Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489 | George's other multiplexer | ? | | cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323 | A third multiplexer to try | ? | | Sessions for leads, tabs for tasks, a split only to show something now (`sonny run`, `sonny show`) | shipped | `sonny run` renamed `sonny split` 10-26 #165; sessions and tabs 10-27 #200; no programs started while a window idles 10-29 #311 | Tabs are a job stream to click into; a split is for attention now | ? | From e59464910100eb83e39fc9e4750bd41121b3b321 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 04:48:21 +0000 Subject: [PATCH 03/27] Sonny: the layout runs on its own thread, so no host call holds up the screen A lead's tasks were laid out by blocking multiplexer CLI calls made inside the screen loop: about seven per task start, two or three per state change, and /status re-read places every two seconds. Each Rex call measures about 27 ms, so typing stalled for ~200 ms whenever a task started. mux::Worker owns the Layout on a thread of its own. Jobs run in order; a window naming that a later one follows is skipped; places are read in the background and /status reads the kept copy. What a job has to say comes back to the screen loop on a channel. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- sonny/src/main.rs | 247 +++++++++++++++++++++++++++++++--------------- sonny/src/mux.rs | 123 ++++++++++++++++++++++- 3 files changed, 289 insertions(+), 83 deletions(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 4caa367de..3ec9ee2e3 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -61,7 +61,7 @@ per feature; group small things into the row they belong to. | Feature | Status | History | Why | Used? | |---|---|---|---|---| -| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166 | Watch and click into every agent from the multiplexer | ? | +| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #PR | Watch and click into every agent from the multiplexer | ? | | Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489 | George's other multiplexer | ? | | cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323 | A third multiplexer to try | ? | | Sessions for leads, tabs for tasks, a split only to show something now (`sonny run`, `sonny show`) | shipped | `sonny run` renamed `sonny split` 10-26 #165; sessions and tabs 10-27 #200; no programs started while a window idles 10-29 #311 | Tabs are a job stream to click into; a split is for attention now | ? | diff --git a/sonny/src/main.rs b/sonny/src/main.rs index 079a3d084..be359bbc2 100644 --- a/sonny/src/main.rs +++ b/sonny/src/main.rs @@ -2254,7 +2254,12 @@ struct App { /// each task while it works, opened as its turn starts. It follows every /// bot on its own connection, so the lead's own view is not disturbed. struct Fleet { - layout: mux::Layout, + /// Worked on a thread of its own, so the multiplexer never holds up + /// the screen. + layout: mux::Worker, + /// What its jobs have to say in the lead's view, as they finish. + said: tokio::sync::mpsc::UnboundedReceiver, + tell: tokio::sync::mpsc::UnboundedSender, client: Arc, events: Events, socket: PathBuf, @@ -2274,8 +2279,8 @@ struct Fleet { note: Option, /// What each bot is doing, as its window says. doing: HashMap, - /// The name each window was given. - named: HashMap, + /// The name each window was given, kept by the worker. + named: Arc>>, /// What to ring for, taken by the app after each event. alerts: Vec<(sound::Alert, String)>, /// Tasks whose last turn completed, and that turn: its window closes @@ -2398,6 +2403,74 @@ fn lineage_of(bots: &[Value], lead: &str, first: bool) -> std::collections::Hash mine } +/// What a lead's windows are named from: its project, the lead, what +/// each of its bots is doing, and every bot of its lineage. +struct Naming { + project: String, + lead: String, + doing: HashMap, + lineage: std::collections::HashSet, +} + +impl Naming { + /// A bot's name in its project's session: `lead`, or the task's name. + fn short<'a>(&self, bot: &'a str) -> &'a str { + if bot == self.lead { + return "lead"; + } + bot.strip_prefix(&format!("{}.", self.project)) + .unwrap_or(bot) + } + + /// The bot as the person tells it apart from every other project's: + /// the lead by its project, as `shop lead`, a task by its own name. + fn named(&self, bot: &str) -> String { + match bot == self.lead { + true => lead_title(&self.project, bot), + false => self.short(bot).to_owned(), + } + } + + /// Whether a block is the project's: a bot's, or a command one asked + /// for (`BOT/NAME`). + fn owns(&self, label: &str) -> bool { + let bot = label.split_once('/').map_or(label, |(bot, _)| bot); + self.doing.contains_key(bot) || self.lineage.contains(bot) + } + + /// The name of a window holding the blocks labelled `labels`: after + /// the lead, or its first bot, and what that bot is doing, with a count + /// of the others that are working or need the person: `shop lead · + /// done · 2 working`. None for a window that also holds the person's + /// own blocks, as when Sonny was opened beside them, which keeps the + /// name they gave it. + fn window(&self, labels: &[String]) -> Option { + if !labels.iter().all(|label| self.owns(label)) { + return None; + } + let bots: Vec<&String> = labels + .iter() + .filter(|l| self.doing.contains_key(*l)) + .collect(); + let first = bots + .iter() + .find(|b| **b == &self.lead) + .or_else(|| bots.first()) + .copied()?; + let mut name = format!("{} · {}", self.named(first), self.doing[first]); + for state in ["needs you", "working"] { + let others = bots + .iter() + .filter(|b| **b != first && self.doing.get(**b) == Some(&state)) + .count(); + if others > 0 { + name.push_str(&format!(" · {others} {state}")); + } + } + Some(name) + } +} + impl Fleet { async fn start(options: &Options, project: &str, lead: &str) -> Option { let layout = mux::here(&options.socket, project, options.rex_host.as_deref())?; @@ -2421,8 +2494,11 @@ impl Fleet { // Said only when the tasks go somewhere this pane is not. let note = (!layout.here()).then(|| format!("tasks open in {}", layout.home())); mux::install_skill(options.rex_host.as_deref()); + let (tell, said) = tokio::sync::mpsc::unbounded_channel(); let mut fleet = Self { - layout, + layout: mux::Worker::new(layout), + said, + tell, client: client.clone(), events, socket: options.socket.clone(), @@ -2435,14 +2511,17 @@ impl Fleet { command, note, doing: HashMap::new(), - named: HashMap::new(), + named: Default::default(), alerts: Vec::new(), ended: HashMap::new(), waits: Default::default(), returned: Vec::new(), resolving: Default::default(), }; - fleet.layout.adopt_own(lead); + let own = lead.to_owned(); + fleet.layout.send(move |layout| layout.adopt_own(&own)); + // Read now, so `/status` has where each task sits when it opens. + let _ = fleet.layout.places(); fleet.doing(lead, "idle"); fleet.reconcile().await; Some(fleet) @@ -2501,7 +2580,7 @@ impl Fleet { // one that started makes the last ending stale. self.ended.remove(name); if running && name != self.lead { - let _ = self.show(name); + self.show(name, false); } else if !running && self .doing @@ -2514,13 +2593,35 @@ impl Fleet { self.name_windows(); } - fn show(&mut self, bot: &str) -> Result { + /// Open the task's window, unless it has one. With `tell`, the lead's + /// view hears where it opened, when not beside it, or why it did not. + fn show(&mut self, bot: &str, tell: bool) { let mut command = self.command.clone(); command.extend(["--agent".into(), bot.into()]); let name = format!("{} · working", self.short(bot)); - let opened = self.layout.show(bot, &name, &command)?; + let (bot_name, home, here) = ( + bot.to_owned(), + self.layout.home().to_owned(), + self.layout.here(), + ); + let told = self.tell.clone(); + self.layout.send(move |layout| { + let said = match layout.show(&bot_name, &name, &command) { + Ok(true) if !here => Some(format!("{bot_name} is in {home}")), + Ok(_) => None, + Err(error) => Some(error), + }; + if let Some(said) = said.filter(|_| tell) { + let _ = told.send(said); + } + }); self.doing(bot, "working"); - Ok(opened) + } + + /// Close the task's window, and every block it asked for. + fn close(&self, bot: &str) { + let bot = bot.to_owned(); + self.layout.send(move |layout| layout.close(&bot)); } /// A finished task's window closes once its result is taken: its turn @@ -2594,7 +2695,7 @@ impl Fleet { for (task, turn) in calls::returned(&printed) { if task != self.lead && self.ended.get(&task) == Some(&turn) { self.ended.remove(&task); - self.layout.close(&task); + self.close(&task); self.doing.remove(&task); self.name_windows(); } @@ -2609,7 +2710,7 @@ impl Fleet { && !matches!(self.doing.get(task), Some(&"working" | &"needs you")) { self.ended.remove(task); - self.layout.close(task); + self.close(task); self.doing.remove(task); self.name_windows(); } @@ -2617,19 +2718,16 @@ impl Fleet { /// A bot's name in its project's session: `lead`, or the task's name. fn short<'a>(&self, bot: &'a str) -> &'a str { - if bot == self.lead { - return "lead"; - } - bot.strip_prefix(&format!("{}.", self.project)) - .unwrap_or(bot) + self.naming().short(bot) } - /// The bot as the person tells it apart from every other project's: - /// the lead by its project, as `shop lead`, a task by its own name. - fn named(&self, bot: &str) -> String { - match bot == self.lead { - true => lead_title(&self.project, bot), - false => self.short(bot).to_owned(), + /// What names the windows, as things stand. + fn naming(&self) -> Naming { + Naming { + project: self.project.clone(), + lead: self.lead.clone(), + doing: self.doing.clone(), + lineage: self.lineage.clone(), } } @@ -2656,50 +2754,25 @@ impl Fleet { } } - /// Each window holding the project's bots is named after the lead, or - /// its first bot, and what that bot is doing, with a count of the others - /// that are working or need the person: `shop lead · done · 2 working`. A - /// window that also holds the person's own blocks, as when Sonny was - /// opened beside them, keeps the name they gave it. + /// Name each window holding the project's bots after what they are + /// doing ([`Naming::window`]), on the worker, from its blocks then. fn name_windows(&mut self) { - for (window, labels) in self.layout.windows() { - if !labels.iter().all(|label| self.owns(label)) { - continue; - } - let bots: Vec<&String> = labels - .iter() - .filter(|l| self.doing.contains_key(*l)) - .collect(); - let Some(first) = bots - .iter() - .find(|b| **b == &self.lead) - .or_else(|| bots.first()) - .copied() - else { - continue; + let naming = self.naming(); + let named = self.named.clone(); + self.layout.names(move |layout| { + let Ok(mut named) = named.lock() else { + return; }; - let mut name = format!("{} · {}", self.named(first), self.doing[first]); - for state in ["needs you", "working"] { - let others = bots - .iter() - .filter(|b| **b != first && self.doing.get(**b) == Some(&state)) - .count(); - if others > 0 { - name.push_str(&format!(" · {others} {state}")); + for (window, labels) in layout.windows() { + let Some(name) = naming.window(&labels) else { + continue; + }; + if named.get(&window) != Some(&name) { + layout.rename_window(&window, &name); + named.insert(window, name); } } - if self.named.get(&window) != Some(&name) { - self.layout.rename_window(&window, &name); - self.named.insert(window, name); - } - } - } - - /// Whether a block is the project's: a bot's, or a command one asked - /// for (`BOT/NAME`). - fn owns(&self, label: &str) -> bool { - let bot = label.split_once('/').map_or(label, |(bot, _)| bot); - self.doing.contains_key(bot) || self.lineage.contains(bot) + }); } /// Follow every bot again on a new connection; what changed while @@ -2799,15 +2872,12 @@ impl Fleet { } None } - "accepted" if bot != self.lead && self.lineage.contains(bot) => match self.show(bot) { - Ok(true) if !self.layout.here() => { - Some(format!("{bot} is in {}", self.layout.home())) - } - Ok(_) => None, - Err(error) => Some(error), - }, + "accepted" if bot != self.lead && self.lineage.contains(bot) => { + self.show(bot, true); + None + } "deleted" => { - self.layout.close(bot); + self.close(bot); self.lineage.remove(bot); self.doing.remove(bot); None @@ -3087,12 +3157,24 @@ impl App { None => self.dropped(), }, Some(key) = keys.recv() => self.key(key).await, - event = async { + news = async { match &mut self.fleet { - Some(fleet) if !fleet.down => fleet.events.recv().await, - _ => std::future::pending().await, + Some(fleet) => tokio::select! { + Some(said) = fleet.said.recv() => Err(said), + event = fleet.events.recv(), if !fleet.down => Ok(event), + else => std::future::pending().await, + }, + None => std::future::pending().await, } } => { + // What the layout's worker said, between the daemon's events. + let event = match news { + Ok(event) => event, + Err(said) => { + self.fleet_said(said); + continue; + } + }; let Some(event) = event else { if let Some(fleet) = &mut self.fleet { fleet.down = true; @@ -3115,12 +3197,7 @@ impl App { self.sounds.alert(alert, &what); } if let Some(said) = said { - // After the call that started the task, not above it. - self.held.push(Line::from(format!(" ↳ {said}")).dim()); - if self.calls.is_empty() { - let held = std::mem::take(&mut self.held); - self.commit(held); - } + self.fleet_said(said); } } _ = tick.tick() => {} @@ -7883,6 +7960,16 @@ impl App { } } + /// What the lead's tasks' layout says, as a line for the lead's view: + /// after the call that started the task, not above it. + fn fleet_said(&mut self, said: String) { + self.held.push(Line::from(format!(" ↳ {said}")).dim()); + if self.calls.is_empty() { + let held = std::mem::take(&mut self.held); + self.commit(held); + } + } + /// A jump to where a note was put, once the conversation is drawn /// back far enough: shown, or the next page older read. async fn jumped(&mut self) { diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index a87cf5392..666874436 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -9,7 +9,7 @@ use std::{ io::Read, path::{Path, PathBuf}, process::{Command, Output, Stdio}, - sync::mpsc, + sync::{Arc, Mutex, mpsc}, time::{Duration, Instant}, }; @@ -103,7 +103,7 @@ pub enum Placement { /// split, its side of its neighbour (`right of`, `below`, … a label). pub type Places = HashMap)>; -pub trait Backend { +pub trait Backend: Send { /// The project's home as the person reads it: `Rex session shop`. fn home(&self) -> String; /// Whether that home is the one Sonny runs in. @@ -408,6 +408,98 @@ impl Layout { } } +/// A job for a [`Worker`]'s layout. +type Job = Box; + +enum Work { + Job(Job), + /// Naming the windows, which one sent after it makes needless. + Names(Job), + /// Reading where each block sits again. + Places, +} + +/// A [`Layout`] worked on a thread of its own, so that no multiplexer +/// call holds up Sonny's screen: jobs run in the order they are sent, +/// a naming of the windows that another follows is skipped, and where +/// each block sits is kept to be read at once. +pub struct Worker { + work: mpsc::Sender, + home: String, + here: bool, + places: Arc>, +} + +impl Worker { + pub fn new(layout: Layout) -> Self { + let (work, queue) = mpsc::channel::(); + let places = Arc::new(Mutex::new(Places::new())); + let kept = places.clone(); + let (home, here) = (layout.home(), layout.here()); + std::thread::spawn(move || { + let mut layout = layout; + while let Ok(first) = queue.recv() { + let batch: Vec = std::iter::once(first).chain(queue.try_iter()).collect(); + let last_names = batch.iter().rposition(|w| matches!(w, Work::Names(_))); + let read = batch.iter().any(|w| matches!(w, Work::Places)); + for (n, work) in batch.into_iter().enumerate() { + match work { + Work::Job(job) => job(&mut layout), + Work::Names(job) if Some(n) == last_names => job(&mut layout), + Work::Names(_) | Work::Places => {} + } + } + if read { + let read = layout.places(); + if let Ok(mut places) = kept.lock() { + *places = read; + } + } + } + }); + Self { + work, + home, + here, + places, + } + } + + /// Run `job` on the layout, after those sent before it. + pub fn send(&self, job: impl FnOnce(&mut Layout) + Send + 'static) { + let _ = self.work.send(Work::Job(Box::new(job))); + } + + /// Name the windows with `job`, unless another naming follows it. + pub fn names(&self, job: impl FnOnce(&mut Layout) + Send + 'static) { + let _ = self.work.send(Work::Names(Box::new(job))); + } + + /// Where each block sat when last read, read again in the background. + pub fn places(&self) -> Places { + let _ = self.work.send(Work::Places); + self.places.lock().map(|p| p.clone()).unwrap_or_default() + } + + pub fn home(&self) -> &str { + &self.home + } + + pub fn here(&self) -> bool { + self.here + } + + /// Wait until every job sent so far has run. + #[cfg(test)] + pub fn settle(&self) { + let (done, settled) = mpsc::channel(); + self.send(move |_| { + let _ = done.send(()); + }); + let _ = settled.recv(); + } +} + /// The multiplexers Sonny lays work out in, by the CLI that drives each. enum Kind { Rex(PathBuf), @@ -655,6 +747,33 @@ mod tests { assert!(started.elapsed() < std::time::Duration::from_secs(5)); } + #[test] + fn a_worker_runs_jobs_in_order_and_skips_a_naming_that_another_follows() { + let worker = Worker::new(Layout::new(Box::new(Fake::default()))); + assert_eq!((worker.home(), worker.here()), ("Fake home", true)); + let named = Arc::new(Mutex::new(Vec::new())); + // Held until all are sent, so they arrive as one batch. + let (go, wait) = mpsc::channel::<()>(); + worker.send(move |_| { + let _ = wait.recv(); + }); + let sonny = ["sonny".to_owned()]; + worker.send(move |layout| { + layout.show("shop.fix", "fix", &sonny).unwrap(); + }); + for n in 0..3 { + let named = named.clone(); + worker.names(move |layout| { + let windows = layout.windows(); + named.lock().unwrap().push((n, windows.len())); + }); + } + go.send(()).unwrap(); + worker.settle(); + // Only the last naming ran, after the task's window was opened. + assert_eq!(*named.lock().unwrap(), [(2, 1)]); + } + #[test] fn a_project_keeps_the_place_it_was_laid_out_in() { let file = std::env::temp_dir().join(format!("sonny-place-{}.json", std::process::id())); From aebcd30049445561b007d2d0b7c9f45752836900 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 04:48:46 +0000 Subject: [PATCH 04/27] FEATURES: layout worker row (#492) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 3ec9ee2e3..49764651e 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -61,7 +61,7 @@ per feature; group small things into the row they belong to. | Feature | Status | History | Why | Used? | |---|---|---|---|---| -| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #PR | Watch and click into every agent from the multiplexer | ? | +| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492 | Watch and click into every agent from the multiplexer | ? | | Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489 | George's other multiplexer | ? | | cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323 | A third multiplexer to try | ? | | Sessions for leads, tabs for tasks, a split only to show something now (`sonny run`, `sonny show`) | shipped | `sonny run` renamed `sonny split` 10-26 #165; sessions and tabs 10-27 #200; no programs started while a window idles 10-29 #311 | Tabs are a job stream to click into; a split is for attention now | ? | From 48dd8addf0abeb807c57cba702f190b246c77a74 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 04:57:07 +0000 Subject: [PATCH 05/27] Sonny: hosts say what changed, so the layout reads a home again only then The layout listed every block before each thing it did: a task start listed three times, every state change of any task once more, and /status re-read places every two seconds. Now the home's blocks and places are kept, and read again only once the host's events say something changed, or Sonny changed it itself. A host's events say that something changed, not what: the home is read again whole, as Herdr's and cmux's event docs ask of a client that keeps their state. - Rex: `rex events -s SESSION --json`; terminal and focus events move no block. - Herdr: `events.subscribe` on HERDR_SOCKET_PATH, which Herdr sets in its panes; its CLI has no stream. An error (events_lost) ends the watch. - cmux: `cmux events` for workspace, pane and surface; its heartbeats end the command once Sonny is gone. With no stream, or once it ends, the home is read once per batch of work. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/cmux.rs | 34 ++++- sonny/src/herdr.rs | 55 ++++++- sonny/src/mux.rs | 351 +++++++++++++++++++++++++++++++++++++++++---- sonny/src/rex.rs | 44 +++++- 4 files changed, 452 insertions(+), 32 deletions(-) diff --git a/sonny/src/cmux.rs b/sonny/src/cmux.rs index 126afe8a2..82780b2cc 100644 --- a/sonny/src/cmux.rs +++ b/sonny/src/cmux.rs @@ -11,7 +11,7 @@ //! cmux shows what Sonny is doing without any of this: the program status //! Sonny writes to its terminal (OSC 7501) is the workspace's status row, //! and its notifications (OSC 777) ring the pane they came from. -use crate::mux::{Backend, Block, Place, Places}; +use crate::mux::{Backend, Block, Heard, Place, Places, Stream, Watch}; use serde_json::{Value, json}; use std::{ collections::HashMap, @@ -196,6 +196,15 @@ impl Resume { } } +/// What a line of `cmux events` says: an event, or its stream's `ack` or +/// `heartbeat`. +fn heard(line: &Value) -> Heard { + match line["type"].as_str() { + Some("event") => Heard::Changed, + _ => Heard::Same, + } +} + pub struct Cmux { bin: PathBuf, workspace: String, @@ -431,6 +440,21 @@ impl Backend for Cmux { } } + /// cmux's heartbeats keep the stream from outliving Sonny: the first + /// one written after it is gone ends the command. + fn watch(&self) -> Option { + let mut command = Command::new(&self.bin); + for name in PLACE_VARS { + command.env_remove(name); + } + command.args(["events", "--category", "workspace"]); + command.args(["--category", "pane", "--category", "surface"]); + Some(Watch { + from: Stream::Command(command), + heard: Box::new(heard), + }) + } + /// Where each tab sits, by label: its number in its pane, and when the /// workspace is split, which side of the first other pane its pane is. fn places(&self) -> Places { @@ -504,6 +528,14 @@ fn side_of(frame: &Value, other: &Value) -> &'static str { #[cfg(test)] mod tests { use super::*; + + #[test] + fn cmux_news_is_its_events_not_its_heartbeats() { + assert_eq!(heard(&json!({"type": "ack", "version": 1})), Heard::Same); + assert_eq!(heard(&json!({"type": "heartbeat"})), Heard::Same); + let closed = json!({"type": "event", "name": "surface.closed", "category": "surface"}); + assert_eq!(heard(&closed), Heard::Changed); + } use crate::mux::Layout; /// A stand-in cmux over a JSON file of workspaces, panes and tabs, that diff --git a/sonny/src/herdr.rs b/sonny/src/herdr.rs index 9246a2381..7d8b625e9 100644 --- a/sonny/src/herdr.rs +++ b/sonny/src/herdr.rs @@ -3,8 +3,8 @@ //! labelled with each bot's name. Every call goes through the `herdr` CLI, //! which prints the socket API's JSON reply; ids are kept because labels //! need not be unique. Written against herdr 0.9.5's CLI and API schema. -use crate::mux::{Backend, Block, Place, Places}; -use serde_json::Value; +use crate::mux::{Backend, Block, Heard, Place, Places, Stream, Watch}; +use serde_json::{Value, json}; use std::{ collections::HashMap, path::{Path, PathBuf}, @@ -270,6 +270,32 @@ fn run(bin: &Path, args: &[&str], limit: Duration) -> bool { } } +/// The events that may move, label or end a pane: `pane.updated` says a +/// pane was renamed, among others. +const CHANGES: [&str; 10] = [ + "tab.created", + "tab.closed", + "tab.moved", + "pane.created", + "pane.closed", + "pane.updated", + "pane.moved", + "pane.exited", + "layout.updated", + "workspace.closed", +]; + +/// What a line of an `events.subscribe` stream says: the reply that it +/// started, an event, or an error, as when it fell behind (`events_lost`), +/// after which Herdr closes it. +fn heard(line: &Value) -> Heard { + match (line.get("error"), line.get("result")) { + (Some(_), _) => Heard::Over, + (None, Some(_)) => Heard::Same, + (None, None) => Heard::Changed, + } +} + pub struct Herdr { bin: PathBuf, workspace: String, @@ -539,6 +565,21 @@ impl Backend for Herdr { } } + /// Herdr's CLI has no stream; its socket does, and Herdr names it in + /// every pane it runs, as in the one Sonny runs in. + fn watch(&self) -> Option { + let socket = std::env::var_os("HERDR_SOCKET_PATH").filter(|s| !s.is_empty())?; + let ask = json!({ + "id": "sonny-layout", + "method": "events.subscribe", + "params": {"subscriptions": CHANGES.map(|kind| json!({"type": kind}))}, + }); + Some(Watch { + from: Stream::Socket(socket.into(), ask.to_string()), + heard: Box::new(heard), + }) + } + /// Where each pane sits, by label: its tab's number, and beside the /// tab's first other pane, which side of it. fn places(&self) -> Places { @@ -611,6 +652,16 @@ fn side_of(rect: &Value, other: &Value) -> &'static str { #[cfg(test)] mod tests { use super::*; + + #[test] + fn herdr_news_starts_with_a_reply_and_ends_on_an_error() { + let started = json!({"id": "sonny-layout", "result": {"type": "subscription_started"}}); + let event = json!({"event": "tab_created", "data": {}}); + let lost = json!({"id": "sonny-layout", "error": {"code": "events_lost"}}); + assert_eq!(heard(&started), Heard::Same); + assert_eq!(heard(&event), Heard::Changed); + assert_eq!(heard(&lost), Heard::Over); + } use crate::mux::{Layout, Placement}; use serde_json::json; diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index 666874436..7ae9d923b 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -6,10 +6,15 @@ //! says how, in its multiplexer's CLI. use std::{ collections::HashMap, - io::Read, + io::{BufRead, BufReader, Read, Write}, + os::unix::net::UnixStream, path::{Path, PathBuf}, - process::{Command, Output, Stdio}, - sync::{Arc, Mutex, mpsc}, + process::{Child, Command, Output, Stdio}, + sync::{ + Arc, Mutex, + atomic::{AtomicBool, AtomicU64, Ordering}, + mpsc, + }, time::{Duration, Instant}, }; @@ -80,6 +85,7 @@ fn output_within(command: &mut Command, limit: Duration) -> std::io::Result Places; /// The home, to find again. fn place(&self) -> Place; + /// Where the multiplexer says what changes in the home, when it can. + fn watch(&self) -> Option { + None + } +} + +/// A multiplexer's news of what changes in a home, one JSON object a line, +/// so that the blocks listed once are listed again only once one changed. +/// The host's events say that something changed, not what: the home is +/// read again whole, as Herdr's and cmux's event docs ask of a client +/// that keeps their state. +pub struct Watch { + pub from: Stream, + /// What a line says. + pub heard: Box Heard + Send>, +} + +pub enum Stream { + /// A command that prints the news until it is stopped. + Command(Command), + /// A socket that sends the news once asked, by the line given. + Socket(PathBuf, String), +} + +#[derive(Debug, PartialEq)] +pub enum Heard { + /// Something in the home may have changed. + Changed, + /// Nothing that moves a block, said by a stream that is up. + Same, + /// The stream will say no more. + Over, +} + +/// What a home's watch has heard: whether it is up, and how many times the +/// home changed, which what was read before is checked against. +#[derive(Default)] +struct News { + up: AtomicBool, + changes: AtomicU64, +} + +impl News { + fn changes(&self) -> u64 { + self.changes.load(Ordering::Acquire) + } + + fn changed(&self) { + self.changes.fetch_add(1, Ordering::AcqRel); + } +} + +/// What a watch holds open, closed with its layout. +enum Watching { + Command(Child), + Socket(UnixStream), } /// A project's home, as its lead's Sonny last found it in the person's @@ -222,6 +284,27 @@ pub struct Layout { /// The blocks a home made just now came with, closed once something /// is placed in it. spare: Vec, + /// The home's blocks and places as last read, with the changes the + /// watch had heard of by then. + listed: Option<(u64, Vec)>, + placed: Option<(u64, Places)>, + news: Arc, + watching: Option, +} + +impl Drop for Layout { + fn drop(&mut self) { + match self.watching.take() { + Some(Watching::Command(mut child)) => { + let _ = child.kill(); + let _ = child.wait(); + } + Some(Watching::Socket(socket)) => { + let _ = socket.shutdown(std::net::Shutdown::Both); + } + None => {} + } + } } impl Layout { @@ -234,14 +317,121 @@ impl Layout { backend, blocks: HashMap::new(), spare, + listed: None, + placed: None, + news: Arc::default(), + watching: None, } } + /// Follow the home's news, when its multiplexer gives it: from then + /// on, what was read is read again only once the news says the home + /// changed. The first line heard says the stream is up; until then, + /// and once it ends, the home is read again for each batch of work. + pub fn watch(&mut self) { + let Some(Watch { from, heard }) = self.backend.watch() else { + return; + }; + let lines: Box = match from { + Stream::Command(mut command) => { + let Ok(mut child) = command + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + else { + return; + }; + let Some(out) = child.stdout.take() else { + return; + }; + self.watching = Some(Watching::Command(child)); + Box::new(BufReader::new(out)) + } + Stream::Socket(path, ask) => { + let Ok(mut socket) = UnixStream::connect(&path) else { + return; + }; + let Ok(kept) = socket.try_clone() else { + return; + }; + if writeln!(socket, "{ask}").is_err() { + return; + } + self.watching = Some(Watching::Socket(kept)); + Box::new(BufReader::new(socket)) + } + }; + let news = self.news.clone(); + std::thread::spawn(move || { + for line in lines.lines() { + let Ok(line) = line else { break }; + let said = serde_json::from_str(&line).map_or(Heard::Changed, |line| heard(&line)); + if said == Heard::Over { + break; + } + // Up just now: what was read before it may have missed a + // change, so it is read again. + if !news.up.swap(true, Ordering::AcqRel) || said == Heard::Changed { + news.changed(); + } + } + news.up.store(false, Ordering::Release); + news.changed(); + }); + } + + /// Ready for a batch of work: with no news followed, what was read + /// before may be out of date, so it is read again when next asked. + pub fn fresh(&mut self) { + if !self.news.up.load(Ordering::Acquire) { + self.listed = None; + self.placed = None; + } + } + + /// The home's blocks, as last read unless the home changed since. + fn listed(&mut self) -> Vec { + let now = self.news.changes(); + if let Some((at, blocks)) = &self.listed + && *at == now + { + return blocks.clone(); + } + let blocks = self.backend.blocks(); + self.listed = Some((now, blocks.clone())); + blocks + } + + /// What Sonny itself changes is read again after. + fn forget(&mut self) { + self.listed = None; + self.placed = None; + } + + fn close_block(&mut self, block: &str) { + self.backend.close_block(block); + if let Some((_, listed)) = &mut self.listed { + listed.retain(|b| b.id != block); + } + self.placed = None; + } + + fn rename_block(&mut self, block: &str, label: &str) { + self.backend.rename_block(block, label); + if let Some((_, listed)) = &mut self.listed { + for b in listed.iter_mut().filter(|b| b.id == block) { + b.label = label.to_owned(); + } + } + self.placed = None; + } + /// Close the shell a home made just now came with, now that it holds /// something of its own. fn tidy(&mut self) { for block in std::mem::take(&mut self.spare) { - self.backend.close_block(&block); + self.close_block(&block); } } @@ -266,7 +456,7 @@ impl Layout { /// task has a block already. Returns whether one was opened. The /// person's focus stays where it is. pub fn show(&mut self, bot: &str, name: &str, command: &[String]) -> Result { - let listed = self.backend.blocks(); + let listed = self.listed(); if let Some(block) = self.blocks.get(bot) && listed.iter().any(|b| &b.id == block) { @@ -287,9 +477,9 @@ impl Layout { command: &[String], placement: &Placement, ) -> Result<(), String> { - let listed = self.backend.blocks(); + let listed = self.listed(); for old in listed.iter().filter(|b| b.label == bot) { - self.backend.close_block(&old.id); + self.close_block(&old.id); } let cwd = Path::new("/"); let block = match placement { @@ -303,15 +493,16 @@ impl Layout { (self.backend).split(&anchor, side, false, bot, cwd, command, false)? } }; + self.forget(); self.blocks.insert(bot.to_owned(), block); self.tidy(); Ok(()) } /// Each window and the labels of its blocks, in the multiplexer's order. - pub fn windows(&self) -> Vec<(String, Vec)> { + pub fn windows(&mut self) -> Vec<(String, Vec)> { let mut windows: Vec<(String, Vec)> = Vec::new(); - for block in self.backend.blocks() { + for block in self.listed() { match windows .iter_mut() .find(|(window, _)| *window == block.window) @@ -323,8 +514,17 @@ impl Layout { windows } - pub fn places(&self) -> Places { - self.backend.places() + /// Where each block sits, as last read unless the home changed since. + pub fn places(&mut self) -> Places { + let now = self.news.changes(); + if let Some((at, places)) = &self.placed + && *at == now + { + return places.clone(); + } + let places = self.backend.places(); + self.placed = Some((now, places.clone())); + places } pub fn rename_window(&self, window: &str, name: &str) { @@ -335,7 +535,7 @@ impl Layout { /// after the bot. pub fn adopt_own(&mut self, bot: &str) { let mine = self.backend.own_block(); - let listed = self.backend.blocks(); + let listed = self.listed(); let own = listed .iter() .find(|b| mine.as_ref().is_some_and(|mine| &b.id == mine)) @@ -343,10 +543,11 @@ impl Layout { let Some(own) = own else { return; }; - if own.label != bot { - self.backend.rename_block(&own.id, bot); + let own = own.id.clone(); + if !listed.iter().any(|b| b.id == own && b.label == bot) { + self.rename_block(&own, bot); } - self.blocks.insert(bot.to_owned(), own.id.clone()); + self.blocks.insert(bot.to_owned(), own); } /// Close every other block labelled with the bot's name, as one Sonny @@ -356,12 +557,11 @@ impl Layout { return; }; for other in self - .backend - .blocks() + .listed() .into_iter() .filter(|b| b.label == bot && b.id != mine) { - self.backend.close_block(&other.id); + self.close_block(&other.id); } } @@ -380,9 +580,9 @@ impl Layout { side: Option<&str>, ) -> Result { let label = format!("{owner}/{name}"); - let listed = self.backend.blocks(); + let listed = self.listed(); for old in listed.iter().filter(|b| b.label == label) { - self.backend.close_block(&old.id); + self.close_block(&old.id); } let anchor = listed.iter().find(|b| b.label == owner); let block = match anchor.zip(side) { @@ -391,6 +591,7 @@ impl Layout { } None => self.backend.window(&label, name, cwd, command, true), }?; + self.forget(); self.tidy(); Ok(block) } @@ -400,10 +601,10 @@ impl Layout { pub fn close(&mut self, bot: &str) { let known = self.blocks.remove(bot); let owned = format!("{bot}/"); - for block in self.backend.blocks().into_iter().filter(|b| { + for block in self.listed().into_iter().filter(|b| { b.label == bot || b.label.starts_with(&owned) || Some(&b.id) == known.as_ref() }) { - self.backend.close_block(&block.id); + self.close_block(&block.id); } } } @@ -422,7 +623,8 @@ enum Work { /// A [`Layout`] worked on a thread of its own, so that no multiplexer /// call holds up Sonny's screen: jobs run in the order they are sent, /// a naming of the windows that another follows is skipped, and where -/// each block sits is kept to be read at once. +/// each block sits is kept to be read at once. It follows the home's +/// news, so what nothing changed is not read again. pub struct Worker { work: mpsc::Sender, home: String, @@ -438,8 +640,10 @@ impl Worker { let (home, here) = (layout.home(), layout.here()); std::thread::spawn(move || { let mut layout = layout; + layout.watch(); while let Ok(first) = queue.recv() { let batch: Vec = std::iter::once(first).chain(queue.try_iter()).collect(); + layout.fresh(); let last_names = batch.iter().rposition(|w| matches!(w, Work::Names(_))); let read = batch.iter().any(|w| matches!(w, Work::Places)); for (n, work) in batch.into_iter().enumerate() { @@ -644,6 +848,9 @@ mod tests { blocks: RefCell>, own: Option, made: bool, + /// How many times the blocks were listed. + lists: Arc, + news: RefCell>, } impl Fake { @@ -669,6 +876,7 @@ mod tests { self.made } fn blocks(&self) -> Vec { + self.lists.fetch_add(1, Ordering::SeqCst); (self.blocks.borrow().iter()) .map(|b| Block { id: b.id.clone(), @@ -728,6 +936,9 @@ mod tests { workspace: "w1".into(), } } + fn watch(&self) -> Option { + self.news.borrow_mut().take() + } } #[test] @@ -747,6 +958,87 @@ mod tests { assert!(started.elapsed() < std::time::Duration::from_secs(5)); } + /// Wait until `news` has heard of `changes` changes. + fn hear(news: &News, changes: u64) { + let started = Instant::now(); + while news.changes() < changes { + assert!(started.elapsed() < Duration::from_secs(5), "never heard"); + std::thread::sleep(Duration::from_millis(1)); + } + } + + #[test] + fn with_no_news_the_home_is_read_once_a_batch() { + let fake = Fake::default(); + let lists = fake.lists.clone(); + fake.add("shop.fix", "w1"); + let mut layout = Layout::new(Box::new(fake)); + layout.windows(); + layout.windows(); + assert_eq!(lists.load(Ordering::SeqCst), 1); + layout.fresh(); + layout.windows(); + assert_eq!(lists.load(Ordering::SeqCst), 2); + // What Sonny opens itself, it reads again. + layout.show("shop.new", "new", &[]).unwrap(); + assert_eq!(lists.load(Ordering::SeqCst), 2); + let windows = layout.windows(); + assert_eq!((lists.load(Ordering::SeqCst), windows.len()), (3, 2)); + } + + #[test] + fn with_news_the_home_is_read_again_only_once_it_changed() { + let dir = std::env::temp_dir().join(format!("sonny-news-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("news.sock"); + let listener = std::os::unix::net::UnixListener::bind(&path).unwrap(); + let fake = Fake::default(); + let lists = fake.lists.clone(); + fake.add("shop.fix", "w1"); + *fake.news.borrow_mut() = Some(Watch { + from: Stream::Socket(path, "{\"ask\":1}".into()), + heard: Box::new(|line| match line["said"].as_str() { + Some("changed") => Heard::Changed, + Some("over") => Heard::Over, + _ => Heard::Same, + }), + }); + let mut layout = Layout::new(Box::new(fake)); + let news = layout.news.clone(); + layout.watch(); + let (socket, _) = listener.accept().unwrap(); + let mut asked = String::new(); + BufReader::new(socket.try_clone().unwrap()) + .read_line(&mut asked) + .unwrap(); + assert_eq!(asked, "{\"ask\":1}\n"); + let say = |said: &str| writeln!(&socket, "{{\"said\":\"{said}\"}}").unwrap(); + // Up: read again once, then not, batch after batch. + say("started"); + hear(&news, 1); + layout.windows(); + layout.fresh(); + layout.places(); + layout.windows(); + assert_eq!(lists.load(Ordering::SeqCst), 1); + // What does not move a block changes nothing. + say("focus"); + say("changed"); + hear(&news, 2); + layout.windows(); + assert_eq!(lists.load(Ordering::SeqCst), 2); + // Once the news ends, each batch reads the home again. + say("over"); + hear(&news, 3); + assert!(!news.up.load(Ordering::SeqCst)); + layout.windows(); + layout.fresh(); + layout.windows(); + assert_eq!(lists.load(Ordering::SeqCst), 4); + let _ = std::fs::remove_dir_all(&dir); + } + #[test] fn a_worker_runs_jobs_in_order_and_skips_a_naming_that_another_follows() { let worker = Worker::new(Layout::new(Box::new(Fake::default()))); @@ -800,7 +1092,7 @@ mod tests { assert_ne!(Place::file(socket, "shop"), Place::file(socket, "shop.web")); } - fn labels(layout: &Layout) -> Vec<(String, Vec)> { + fn labels(layout: &mut Layout) -> Vec<(String, Vec)> { layout.windows() } @@ -830,7 +1122,7 @@ mod tests { .split("shop.lead", "server", Path::new("/w"), &sonny, None) .unwrap(); assert_eq!( - labels(&layout), + labels(&mut layout), [ ( "home".into(), @@ -845,7 +1137,7 @@ mod tests { ); layout.close("shop.fix"); assert_eq!( - labels(&layout), + labels(&mut layout), [ ("home".into(), vec!["shop.lead".into()]), ("server".into(), vec!["shop.lead/server".into()]), @@ -863,7 +1155,10 @@ mod tests { let mut layout = Layout::new(Box::new(fake)); let sonny = ["sonny".to_owned()]; assert!(layout.show("web.lead", "lead", &sonny).unwrap()); - assert_eq!(labels(&layout), [("lead".into(), vec!["web.lead".into()])]); + assert_eq!( + labels(&mut layout), + [("lead".into(), vec!["web.lead".into()])] + ); // The lead's Sonny is there already: left alone. assert!(!layout.show("web.lead", "lead", &sonny).unwrap()); } @@ -892,7 +1187,7 @@ mod tests { .unwrap(); layout.close("shop.fix"); assert_eq!( - labels(&layout), + labels(&mut layout), [( "work".into(), vec![ diff --git a/sonny/src/rex.rs b/sonny/src/rex.rs index 8980944bd..13531ea25 100644 --- a/sonny/src/rex.rs +++ b/sonny/src/rex.rs @@ -5,7 +5,7 @@ //! labels need not be unique. Checked against rex 0.0.1792787366 (API rev 7) //! for `ls`, `new`, `split`, `block ls` and `block close`; `run`'s output is //! read the way `split`'s is. -use crate::mux::{Backend, Block, Place, Places}; +use crate::mux::{Backend, Block, Heard, Place, Places, Stream, Watch}; use serde_json::Value; use std::{ collections::HashMap, @@ -29,6 +29,23 @@ pub fn find() -> Option { on_path.or_else(|| Path::new(MAC_APP).is_file().then(|| PathBuf::from(MAC_APP))) } +/// What a line of `rex events -s SESSION --json` says. A session's event +/// comes wrapped in a `session_event`; a terminal's, once more, in a +/// `block_event`, which moves no block, as focus and clients coming and +/// going don't. Anything else may. +fn heard(line: &Value) -> Heard { + match line["payload"]["event"]["type"].as_str() { + Some( + "block_event" + | "active_window_changed" + | "client_connected" + | "client_disconnected" + | "client_connection_changed", + ) => Heard::Same, + _ => Heard::Changed, + } +} + pub struct Rex { bin: PathBuf, /// `-S HOST` when the session lives on another Rex server. @@ -273,6 +290,17 @@ impl Backend for Rex { } } + fn watch(&self) -> Option { + let mut command = Command::new(&self.bin); + (command.env_remove("REX_SESSION").env_remove("REX_BLOCK")) + .args(&self.host) + .args(["events", "-s", &self.session, "--json"]); + Some(Watch { + from: Stream::Command(command), + heard: Box::new(heard), + }) + } + /// Where each block sits, by label: its tab, counted from 1, and in a /// split, its side of its neighbour (`right of`, `below`, … a label). fn places(&self) -> Places { @@ -341,6 +369,20 @@ fn sides_of(node: &Value, sides: &mut HashMap) { mod tests { use std::path::{Path, PathBuf}; + #[test] + fn rex_news_of_a_terminal_or_focus_moves_no_block() { + use crate::mux::Heard; + let event = |kind: &str| { + serde_json::json!({"type": "session_event", "payload": { + "session_id": "session:01", "event": {"type": kind, "payload": {}}}}) + }; + assert_eq!(super::heard(&event("block_event")), Heard::Same); + assert_eq!(super::heard(&event("active_window_changed")), Heard::Same); + for kind in ["window_created", "block_closed", "session_view_changed"] { + assert_eq!(super::heard(&event(kind)), Heard::Changed); + } + } + /// A stand-in rex, over a JSON file of sessions and their blocks, that /// logs each call it gets. const FAKE: &str = r#"#!/usr/bin/env python3 From de629abd25b909f5b70aef483c98898131909ab6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 04:59:50 +0000 Subject: [PATCH 06/27] Rex news, as checked on 0.1.0 preview.24: ownership moves no block, session_destroyed ends the stream Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/rex.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/sonny/src/rex.rs b/sonny/src/rex.rs index 13531ea25..b1597f273 100644 --- a/sonny/src/rex.rs +++ b/sonny/src/rex.rs @@ -31,12 +31,18 @@ pub fn find() -> Option { /// What a line of `rex events -s SESSION --json` says. A session's event /// comes wrapped in a `session_event`; a terminal's, once more, in a -/// `block_event`, which moves no block, as focus and clients coming and -/// going don't. Anything else may. +/// `block_event`, which moves no block, as focus, ownership and clients +/// coming and going (each CLI call is one) don't. A block renamed says +/// only `session_view_changed`. The stream goes on once its session is +/// gone, so `session_destroyed` ends it. fn heard(line: &Value) -> Heard { + if line["type"] == "session_destroyed" { + return Heard::Over; + } match line["payload"]["event"]["type"].as_str() { Some( "block_event" + | "block_owner_changed" | "active_window_changed" | "client_connected" | "client_disconnected" @@ -378,9 +384,12 @@ mod tests { }; assert_eq!(super::heard(&event("block_event")), Heard::Same); assert_eq!(super::heard(&event("active_window_changed")), Heard::Same); + assert_eq!(super::heard(&event("client_connected")), Heard::Same); for kind in ["window_created", "block_closed", "session_view_changed"] { assert_eq!(super::heard(&event(kind)), Heard::Changed); } + let gone = serde_json::json!({"type": "session_destroyed", "payload": {"session_id": "session:01"}}); + assert_eq!(super::heard(&gone), Heard::Over); } /// A stand-in rex, over a JSON file of sessions and their blocks, that From b3b7a982b0ff87efcc351b3d0b16579088fb40a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:02:01 +0000 Subject: [PATCH 07/27] FEATURES: hosts' news row Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 7012aaab0..22e8e8be7 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -61,7 +61,7 @@ per feature; group small things into the row they belong to. | Feature | Status | History | Why | Used? | |---|---|---|---|---| -| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492 | Watch and click into every agent from the multiplexer | ? | +| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492; a home read again only once its host says it changed 10-31 #PR | Watch and click into every agent from the multiplexer | ? | | Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489 | George's other multiplexer | ? | | cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323 | A third multiplexer to try | ? | | Sessions for leads, tabs for tasks, a split only to show something now (`sonny run`, `sonny show`) | shipped | `sonny run` renamed `sonny split` 10-26 #165; sessions and tabs 10-27 #200; no programs started while a window idles 10-29 #311 | Tabs are a job stream to click into; a split is for attention now | ? | From 0cc9447420f9a5b2ffa55e6f3c0c8c2189f758a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:02:23 +0000 Subject: [PATCH 08/27] FEATURES: #493 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 22e8e8be7..3b3e0654e 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -61,7 +61,7 @@ per feature; group small things into the row they belong to. | Feature | Status | History | Why | Used? | |---|---|---|---|---| -| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492; a home read again only once its host says it changed 10-31 #PR | Watch and click into every agent from the multiplexer | ? | +| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492; a home read again only once its host says it changed 10-31 #493 | Watch and click into every agent from the multiplexer | ? | | Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489 | George's other multiplexer | ? | | cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323 | A third multiplexer to try | ? | | Sessions for leads, tabs for tasks, a split only to show something now (`sonny run`, `sonny show`) | shipped | `sonny run` renamed `sonny split` 10-26 #165; sessions and tabs 10-27 #200; no programs started while a window idles 10-29 #311 | Tabs are a job stream to click into; a split is for attention now | ? | From aa209e51fc2089f948846c57aaafbf7561423e36 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:16:39 +0000 Subject: [PATCH 09/27] Sonny: a failed home read is tried again, not kept; a host's news opens off the worker A read that fails is no longer cached as an empty home while the news stream is up, and a host slow to take the subscription no longer holds up the layout worker. Herdr's and cmux's feature rows record the change. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 4 +- sonny/src/cmux.rs | 54 +++++++-------- sonny/src/herdr.rs | 49 +++++++------- sonny/src/mux.rs | 159 ++++++++++++++++++++++++++++++--------------- sonny/src/rex.rs | 29 ++++----- 5 files changed, 168 insertions(+), 127 deletions(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 3b3e0654e..2702ecc42 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -62,8 +62,8 @@ per feature; group small things into the row they belong to. | Feature | Status | History | Why | Used? | |---|---|---|---|---| | Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492; a home read again only once its host says it changed 10-31 #493 | Watch and click into every agent from the multiplexer | ? | -| Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489 | George's other multiplexer | ? | -| cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323 | A third multiplexer to try | ? | +| Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489; every host call off the screen thread 10-31 #492; a workspace read again only once Herdr says it changed 10-31 #493 | George's other multiplexer | ? | +| cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323; every host call off the screen thread 10-31 #492; a workspace read again only once cmux says it changed 10-31 #493 | A third multiplexer to try | ? | | Sessions for leads, tabs for tasks, a split only to show something now (`sonny run`, `sonny show`) | shipped | `sonny run` renamed `sonny split` 10-26 #165; sessions and tabs 10-27 #200; no programs started while a window idles 10-29 #311 | Tabs are a job stream to click into; a split is for attention now | ? | ## The conversation diff --git a/sonny/src/cmux.rs b/sonny/src/cmux.rs index 82780b2cc..e30d156e9 100644 --- a/sonny/src/cmux.rs +++ b/sonny/src/cmux.rs @@ -281,14 +281,13 @@ impl Cmux { } /// This workspace, as `system.tree` has it. - fn tree(&self) -> Option { - let tree = self - .call( - "system.tree", - json!({"all_windows": true, "workspace_id": self.workspace}), - ) - .ok()?; - workspaces_of(&tree).find(|w| w["id"] == self.workspace.as_str()) + fn tree(&self) -> Result { + let tree = self.call( + "system.tree", + json!({"all_windows": true, "workspace_id": self.workspace}), + )?; + (workspaces_of(&tree).find(|w| w["id"] == self.workspace.as_str())) + .ok_or_else(|| format!("cmux: no workspace {}", self.workspace)) } /// The pane a task's tab goes in: the lead's, else this tab's, else the @@ -328,17 +327,15 @@ impl Backend for Cmux { /// Each tab, pane by pane: its title is its label, and its pane stands /// for its window. - fn blocks(&self) -> Vec { - let Some(workspace) = self.tree() else { - return Vec::new(); - }; - surfaces(&workspace) + fn blocks(&self) -> Result, String> { + let workspace = self.tree()?; + Ok(surfaces(&workspace) .map(|(pane, surface, title)| Block { id: surface.to_owned(), label: title.to_owned(), window: pane.to_owned(), }) - .collect() + .collect()) } fn close_block(&self, block: &str) { @@ -374,7 +371,7 @@ impl Backend for Cmux { "initial_input": Self::input(command, keep), "focus": false, }); - if let Some(pane) = self.home_pane(&self.blocks()) { + if let Some(pane) = self.home_pane(&self.blocks().unwrap_or_default()) { params["pane_id"] = pane.into(); } if keep { @@ -457,11 +454,9 @@ impl Backend for Cmux { /// Where each tab sits, by label: its number in its pane, and when the /// workspace is split, which side of the first other pane its pane is. - fn places(&self) -> Places { + fn places(&self) -> Result { let mut places = HashMap::new(); - let Some(workspace) = self.tree() else { - return places; - }; + let workspace = self.tree()?; let panes: Vec<&Value> = workspace["panes"] .as_array() .into_iter() @@ -469,14 +464,12 @@ impl Backend for Cmux { .collect(); let frames: HashMap = match panes.len() { 0 | 1 => HashMap::new(), - _ => self - .call("pane.list", json!({"workspace_id": self.workspace})) - .ok() - .and_then(|listed| listed["panes"].as_array().cloned()) - .into_iter() - .flatten() - .filter_map(|p| Some((p["id"].as_str()?.to_owned(), p["pixel_frame"].clone()))) - .collect(), + _ => { + let listed = self.call("pane.list", json!({"workspace_id": self.workspace}))?; + (listed["panes"].as_array().into_iter().flatten()) + .filter_map(|p| Some((p["id"].as_str()?.to_owned(), p["pixel_frame"].clone()))) + .collect() + } }; let label = |pane: &Value| { let selected = pane["selected_surface_id"].clone(); @@ -506,7 +499,7 @@ impl Backend for Cmux { } } } - places + Ok(places) } } @@ -792,7 +785,10 @@ print(json.dumps(out)) "places", json!({"n": 10, "workspaces": [{"id": "ws-1", "title": "shop", "panes": [lead, right]}]}), ); - let places = Cmux::open(bin, "shop", None, None).unwrap().places(); + let places = Cmux::open(bin, "shop", None, None) + .unwrap() + .places() + .unwrap(); let side = |s: &str, of: &str| Some((s.to_owned(), of.to_owned())); assert_eq!( places["shop.lead"], diff --git a/sonny/src/herdr.rs b/sonny/src/herdr.rs index 7d8b625e9..17b5c7da9 100644 --- a/sonny/src/herdr.rs +++ b/sonny/src/herdr.rs @@ -453,11 +453,9 @@ impl Backend for Herdr { self.made } - fn blocks(&self) -> Vec { - let Ok(listed) = self.call(&["pane", "list", "--workspace", &self.workspace]) else { - return Vec::new(); - }; - listed["panes"] + fn blocks(&self) -> Result, String> { + let listed = self.call(&["pane", "list", "--workspace", &self.workspace])?; + Ok(listed["panes"] .as_array() .into_iter() .flatten() @@ -468,7 +466,7 @@ impl Backend for Herdr { window: p["tab_id"].as_str().unwrap_or("").to_owned(), }) }) - .collect() + .collect()) } fn close_block(&self, block: &str) { @@ -582,14 +580,10 @@ impl Backend for Herdr { /// Where each pane sits, by label: its tab's number, and beside the /// tab's first other pane, which side of it. - fn places(&self) -> Places { + fn places(&self) -> Result { let mut places = HashMap::new(); - let numbers: HashMap = self - .call(&["tab", "list", "--workspace", &self.workspace]) - .ok() - .and_then(|tabs| tabs["tabs"].as_array().cloned()) - .into_iter() - .flatten() + let tabs = self.call(&["tab", "list", "--workspace", &self.workspace])?; + let numbers: HashMap = (tabs["tabs"].as_array().into_iter().flatten()) .filter_map(|t| { Some(( t["tab_id"].as_str()?.to_owned(), @@ -597,7 +591,7 @@ impl Backend for Herdr { )) }) .collect(); - let blocks = self.blocks(); + let blocks = self.blocks()?; let mut tabs: Vec<&str> = Vec::new(); for block in &blocks { if !tabs.contains(&block.window.as_str()) { @@ -609,14 +603,14 @@ impl Backend for Herdr { let panes: Vec<&Block> = blocks.iter().filter(|b| b.window == *tab).collect(); let rects: HashMap = match panes.len() { 1 => HashMap::new(), - _ => self - .call(&["pane", "layout", "--pane", &panes[0].id]) - .ok() - .and_then(|l| l["layout"]["panes"].as_array().cloned()) - .into_iter() - .flatten() - .filter_map(|p| Some((p["pane_id"].as_str()?.to_owned(), p["rect"].clone()))) - .collect(), + _ => { + let layout = self.call(&["pane", "layout", "--pane", &panes[0].id])?; + (layout["layout"]["panes"].as_array().into_iter().flatten()) + .filter_map(|p| { + Some((p["pane_id"].as_str()?.to_owned(), p["rect"].clone())) + }) + .collect() + } }; for pane in &panes { let side = panes @@ -629,7 +623,7 @@ impl Backend for Herdr { places.insert(pane.label.clone(), (number, side)); } } - places + Ok(places) } } @@ -916,7 +910,7 @@ if out is not None: let bin = find().expect("herdr on the PATH"); let herdr = Herdr::open(bin, "sonnycheck", None, None).unwrap(); let workspace = herdr.workspace.clone(); - let lead = herdr.blocks()[0].id.clone(); + let lead = herdr.blocks().unwrap()[0].id.clone(); herdr.rename_block(&lead, "sonnycheck.lead"); let mut layout = Layout::new(Box::new(herdr)); let sleep = ["sh".to_owned(), "-c".into(), "echo task; sleep 60".into()]; @@ -973,7 +967,12 @@ if out is not None: "below" ); layout.close("sonnycheck.fix"); - let left: Vec = check.blocks().into_iter().map(|b| b.label).collect(); + let left: Vec = check + .blocks() + .unwrap() + .into_iter() + .map(|b| b.label) + .collect(); println!("after closing the task: {left:?}"); assert!(!left.iter().any(|l| l.starts_with("sonnycheck.fix"))); let _ = check.call(&["workspace", "close", &workspace]); diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index 7ae9d923b..d63459a0b 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -116,8 +116,9 @@ pub trait Backend: Send { fn here(&self) -> bool; /// Whether that home was made just now, with a shell nobody asked for. fn made(&self) -> bool; - /// The home's blocks, in the multiplexer's order. - fn blocks(&self) -> Vec; + /// The home's blocks, in the multiplexer's order, or why they could + /// not be read. + fn blocks(&self) -> Result, String>; fn close_block(&self, block: &str); fn rename_block(&self, block: &str, label: &str); fn rename_window(&self, window: &str, name: &str); @@ -148,7 +149,7 @@ pub trait Backend: Send { ) -> Result; /// The block Sonny itself runs in, when in this home. fn own_block(&self) -> Option; - fn places(&self) -> Places; + fn places(&self) -> Result; /// The home, to find again. fn place(&self) -> Place; /// Where the multiplexer says what changes in the home, when it can. @@ -209,6 +210,29 @@ enum Watching { Socket(UnixStream), } +impl Watching { + fn close(self) { + match self { + Self::Command(mut child) => { + let _ = child.kill(); + let _ = child.wait(); + } + Self::Socket(socket) => { + let _ = socket.shutdown(std::net::Shutdown::Both); + } + } + } +} + +/// The watch's stream, opened on its own thread so that a host slow to +/// take it holds up no work. Once the layout is gone, one opened after +/// is closed at once. +#[derive(Default)] +struct Following { + gone: bool, + stream: Option, +} + /// A project's home, as its lead's Sonny last found it in the person's /// terminal: kept so that an agent's `run_in_pane` and `show_agent` calls /// go there, to that multiplexer, instead of one guessed from where the @@ -289,20 +313,15 @@ pub struct Layout { listed: Option<(u64, Vec)>, placed: Option<(u64, Places)>, news: Arc, - watching: Option, + following: Arc>, } impl Drop for Layout { fn drop(&mut self) { - match self.watching.take() { - Some(Watching::Command(mut child)) => { - let _ = child.kill(); - let _ = child.wait(); - } - Some(Watching::Socket(socket)) => { - let _ = socket.shutdown(std::net::Shutdown::Both); - } - None => {} + let mut following = self.following.lock().unwrap_or_else(|e| e.into_inner()); + following.gone = true; + if let Some(stream) = following.stream.take() { + stream.close(); } } } @@ -310,7 +329,9 @@ impl Drop for Layout { impl Layout { pub fn new(backend: Box) -> Self { let spare = match backend.made() { - true => backend.blocks().into_iter().map(|b| b.id).collect(), + true => (backend.blocks().unwrap_or_default().into_iter()) + .map(|b| b.id) + .collect(), false => Vec::new(), }; Self { @@ -320,7 +341,7 @@ impl Layout { listed: None, placed: None, news: Arc::default(), - watching: None, + following: Arc::default(), } } @@ -332,38 +353,45 @@ impl Layout { let Some(Watch { from, heard }) = self.backend.watch() else { return; }; - let lines: Box = match from { - Stream::Command(mut command) => { - let Ok(mut child) = command - .stdin(Stdio::null()) - .stdout(Stdio::piped()) - .stderr(Stdio::null()) - .spawn() - else { - return; - }; - let Some(out) = child.stdout.take() else { - return; - }; - self.watching = Some(Watching::Command(child)); - Box::new(BufReader::new(out)) - } - Stream::Socket(path, ask) => { - let Ok(mut socket) = UnixStream::connect(&path) else { - return; - }; - let Ok(kept) = socket.try_clone() else { - return; - }; - if writeln!(socket, "{ask}").is_err() { + let news = self.news.clone(); + let following = self.following.clone(); + std::thread::spawn(move || { + let (stream, lines): (_, Box) = match from { + Stream::Command(mut command) => { + let Ok(mut child) = command + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + else { + return; + }; + let Some(out) = child.stdout.take() else { + return; + }; + (Watching::Command(child), Box::new(BufReader::new(out))) + } + Stream::Socket(path, ask) => { + let Ok(mut socket) = UnixStream::connect(&path) else { + return; + }; + let Ok(kept) = socket.try_clone() else { + return; + }; + if writeln!(socket, "{ask}").is_err() { + return; + } + (Watching::Socket(kept), Box::new(BufReader::new(socket))) + } + }; + { + let mut following = following.lock().unwrap_or_else(|e| e.into_inner()); + if following.gone { + stream.close(); return; } - self.watching = Some(Watching::Socket(kept)); - Box::new(BufReader::new(socket)) + following.stream = Some(stream); } - }; - let news = self.news.clone(); - std::thread::spawn(move || { for line in lines.lines() { let Ok(line) = line else { break }; let said = serde_json::from_str(&line).map_or(Heard::Changed, |line| heard(&line)); @@ -390,7 +418,8 @@ impl Layout { } } - /// The home's blocks, as last read unless the home changed since. + /// The home's blocks, as last read unless the home changed since. A + /// read that failed is none, and tried again when next asked. fn listed(&mut self) -> Vec { let now = self.news.changes(); if let Some((at, blocks)) = &self.listed @@ -398,7 +427,9 @@ impl Layout { { return blocks.clone(); } - let blocks = self.backend.blocks(); + let Ok(blocks) = self.backend.blocks() else { + return Vec::new(); + }; self.listed = Some((now, blocks.clone())); blocks } @@ -515,6 +546,7 @@ impl Layout { } /// Where each block sits, as last read unless the home changed since. + /// A read that failed is nowhere, and tried again when next asked. pub fn places(&mut self) -> Places { let now = self.news.changes(); if let Some((at, places)) = &self.placed @@ -522,7 +554,9 @@ impl Layout { { return places.clone(); } - let places = self.backend.places(); + let Ok(places) = self.backend.places() else { + return Places::new(); + }; self.placed = Some((now, places.clone())); places } @@ -850,6 +884,8 @@ mod tests { made: bool, /// How many times the blocks were listed. lists: Arc, + /// While set, the blocks cannot be read. + down: Arc, news: RefCell>, } @@ -875,15 +911,18 @@ mod tests { fn made(&self) -> bool { self.made } - fn blocks(&self) -> Vec { + fn blocks(&self) -> Result, String> { self.lists.fetch_add(1, Ordering::SeqCst); - (self.blocks.borrow().iter()) + if self.down.load(Ordering::SeqCst) { + return Err("down".into()); + } + Ok((self.blocks.borrow().iter()) .map(|b| Block { id: b.id.clone(), label: b.label.clone(), window: b.window.clone(), }) - .collect() + .collect()) } fn close_block(&self, block: &str) { self.blocks.borrow_mut().retain(|b| b.id != block); @@ -928,8 +967,8 @@ mod tests { fn own_block(&self) -> Option { self.own.clone() } - fn places(&self) -> Places { - Places::new() + fn places(&self) -> Result { + Ok(Places::new()) } fn place(&self) -> Place { Place::Herdr { @@ -995,6 +1034,7 @@ mod tests { let listener = std::os::unix::net::UnixListener::bind(&path).unwrap(); let fake = Fake::default(); let lists = fake.lists.clone(); + let down = fake.down.clone(); fake.add("shop.fix", "w1"); *fake.news.borrow_mut() = Some(Watch { from: Stream::Socket(path, "{\"ask\":1}".into()), @@ -1028,14 +1068,25 @@ mod tests { hear(&news, 2); layout.windows(); assert_eq!(lists.load(Ordering::SeqCst), 2); + // A read that failed is not kept: it is tried again until it is + // read. + say("changed"); + hear(&news, 3); + down.store(true, Ordering::SeqCst); + assert!(layout.windows().is_empty()); + layout.windows(); + down.store(false, Ordering::SeqCst); + assert_eq!(layout.windows().len(), 1); + layout.windows(); + assert_eq!(lists.load(Ordering::SeqCst), 5); // Once the news ends, each batch reads the home again. say("over"); - hear(&news, 3); + hear(&news, 4); assert!(!news.up.load(Ordering::SeqCst)); layout.windows(); layout.fresh(); layout.windows(); - assert_eq!(lists.load(Ordering::SeqCst), 4); + assert_eq!(lists.load(Ordering::SeqCst), 7); let _ = std::fs::remove_dir_all(&dir); } diff --git a/sonny/src/rex.rs b/sonny/src/rex.rs index b1597f273..d56d3d245 100644 --- a/sonny/src/rex.rs +++ b/sonny/src/rex.rs @@ -135,9 +135,9 @@ impl Rex { let kept = kept.and_then(|kept| sessions.iter().find(|(id, _)| id == kept)); let lead = format!("{label}.lead"); let leads = || { - sessions - .iter() - .find(|(id, _)| rex.blocks_in(id).iter().any(|b| b.label == lead)) + sessions.iter().find(|(id, _)| { + (rex.blocks_in(id).unwrap_or_default().iter()).any(|b| b.label == lead) + }) }; (rex.session, rex.name) = match current { Some((id, now)) => { @@ -159,11 +159,9 @@ impl Rex { Ok(rex) } - fn blocks_in(&self, session: &str) -> Vec { - let Ok(ls) = self.call(&["block", "ls", "-s", session, "--json"]) else { - return Vec::new(); - }; - ls["blocks"] + fn blocks_in(&self, session: &str) -> Result, String> { + let ls = self.call(&["block", "ls", "-s", session, "--json"])?; + Ok(ls["blocks"] .as_array() .into_iter() .flatten() @@ -174,7 +172,7 @@ impl Rex { window: b["window_id"].as_str().unwrap_or("").to_owned(), }) }) - .collect() + .collect()) } /// The new block's ID from `split`'s, `run`'s or `window new`'s reply. @@ -201,7 +199,7 @@ impl Backend for Rex { self.made } - fn blocks(&self) -> Vec { + fn blocks(&self) -> Result, String> { self.blocks_in(&self.session) } @@ -283,8 +281,7 @@ impl Backend for Rex { fn own_block(&self) -> Option { let mine = std::env::var("REX_BLOCK").ok().filter(|b| !b.is_empty())?; - self.blocks() - .into_iter() + (self.blocks().ok()?.into_iter()) .find(|b| b.id == mine || b.id.ends_with(&mine)) .map(|b| b.id) } @@ -309,11 +306,9 @@ impl Backend for Rex { /// Where each block sits, by label: its tab, counted from 1, and in a /// split, its side of its neighbour (`right of`, `below`, … a label). - fn places(&self) -> Places { + fn places(&self) -> Result { let mut places = HashMap::new(); - let Ok(ls) = self.call(&["window", "ls", "-s", &self.session, "--json"]) else { - return places; - }; + let ls = self.call(&["window", "ls", "-s", &self.session, "--json"])?; for (tab, window) in ls["windows"].as_array().into_iter().flatten().enumerate() { for layer in window["layers"].as_array().into_iter().flatten() { let labels: HashMap<&str, &str> = layer["blocks"] @@ -333,7 +328,7 @@ impl Backend for Rex { } } } - places + Ok(places) } } From ac744245968a074e3a63660cfb805b8a75f1a158 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:20:37 +0000 Subject: [PATCH 10/27] Sonny: a close or rename reads the home again rather than guessing it worked Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/mux.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index d63459a0b..6b17b2cdf 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -440,22 +440,16 @@ impl Layout { self.placed = None; } + /// A close or rename is not confirmed by its multiplexer, so the + /// home is read again after rather than guessed. fn close_block(&mut self, block: &str) { self.backend.close_block(block); - if let Some((_, listed)) = &mut self.listed { - listed.retain(|b| b.id != block); - } - self.placed = None; + self.forget(); } fn rename_block(&mut self, block: &str, label: &str) { self.backend.rename_block(block, label); - if let Some((_, listed)) = &mut self.listed { - for b in listed.iter_mut().filter(|b| b.id == block) { - b.label = label.to_owned(); - } - } - self.placed = None; + self.forget(); } /// Close the shell a home made just now came with, now that it holds From 3c533ab21f728a473c4903f2d4f61143e8f27a2b Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 04:58:40 +0000 Subject: [PATCH 11/27] Sonny: out of every multiplexer, an agent's window opens nowhere unseen When the project's lead ran out of Rex, Herdr and cmux, an agent's run_in_pane or show_agent opened a Rex session (or else a Herdr or cmux workspace) that no one watched, and agents got the Rex skill. Now a call goes only where the lead laid the project out, where its lead is, or where the project it was asked from is; otherwise it says nothing opened would be seen. Out of every multiplexer, agents get no layout skill. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/mux.rs | 93 +++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index 6b17b2cdf..f5d41f948 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -779,8 +779,8 @@ pub fn here(socket: &Path, project: &str, host: Option<&str>) -> Option /// in: where the lead's Sonny last laid the project out, in that /// multiplexer; without one, in Herdr or cmux when the project's lead is in /// a Herdr pane or a cmux tab, else in the multiplexer project `like`'s -/// home is in, else in Rex (or Herdr, or cmux, without Rex), made when -/// needed. +/// home is in, made when needed. With none of these, the lead is in no +/// multiplexer, and neither is anything it would see. pub fn for_agent(socket: &Path, project: &str, like: Option<&str>) -> Result { let herdr = crate::herdr::find(); let cmux = crate::cmux::find(); @@ -788,43 +788,46 @@ pub fn for_agent(socket: &Path, project: &str, like: Option<&str>) -> Result = - match (kept, herdr, cmux, rex) { - (Some(Place::Herdr { workspace }), Some(herdr), _, _) => Box::new( - crate::herdr::Herdr::open(herdr, project, None, Some(&workspace))?, - ), - (Some(Place::Cmux { workspace }), _, Some(cmux), _) => Box::new( - crate::cmux::Cmux::open(cmux, project, None, Some(&workspace))?, - ), - (Some(Place::Rex { session, host }), _, _, Some(rex)) => Box::new( - crate::rex::Rex::open(rex, host.as_deref(), project, false, Some(&session))?, - ), - (_, Some(herdr), _, _) - if crate::herdr::has_lead(&herdr, project) - || matches!(like, Some(Place::Herdr { .. })) => - { - Box::new(crate::herdr::Herdr::open(herdr, project, None, None)?) - } - (_, _, Some(cmux), _) - if crate::cmux::has_lead(&cmux, project) - || matches!(like, Some(Place::Cmux { .. })) => - { - Box::new(crate::cmux::Cmux::open(cmux, project, None, None)?) - } - (_, _, _, Some(rex)) if let Some(Place::Rex { host, .. }) = &like => Box::new( - crate::rex::Rex::open(rex, host.as_deref(), project, false, None)?, - ), - (_, _, _, Some(rex)) => { - Box::new(crate::rex::Rex::open(rex, None, project, false, None)?) - } - (_, Some(herdr), _, None) => { - Box::new(crate::herdr::Herdr::open(herdr, project, None, None)?) - } - (_, None, Some(cmux), None) => { - Box::new(crate::cmux::Cmux::open(cmux, project, None, None)?) - } - (_, None, None, None) => return Err("none of rex, herdr or cmux was found".into()), - }; + let backend: Box = match (kept, herdr, cmux, rex) { + (Some(Place::Herdr { workspace }), Some(herdr), _, _) => Box::new( + crate::herdr::Herdr::open(herdr, project, None, Some(&workspace))?, + ), + (Some(Place::Cmux { workspace }), _, Some(cmux), _) => Box::new(crate::cmux::Cmux::open( + cmux, + project, + None, + Some(&workspace), + )?), + (Some(Place::Rex { session, host }), _, _, Some(rex)) => Box::new(crate::rex::Rex::open( + rex, + host.as_deref(), + project, + false, + Some(&session), + )?), + (_, Some(herdr), _, _) + if crate::herdr::has_lead(&herdr, project) + || matches!(like, Some(Place::Herdr { .. })) => + { + Box::new(crate::herdr::Herdr::open(herdr, project, None, None)?) + } + (_, _, Some(cmux), _) + if crate::cmux::has_lead(&cmux, project) + || matches!(like, Some(Place::Cmux { .. })) => + { + Box::new(crate::cmux::Cmux::open(cmux, project, None, None)?) + } + (_, _, _, Some(rex)) if let Some(Place::Rex { host, .. }) = &like => Box::new( + crate::rex::Rex::open(rex, host.as_deref(), project, false, None)?, + ), + // A home no one watches would hide what it runs: the person + // reads this project's work in its lead's Sonny instead. + _ => { + return Err(format!( + "{project}'s lead is not in Rex, Herdr or cmux, so nothing opened there would be seen" + )); + } + }; Ok(Layout::new(backend)) } @@ -851,13 +854,13 @@ pub fn install_skill(host: Option<&str>) { skill("cmux-tabs", text, "CMUX_BIN", bin) } Some(Kind::Rex(bin)) => rex(bin), - // Out of sight, Rex is the default. - None => match crate::rex::find() { - Some(bin) => rex(bin), - None => return, - }, + // Out of every multiplexer, agents get none: nothing they opened + // in one would be seen. + None => ("", String::new()), }; - crate::skills::install(name, &text); + if !name.is_empty() { + crate::skills::install(name, &text); + } for other in ["rex-windows", "herdr-panes", "cmux-tabs"] { if other != name { crate::skills::remove(other); From add98a701f4ca1f880c300db02670075c3f50ded Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:46:47 +0000 Subject: [PATCH 12/27] README: out of every multiplexer, nothing is opened unseen Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sonny/README.md b/sonny/README.md index 274931c00..265d90578 100644 --- a/sonny/README.md +++ b/sonny/README.md @@ -279,7 +279,9 @@ The place a project was laid out in is kept in `~/.agent/sonny/places`, so an agent's `run_in_pane` and `show_agent` calls go to that Herdr or cmux workspace or Rex session, in that multiplexer, rather than one guessed from where the -tool host answering them runs, which is in neither. A task's window +tool host answering them runs, which is in neither. A project whose lead is +in no multiplexer gets none: such a call says nothing opened would be seen, +and its agents get no layout skill. A task's window closes once its work is taken: its turn completed and the lead (or the task that started it) waited on it. One that failed, stopped or waits on you stays open, and a task given more work opens again; ask the lead to show one, or From 7e73981387fa46ac5d6768d833f1c8f07577199c Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:47:02 +0000 Subject: [PATCH 13/27] FEATURES: #500 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 80d58347a..4f25ec08b 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -61,7 +61,7 @@ per feature; group small things into the row they belong to. | Feature | Status | History | Why | Used? | |---|---|---|---|---| -| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492; a home read again only once its host says it changed 10-31 #493 | Watch and click into every agent from the multiplexer | ? | +| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492; a home read again only once its host says it changed 10-31 #493; out of every multiplexer, nothing opens unseen (no Rex fallback) 10-31 #500 | Watch and click into every agent from the multiplexer | ? | | Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489; every host call off the screen thread 10-31 #492; a workspace read again only once Herdr says it changed 10-31 #493 | George's other multiplexer | ? | | cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323; every host call off the screen thread 10-31 #492; a workspace read again only once cmux says it changed 10-31 #493 | A third multiplexer to try | ? | | Sessions for leads, tabs for tasks, a split only to show something now (`sonny run`, `sonny show`) | shipped | `sonny run` renamed `sonny split` 10-26 #165; sessions and tabs 10-27 #200; no programs started while a window idles 10-29 #311 | Tabs are a job stream to click into; a split is for attention now | ? | From b997e468b2741529f031891a82802d95a62bd577 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:52:12 +0000 Subject: [PATCH 14/27] Sonny: out of every multiplexer, layout skills are left as they are Another project's Sonny still in its multiplexer keeps the skill it installed; this one only installs none. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/mux.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index f5d41f948..cef00642f 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -854,13 +854,12 @@ pub fn install_skill(host: Option<&str>) { skill("cmux-tabs", text, "CMUX_BIN", bin) } Some(Kind::Rex(bin)) => rex(bin), - // Out of every multiplexer, agents get none: nothing they opened - // in one would be seen. - None => ("", String::new()), + // Out of every multiplexer, none is installed: nothing opened in + // one would be seen. One installed stays, for another project's + // Sonny still in its multiplexer. + None => return, }; - if !name.is_empty() { - crate::skills::install(name, &text); - } + crate::skills::install(name, &text); for other in ["rex-windows", "herdr-panes", "cmux-tabs"] { if other != name { crate::skills::remove(other); From 41120281f48d9ff425c7830ea2526febba79de1f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:05:23 +0000 Subject: [PATCH 15/27] Sonny: an agent's command or window can float over its window, in Rex run_in_pane and show_agent take split "float": for something the person deals with once and closes (a sign-in, a quick look). Rex floats a block only in its own window, so it is split beside its anchor, then floated with session.float_block over the middle of the window, taking neither focus nor the active window. Where a multiplexer cannot float (Herdr's popup is plugin-only and modal, cmux's canvas is a whole-workspace mode), it is split on the right. Only Rex's skill mentions it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/skills/rex-windows/SKILL.md | 4 +- sonny/src/main.rs | 8 ++-- sonny/src/mux.rs | 24 ++++++++++-- sonny/src/rex.rs | 62 +++++++++++++++++++++++++++++++ sonny/src/served.rs | 20 ++++++---- 5 files changed, 101 insertions(+), 17 deletions(-) diff --git a/sonny/skills/rex-windows/SKILL.md b/sonny/skills/rex-windows/SKILL.md index f416c0529..8677997f9 100644 --- a/sonny/skills/rex-windows/SKILL.md +++ b/sonny/skills/rex-windows/SKILL.md @@ -14,14 +14,14 @@ Sessions are for leads: each project's lead has a Rex session of its own, and it ## Where things go - An agent you start gets a tab of its own when its turn starts, without your doing anything. -- A command of yours that belongs in a block (a dev server, a test watcher, a build or test run over 10 minutes, a log tail, a REPL) goes in one of its own with the `run_in_pane` tool, named for what it is (`dev-server`, `tests`), not a number. A tab is the default; `split` (right, left, above, below) puts it beside you, to show it now. +- A command of yours that belongs in a block (a dev server, a test watcher, a build or test run over 10 minutes, a log tail, a REPL) goes in one of its own with the `run_in_pane` tool, named for what it is (`dev-server`, `tests`), not a number. A tab is the default; `split` (right, left, above, below) puts it beside you, to show it now, and `split` `float` floats it over your window, for something the person deals with once and closes: a sign-in, a quick look. It runs as `sh -c` runs the command, in your folder (or `cwd`), in the person's shell, which stays open after the command exits so the output can be read. The result holds the block's ID as `block`; keep it. The block is labelled `YOUR-NAME/NAME`, so the person can see whose it is. Running the same name again replaces the old block. Your shell's variables are not there, so put what the command needs in it: `env VAR=value COMMAND`. To wait for the command to end, end it with a line you can wait for: `cargo test; echo "exited $?"`. - Never make a Rex session or window yourself (`rex new`, `rex window new`, `rex run`, `rex split`), and never use `$REX_SESSION` or `$REX_BLOCK`: they may name a terminal that is not yours. - Never close a window or block the person has typed in or asked you to open; ask them first. Close only what you opened for your own work. -- When the person asks for an agent somewhere else ("put it next to the lead", "open that in a split", "give it its own tab"), or they should watch one beside you now, move it with the `show_agent` tool, before or after starting it: `split` puts it on that side of you, or of the agent `beside` names, and without `split` it gets its own tab again. +- When the person asks for an agent somewhere else ("put it next to the lead", "open that in a split", "give it its own tab"), or they should watch one beside you now, move it with the `show_agent` tool, before or after starting it: `split` puts it on that side of you, or of the agent `beside` names (`float` floats it over that window), and without `split` it gets its own tab again. - Another project's lead you hand work to (`PROJECT.lead`) has a Rex session of its own, not a tab in yours. When the person wants to watch it, or you start a lead that is not open anywhere yet, open it there with `show_agent` and no `split`; one already open is left as it is. diff --git a/sonny/src/main.rs b/sonny/src/main.rs index 7a9f62473..32e7be555 100644 --- a/sonny/src/main.rs +++ b/sonny/src/main.rs @@ -266,9 +266,9 @@ async fn show( split: Option, beside: Option, ) -> Result { - if let Some(side) = split.as_deref().filter(|side| !mux::SIDES.contains(side)) { + if let Some(side) = split.as_deref().filter(|side| !mux::SPLITS.contains(side)) { return Err(format!( - "{side:?} is not a side: right, left, above or below" + "{side:?} is not a side: right, left, above, below or float" )); } let (socket, project) = agents_project(caller).await?; @@ -364,9 +364,9 @@ async fn run_in_pane( if name.is_empty() || name.contains('/') { return Err("a name without /".into()); } - if let Some(side) = split.as_deref().filter(|side| !mux::SIDES.contains(side)) { + if let Some(side) = split.as_deref().filter(|side| !mux::SPLITS.contains(side)) { return Err(format!( - "{side:?} is not a side: right, left, above or below" + "{side:?} is not a side: right, left, above, below or float" )); } if !cwd.is_absolute() || !cwd.is_dir() { diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index cef00642f..35c6be9d9 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -92,8 +92,10 @@ pub struct Block { pub window: String, } -/// The sides a split may be on. -pub const SIDES: [&str; 4] = ["right", "left", "above", "below"]; +/// Where a block may go beside another: on a side, or floating over its +/// window, for a moment. +pub const SPLITS: [&str; 5] = ["right", "left", "above", "below", FLOAT]; +pub const FLOAT: &str = "float"; /// Where `show_agent` puts a task. pub enum Placement { @@ -147,6 +149,18 @@ pub trait Backend: Send { command: &[String], keep: bool, ) -> Result; + /// A new block floating over `anchor`'s window, as `split` makes one. + /// A multiplexer that cannot float one splits it on the right. + fn float( + &self, + anchor: &str, + label: &str, + cwd: &Path, + command: &[String], + keep: bool, + ) -> Result { + self.split(anchor, "right", false, label, cwd, command, keep) + } /// The block Sonny itself runs in, when in this home. fn own_block(&self) -> Option; fn places(&self) -> Result; @@ -515,7 +529,10 @@ impl Layout { .find(|b| &b.label == beside && b.label != bot) .map(|b| b.id.clone()) .ok_or(format!("no block labelled {beside} in {}", self.home()))?; - (self.backend).split(&anchor, side, false, bot, cwd, command, false)? + match side.as_str() { + FLOAT => (self.backend).float(&anchor, bot, cwd, command, false)?, + side => (self.backend).split(&anchor, side, false, bot, cwd, command, false)?, + } } }; self.forget(); @@ -614,6 +631,7 @@ impl Layout { } let anchor = listed.iter().find(|b| b.label == owner); let block = match anchor.zip(side) { + Some((anchor, FLOAT)) => (self.backend).float(&anchor.id, &label, cwd, command, true), Some((anchor, side)) => { (self.backend).split(&anchor.id, side, true, &label, cwd, command, true) } diff --git a/sonny/src/rex.rs b/sonny/src/rex.rs index d56d3d245..926f59aa7 100644 --- a/sonny/src/rex.rs +++ b/sonny/src/rex.rs @@ -52,6 +52,10 @@ fn heard(line: &Value) -> Heard { } } +/// Where a floating block sits over its window, as fractions of it: the +/// middle, with a tenth of the window around it. +const FLOATING: [f64; 4] = [0.1, 0.1, 0.8, 0.8]; + pub struct Rex { bin: PathBuf, /// `-S HOST` when the session lives on another Rex server. @@ -279,6 +283,31 @@ impl Backend for Rex { Ok(self.made(&args)?.0) } + /// Split beside `anchor`, then floated over its window, as Rex floats + /// a block only in its own window. Floating takes neither focus nor + /// the active window; one Rex refuses stays split. + fn float( + &self, + anchor: &str, + label: &str, + cwd: &Path, + command: &[String], + keep: bool, + ) -> Result { + let block = self.split(anchor, "right", false, label, cwd, command, keep)?; + let float = serde_json::json!({"block_id": block, "bounds": FLOATING}); + let float = float.to_string(); + let _ = self.call(&[ + "api", + "call", + "-s", + &self.session, + "session.float_block", + &float, + ]); + Ok(block) + } + fn own_block(&self) -> Option { let mine = std::env::var("REX_BLOCK").ok().filter(|b| !b.is_empty())?; (self.blocks().ok()?.into_iter()) @@ -474,6 +503,39 @@ if out is not None: let _ = std::fs::remove_dir_all(&dir); } + #[test] + fn a_command_floats_over_its_owners_window() { + let (bin, dir) = fake( + "float", + serde_json::json!({"01home": {"label": "graft", "blocks": [block("block:task", "graft.fix")]}}), + ); + let rex = super::Rex::open(bin, None, "graft", false, None).unwrap(); + let mut rex = crate::mux::Layout::new(Box::new(rex)); + let login = ["gh".into(), "auth".into(), "login".into()]; + let made = rex.split( + "graft.fix", + "login", + Path::new("/work"), + &login, + Some("float"), + ); + let made = made.unwrap(); + let calls = std::fs::read_to_string(dir.join("calls")).unwrap(); + let calls: Vec<&str> = calls.lines().rev().take(2).collect(); + // Split beside its owner, then floated, as Rex floats a block only + // in its own window. + assert!( + calls[1].starts_with("split -b block:task --split right") + && calls[1].contains("--label graft.fix/login --keep-open"), + "{calls:?}" + ); + let float = format!( + r#"api call -s 01home session.float_block {{"block_id":"{made}","bounds":[0.1,0.1,0.8,0.8]}}"# + ); + assert_eq!(calls[0], float); + let _ = std::fs::remove_dir_all(&dir); + } + #[test] fn an_agent_runs_its_commands_in_its_leads_session_and_they_close_with_it() { // The lead was opened in a session named for something else; a diff --git a/sonny/src/served.rs b/sonny/src/served.rs index ea17130b3..adbf77558 100644 --- a/sonny/src/served.rs +++ b/sonny/src/served.rs @@ -62,7 +62,7 @@ const OUTPUT_BYTES: usize = 60 * 1024; /// start missed while the daemon restarted is answered by then. const SWEEP: Duration = Duration::from_secs(30); -const SPLIT: [&str; 4] = crate::mux::SIDES; +const SPLIT: [&str; 5] = crate::mux::SPLITS; /// What an agent may do beyond what every agent may: a lead makes /// projects, and an agent with room below it in its lead's line, as the @@ -479,15 +479,17 @@ fn show() -> Value { json!({ "name": SHOW, "description": "Put an agent's window where the person asks, or where they should watch it now: a tab \ - of its own, the default, or a split on one side of yours, or of the agent beside names. Another \ - project's lead (PROJECT.lead) opens in its project's own home instead, unless it is open there \ - already. Every agent you start gets a tab of its own when its turn starts, without this.", + of its own, the default, or a split on one side of yours, or of the agent beside names, or \ + floating over that window for a moment where the terminal can float it. Another project's \ + lead (PROJECT.lead) opens in its project's own home instead, unless it is open there already. \ + Every agent you start gets a tab of its own when its turn starts, without this.", "parameters": { "type": "object", "properties": { "agent": {"type": "string", "minLength": 1, "description": "The agent's full name."}, "split": {"type": "string", "enum": SPLIT, - "description": "Beside you, or beside the agent beside names, on this side."}, + "description": "Beside you, or beside the agent beside names, on this side, or \ + float over its window."}, "beside": {"type": "string", "minLength": 1, "description": "The agent to split beside."} }, "required": ["agent"], @@ -504,8 +506,9 @@ fn run() -> Value { that runs longer than the shell tool allows, keeps running (a dev server, a watcher), or that the \ person wants to watch. It runs in your folder, or cwd, in the person's shell, which stays open after \ the command exits so its output can be read. Running the same name again replaces its pane. A tab \ - is the default; a split puts it beside you, for something the person should see now. The result \ - holds the pane's ID as block.", + is the default; a split puts it beside you, for something the person should see now, and float \ + over your window, where the terminal can float it, for a moment (a sign-in, a quick look). The \ + result holds the pane's ID as block.", "parameters": { "type": "object", "properties": { @@ -515,7 +518,8 @@ fn run() -> Value { "description": "The command line, as sh -c runs it. End it with a line you can wait \ for, such as `cargo test; echo \"exited $?\"`, to know when it ends."}, "cwd": {"type": "string", "minLength": 1, "description": "Where it runs, absolute."}, - "split": {"type": "string", "enum": SPLIT, "description": "Beside you, on this side."} + "split": {"type": "string", "enum": SPLIT, + "description": "Beside you, on this side, or float over your window."} }, "required": ["name", "command"], "additionalProperties": false From d641ec6370eb6c945ef948db99c3648a0224aae0 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 15:56:32 +0000 Subject: [PATCH 16/27] README: float, a split over the agent's window in Rex Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sonny/README.md b/sonny/README.md index 42eb8a8e8..46147492f 100644 --- a/sonny/README.md +++ b/sonny/README.md @@ -304,9 +304,10 @@ server; `--no-layout` turns the layout off. Sonny writes the skill for the multiplexer it runs in to `~/.agents/skills` (`rex-windows`, `herdr-panes` or `cmux-tabs`, from `skills/`), so any agent can run a long command where the person can watch it, and read it back: the `run_in_pane` tool -(`name`, `command`, `split` right, left, above or below) opens a block +(`name`, `command`, `split` right, left, above, below or float) opens a block labelled `AGENT/NAME` in a tab of its own (or on that side of the agent's -own block) and gives its ID. The `show_agent` tool (`agent`, `split`, +own block; `float`, in Rex, over the agent's window, for something +looked at once and closed) and gives its ID. The `show_agent` tool (`agent`, `split`, `beside`) moves a task's window. Tabs are the default; the skills tell agents to split only to show the person something now, such as a server's or a tool's output, or an agent to watch beside them. Sessions are for leads, tabs and splits for From 1452f77819cf8876598e2091dfbdb7a0bed186c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 15:56:49 +0000 Subject: [PATCH 17/27] FEATURES: #523 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 7d074964b..02aa37652 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -63,7 +63,7 @@ per feature; group small things into the row they belong to. | Feature | Status | History | Why | Used? | |---|---|---|---|---| -| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492; a home read again only once its host says it changed 10-31 #493; out of every multiplexer, nothing opens unseen (no Rex fallback) 10-31 #500 | Watch and click into every agent from the multiplexer | ? | +| Rex: tasks in Rex windows, one Rex home per project | shipped | added 10-26 #146; one home per project 10-26 #160; multiplexer interface 10-26 #166; every host call off the screen thread, window names coalesced 10-31 #492; a home read again only once its host says it changed 10-31 #493; out of every multiplexer, nothing opens unseen (no Rex fallback) 10-31 #500; `float` over the agent's window 10-31 #523 | Watch and click into every agent from the multiplexer | ? | | Herdr: the same layout in Herdr, Sonny as a Herdr harness | shipped | added 10-26 #167; as Herdr's own agents 10-27 #178; survives Herdr restarts 10-28 #231; host calls time out, small split above/left fixed 10-31 #489; every host call off the screen thread 10-31 #492; a workspace read again only once Herdr says it changed 10-31 #493 | George's other multiplexer | ? | | cmux: the same layout in cmux, Automation mode for agents | experimental | added 10-29 #300, #323; every host call off the screen thread 10-31 #492; a workspace read again only once cmux says it changed 10-31 #493 | A third multiplexer to try | ? | | Sessions for leads, tabs for tasks, a split only to show something now (`sonny run`, `sonny show`) | shipped | `sonny run` renamed `sonny split` 10-26 #165; sessions and tabs 10-27 #200; no programs started while a window idles 10-29 #311 | Tabs are a job stream to click into; a split is for attention now | ? | From 39907ba1bbd96026f8861a8783b78eeea0d3828e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:10:16 +0000 Subject: [PATCH 18/27] Sonny: an agent's name in the side pane goes to its own tab, as Ctrl+T does MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Where the lead's tasks are laid out in Rex, Herdr or cmux, clicking a task's name in the side pane's title, or Ctrl+T, brings its own tab into view there: Rex makes its window the session's active one, Herdr focuses its tab, cmux its surface. The ↗ beside the name still opens it in the main view. Out of a multiplexer the name is only a name and the hint leaves Ctrl+T out. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/cmux.rs | 5 ++++ sonny/src/herdr.rs | 4 +++ sonny/src/main.rs | 53 +++++++++++++++++++++++++++++---- sonny/src/mux.rs | 28 ++++++++++++++++++ sonny/src/pane.rs | 74 +++++++++++++++++++++++++++++++++++++++++++--- sonny/src/rex.rs | 20 +++++++++++++ 6 files changed, 174 insertions(+), 10 deletions(-) diff --git a/sonny/src/cmux.rs b/sonny/src/cmux.rs index e30d156e9..ea666d32b 100644 --- a/sonny/src/cmux.rs +++ b/sonny/src/cmux.rs @@ -437,6 +437,11 @@ impl Backend for Cmux { } } + fn focus(&self, block: &Block) -> Result<(), String> { + let at = json!({"surface_id": block.id, "workspace_id": self.workspace}); + self.call("surface.focus", at).map(|_| ()) + } + /// cmux's heartbeats keep the stream from outliving Sonny: the first /// one written after it is gone ends the command. fn watch(&self) -> Option { diff --git a/sonny/src/herdr.rs b/sonny/src/herdr.rs index 17b5c7da9..e259b1232 100644 --- a/sonny/src/herdr.rs +++ b/sonny/src/herdr.rs @@ -563,6 +563,10 @@ impl Backend for Herdr { } } + fn focus(&self, block: &Block) -> Result<(), String> { + self.call(&["tab", "focus", &block.window]).map(|_| ()) + } + /// Herdr's CLI has no stream; its socket does, and Herdr names it in /// every pane it runs, as in the one Sonny runs in. fn watch(&self) -> Option { diff --git a/sonny/src/main.rs b/sonny/src/main.rs index aeef628ae..ae5f90d1b 100644 --- a/sonny/src/main.rs +++ b/sonny/src/main.rs @@ -2341,6 +2341,13 @@ struct App { suggestion: Option, } +/// What a job of the lead's layout has to say: a line in the lead's view, +/// or, for something the person asked of it, a notice. +enum Told { + Line(String), + Notice(String), +} + /// The lead's tasks, laid out in the project's home in a multiplexer: a window for /// each task while it works, opened as its turn starts. It follows every /// bot on its own connection, so the lead's own view is not disturbed. @@ -2349,8 +2356,8 @@ struct Fleet { /// the screen. layout: mux::Worker, /// What its jobs have to say in the lead's view, as they finish. - said: tokio::sync::mpsc::UnboundedReceiver, - tell: tokio::sync::mpsc::UnboundedSender, + said: tokio::sync::mpsc::UnboundedReceiver, + tell: tokio::sync::mpsc::UnboundedSender, client: Arc, events: Events, socket: PathBuf, @@ -2706,12 +2713,23 @@ impl Fleet { Err(error) => Some(error), }; if let Some(said) = said.filter(|_| tell) { - let _ = told.send(said); + let _ = told.send(Told::Line(said)); } }); self.doing(bot, "working"); } + /// Bring the task's own tab into view in the multiplexer, as the + /// person asked. + fn go_to(&self, bot: &str) { + let (bot, told) = (bot.to_owned(), self.tell.clone()); + self.layout.send(move |layout| { + if let Err(error) = layout.focus(&bot) { + let _ = told.send(Told::Notice(error)); + } + }); + } + /// Close the task's window, and every block it asked for. fn close(&self, bot: &str) { let bot = bot.to_owned(); @@ -3276,10 +3294,14 @@ impl App { // What the layout's worker said, between the daemon's events. let event = match news { Ok(event) => event, - Err(said) => { + Err(Told::Line(said)) => { self.fleet_said(said); continue; } + Err(Told::Notice(notice)) => { + self.notice = Some(notice); + continue; + } }; let Some(event) = event else { if let Some(fleet) = &mut self.fleet { @@ -4455,8 +4477,9 @@ impl App { match self.embed(&name, &args) { // Its title's button opens it in the main view. Ok(()) => { + let tab = self.fleet.is_some(); if let Some(pane) = &mut self.pane { - pane.as_agent(bot); + pane.as_agent(bot, tab); } } Err(error) => self.notice = Some(format!("could not open {name}: {error}")), @@ -7677,6 +7700,14 @@ impl App { self.pressed.push(format!("main/{agent}")); return true; } + // Ctrl+T goes to its own tab in the multiplexer, as its name does. + if pane::to_tab(key) + && let Some(pane) = self.pane.as_ref().filter(|p| p.has_tab()) + && let Some(agent) = pane.agent.clone() + { + self.pressed.push(format!("tab/{agent}")); + return true; + } // Ctrl+[ and Ctrl+] step the pane, but for a program that has them. let esc = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE); let program = self.pane_keys && self.pane.as_ref().is_some_and(|p| p.program.is_some()); @@ -8010,6 +8041,7 @@ impl App { ("ctrl+\\", "keys to the pane and back"), ("ctrl+[ ctrl+]", "hide the pane, show it again"), ("ctrl+g", "open the pane's agent in the main view"), + ("ctrl+t", "go to the pane's agent's own tab"), ("alt+t", "boost the next message; again for more"), ("ctrl+v", "paste a picture"), ("ctrl+c", "copy the selection, clear, or quit"), @@ -8276,6 +8308,15 @@ impl App { "status" if self.options.side.is_none() => self.status().await, // An agent's title button: this window becomes its Sonny. "main" => self.switch_command(id).await, + // Its name: its own tab, where the lead's tasks are laid out. + "tab" => match &self.fleet { + Some(fleet) => fleet.go_to(id), + None => { + self.notice = Some(format!( + "{id} has no tab: this Sonny is not in Rex, Herdr or cmux" + )) + } + }, "background" if self.options.side.is_none() => self.background(), "status" | "background" => {} _ => self.press_row(what).await, @@ -11805,7 +11846,7 @@ mod tests { .is_some_and(|n| n.starts_with("/switch NAME")) ); let mut pane = pane::Pane::card("shop.lead", STATUS_KEYS); - pane.as_agent("shop.lead"); + pane.as_agent("shop.lead", false); app.open_pane(pane); app.input.set("/switch"); app.submit().await; diff --git a/sonny/src/mux.rs b/sonny/src/mux.rs index 35c6be9d9..8e508545e 100644 --- a/sonny/src/mux.rs +++ b/sonny/src/mux.rs @@ -161,6 +161,8 @@ pub trait Backend: Send { ) -> Result { self.split(anchor, "right", false, label, cwd, command, keep) } + /// Bring the block into view: its window made the one shown. + fn focus(&self, block: &Block) -> Result<(), String>; /// The block Sonny itself runs in, when in this home. fn own_block(&self) -> Option; fn places(&self) -> Result; @@ -576,6 +578,17 @@ impl Layout { self.backend.rename_window(window, name); } + /// Bring the bot's block into view where the person watches. + pub fn focus(&mut self, bot: &str) -> Result<(), String> { + let known = self.blocks.get(bot).cloned(); + let listed = self.listed(); + let block = (listed.iter()) + .find(|b| Some(&b.id) == known.as_ref()) + .or_else(|| listed.iter().find(|b| b.label == bot)) + .ok_or_else(|| format!("{bot} has no tab in {}", self.home()))?; + self.backend.focus(block) + } + /// Take on the block Sonny itself runs in, when in this home, named /// after the bot. pub fn adopt_own(&mut self, bot: &str) { @@ -901,6 +914,7 @@ mod tests { /// While set, the blocks cannot be read. down: Arc, news: RefCell>, + focused: RefCell>, } impl Fake { @@ -989,6 +1003,10 @@ mod tests { workspace: "w1".into(), } } + fn focus(&self, block: &Block) -> Result<(), String> { + self.focused.borrow_mut().push(block.id.clone()); + Ok(()) + } fn watch(&self) -> Option { self.news.borrow_mut().take() } @@ -1020,6 +1038,16 @@ mod tests { } } + #[test] + fn a_task_is_brought_into_view_by_its_block() { + let fake = Fake::default(); + fake.add("shop.fix", "w1"); + let mut layout = Layout::new(Box::new(fake)); + assert_eq!(layout.focus("shop.fix"), Ok(())); + let none = layout.focus("shop.gone"); + assert_eq!(none, Err("shop.gone has no tab in Fake home".into())); + } + #[test] fn with_no_news_the_home_is_read_once_a_batch() { let fake = Fake::default(); diff --git a/sonny/src/pane.rs b/sonny/src/pane.rs index 135e3939e..a24b57f72 100644 --- a/sonny/src/pane.rs +++ b/sonny/src/pane.rs @@ -93,6 +93,10 @@ pub struct Pane { /// opens in the main view; and that button's columns as last drawn. pub agent: Option, main_at: Option<(u16, u16)>, + /// Where its name was last drawn, when a click on it goes to the + /// agent's own tab in the multiplexer. + name_at: Option<(u16, u16)>, + tab: bool, } /// The button beside an agent's name that opens it in the main view: the @@ -101,6 +105,9 @@ pub struct Pane { const MAIN: &str = " ↗"; /// What the keys do on an agent's Sonny in the pane. const AGENT_KEYS: &str = "ctrl+g main view · ctrl+\\ chat"; +/// The same, for one laid out in a multiplexer, whose tab its name and +/// Ctrl+T go to. +const LAID_OUT_KEYS: &str = "ctrl+g main view · ctrl+t its tab · ctrl+\\ chat"; /// Ctrl+G, which opens the agent in the pane in the main view, as its /// title's button does: no Sonny key takes it. @@ -109,6 +116,13 @@ pub fn to_main(key: &ratatui::crossterm::event::KeyEvent) -> bool { key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('g') } +/// Ctrl+T, which goes to the agent's own tab in the multiplexer, as a +/// click on its name does. +pub fn to_tab(key: &ratatui::crossterm::event::KeyEvent) -> bool { + use ratatui::crossterm::event::{KeyCode, KeyModifiers}; + key.modifiers.contains(KeyModifiers::CONTROL) && key.code == KeyCode::Char('t') +} + impl Pane { /// A file's preview, from a `file://` address as a link holds it, /// with a `#L…` line to show, at `width`. Paths are shown from @@ -174,6 +188,8 @@ impl Pane { over: false, agent: None, main_at: None, + name_at: None, + tab: false, }) } @@ -280,6 +296,8 @@ impl Pane { over: false, agent: None, main_at: None, + name_at: None, + tab: false, } } @@ -307,14 +325,26 @@ impl Pane { over: false, agent: None, main_at: None, + name_at: None, + tab: false, } } /// The program is `agent`'s Sonny: its title's button and Ctrl+G open - /// it in the main view. - pub fn as_agent(&mut self, agent: &str) { + /// it in the main view, and, when it is laid out in a multiplexer + /// (`tab`), its name and Ctrl+T go to its own tab there. + pub fn as_agent(&mut self, agent: &str, tab: bool) { self.agent = Some(agent.to_owned()); - self.keys = AGENT_KEYS; + self.tab = tab; + self.keys = match tab { + true => LAID_OUT_KEYS, + false => AGENT_KEYS, + }; + } + + /// Whether Ctrl+T goes to the agent's tab. + pub fn has_tab(&self) -> bool { + self.tab && self.agent.is_some() } /// Where what the pane holds starts, under its title: the program's @@ -375,6 +405,11 @@ impl Pane { { return Some(format!("{}main/{agent}", crate::screen::BUTTON)); } + if let (Some(agent), Some((from, to))) = (&self.agent, self.name_at) + && (from..to).contains(&column) + { + return Some(format!("{}tab/{agent}", crate::screen::BUTTON)); + } let (_, buttons) = self.head.as_ref()?; (buttons.iter()) .find(|(from, to, _)| (*from..*to).contains(&at)) @@ -503,6 +538,8 @@ impl Pane { let gap = (area.width as usize) .saturating_sub(used + title.width() + button.width() + keys.width()); let at = area.x + (used + title.width() + 1) as u16; + let name = area.x + used as u16; + self.name_at = self.tab.then_some((name, name + title.width() as u16)); head.push(Span::styled(title, style)); self.main_at = None; if !button.is_empty() { @@ -760,7 +797,7 @@ mod tests { #[test] fn an_agents_title_opens_it_in_the_main_view_where_it_fits() { let mut pane = Pane::card("shop.fix", "esc close"); - pane.as_agent("shop.fix"); + pane.as_agent("shop.fix", false); let head = rows(&mut pane, 60, 3).remove(0); assert!(head.starts_with(" shop.fix ↗ "), "{head}"); assert!(head.ends_with("ctrl+g main view · ctrl+\\ chat"), "{head}"); @@ -777,6 +814,35 @@ mod tests { assert_eq!(pane.head_button(column), None); } + #[test] + fn a_laid_out_agents_name_goes_to_its_tab() { + let mut pane = Pane::card("shop.fix", "esc close"); + pane.as_agent("shop.fix", true); + assert!(pane.has_tab()); + let head = rows(&mut pane, 72, 3).remove(0); + assert!(head.starts_with(" shop.fix ↗ "), "{head}"); + assert!( + head.ends_with("ctrl+g main view · ctrl+t its tab · ctrl+\\ chat"), + "{head}" + ); + let name = " shop.fix".width() as u16; + assert_eq!(pane.head_button(1).as_deref(), Some("sonny:tab/shop.fix")); + assert_eq!( + pane.head_button(name - 1).as_deref(), + Some("sonny:tab/shop.fix") + ); + assert_eq!(pane.head_button(name), None); + assert_eq!( + pane.head_button(name + 1).as_deref(), + Some("sonny:main/shop.fix") + ); + // Not laid out: the name is only a name. + pane.as_agent("shop.fix", false); + rows(&mut pane, 72, 3); + assert!(!pane.has_tab()); + assert_eq!(pane.head_button(1), None); + } + #[test] fn a_cards_foot_stays_on_the_last_row_however_it_is_scrolled() { let mut pane = Pane::card("/background", "esc close"); diff --git a/sonny/src/rex.rs b/sonny/src/rex.rs index 926f59aa7..ee61d4c5e 100644 --- a/sonny/src/rex.rs +++ b/sonny/src/rex.rs @@ -308,6 +308,26 @@ impl Backend for Rex { Ok(block) } + /// Its window made the session's active one; Rex is not brought to + /// the front. + fn focus(&self, block: &Block) -> Result<(), String> { + let out = crate::mux::output( + Command::new(&self.bin) + .env_remove("REX_SESSION") + .env_remove("REX_BLOCK") + .args(&self.host) + .args(["focus", "-s", &self.session, &block.id]), + ) + .map_err(|e| format!("rex: {e}"))?; + match out.status.success() { + true => Ok(()), + false => Err(format!( + "rex focus: {}", + String::from_utf8_lossy(&out.stderr).trim() + )), + } + } + fn own_block(&self) -> Option { let mine = std::env::var("REX_BLOCK").ok().filter(|b| !b.is_empty())?; (self.blocks().ok()?.into_iter()) From e40ace40a79ce63fe7f32b1e3885db3424ae8387 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:13:20 +0000 Subject: [PATCH 19/27] Sonny: a laid out agent's name keeps its room, its tab key going first Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/pane.rs | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/sonny/src/pane.rs b/sonny/src/pane.rs index a24b57f72..12cead463 100644 --- a/sonny/src/pane.rs +++ b/sonny/src/pane.rs @@ -107,7 +107,7 @@ const MAIN: &str = " ↗"; const AGENT_KEYS: &str = "ctrl+g main view · ctrl+\\ chat"; /// The same, for one laid out in a multiplexer, whose tab its name and /// Ctrl+T go to. -const LAID_OUT_KEYS: &str = "ctrl+g main view · ctrl+t its tab · ctrl+\\ chat"; +const LAID_OUT_KEYS: &str = "ctrl+g main view · ctrl+\\ chat · ctrl+t its tab"; /// Ctrl+G, which opens the agent in the pane in the main view, as its /// title's button does: no Sonny key takes it. @@ -470,14 +470,15 @@ impl Pane { if area.height == 0 { return None; } - let keys = match focused { + let mut keys = match focused { true if !self.back.is_empty() => format!(" ← back · {} ", self.keys), true => format!(" {} ", self.keys), false => format!(" {switch} "), }; let mut style = Style::new(); - // A file's name is a link to it. - if self.link.is_some() { + // A file's name is a link to it, as a laid out agent's is to its + // tab. + if self.link.is_some() || self.tab { style = style.add_modifier(Modifier::UNDERLINED); } if !focused { @@ -501,6 +502,22 @@ impl Pane { head.push(arrow("›", !self.forward.is_empty())); head.push(Span::raw(" ")); } + // A name keeps its room: the last keys go first, as the footer's + // least urgent do. + if self.head.is_none() { + let button = if self.agent.is_some() { + MAIN.width() + } else { + 0 + }; + let name = + head.iter().map(|s| s.content.width()).sum::() + self.title.width() + button; + while name + keys.width() > area.width as usize + && let Some((kept, _)) = keys.trim_end().rsplit_once(" · ") + { + keys = format!("{kept} "); + } + } // Keys in their one grammar while they go here, else dim. let key_spans = match focused { true => crate::hint::spans(&keys), @@ -822,7 +839,7 @@ mod tests { let head = rows(&mut pane, 72, 3).remove(0); assert!(head.starts_with(" shop.fix ↗ "), "{head}"); assert!( - head.ends_with("ctrl+g main view · ctrl+t its tab · ctrl+\\ chat"), + head.ends_with("ctrl+g main view · ctrl+\\ chat · ctrl+t its tab"), "{head}" ); let name = " shop.fix".width() as u16; @@ -836,6 +853,13 @@ mod tests { pane.head_button(name + 1).as_deref(), Some("sonny:main/shop.fix") ); + // Narrower, the keys go from the last before the name is cut. + let head = rows(&mut pane, 48, 3).remove(0); + assert!(head.starts_with(" shop.fix ↗ "), "{head}"); + assert!(head.ends_with("ctrl+g main view · ctrl+\\ chat"), "{head}"); + let head = rows(&mut pane, 32, 3).remove(0); + assert!(head.starts_with(" shop.fix ↗ "), "{head}"); + assert!(head.ends_with("ctrl+g main view"), "{head}"); // Not laid out: the name is only a name. pane.as_agent("shop.fix", false); rows(&mut pane, 72, 3); From f9965599971c5abc025c6eba19c2aff09f372ec8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 05:45:47 +0000 Subject: [PATCH 20/27] Sonny: a click on an agent's name goes to its tab, a right click beside; agent_opens swaps them An agent laid out in Rex, Herdr or cmux is worked with in its own tab, so a click on its name, its roster row or its /status row goes there; a right click opens the look beside the conversation. [sonny] agent_opens = "beside" swaps the two. An agent with no tab opens beside either way. The pane's title draws a laid out agent's name in the agents' blue, as any agent's name that opens it is drawn. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- client/src/config.rs | 31 +++++++++++++++++++++++++++++++ sonny/README.md | 18 +++++++++++------- sonny/src/config.rs | 6 ++++++ sonny/src/main.rs | 30 +++++++++++++++++++++++++++--- sonny/src/pane.rs | 9 ++++++--- sonny/src/screen.rs | 21 +++++++++++++++++++++ sonny/src/settings.rs | 3 +++ 7 files changed, 105 insertions(+), 13 deletions(-) diff --git a/client/src/config.rs b/client/src/config.rs index c558cdcf8..1129f1184 100644 --- a/client/src/config.rs +++ b/client/src/config.rs @@ -29,6 +29,7 @@ //! chime = false # rings when work ends or waits, the default //! verbose = true # every tool call drawn, not the clean view //! margin_opens = "never" # "always", the default, or "unless-closed" +//! agent_opens = "beside" # a click on an agent's name: "tab", the default, or "beside" //! suggest = false # no guess at the next message, on by default //! commons = false # agents go without the commons, on by default //! commons_invites = 3 # least invites a day new threads send (more with the square root of the week's agents), 0 none; answers add up to as many again @@ -167,6 +168,9 @@ pub struct Sonny { pub verbose: bool, /// When a note the agent puts up or changes opens the margin. pub margin_opens: MarginOpens, + /// Where a click on an agent's name takes the person; a right click + /// takes them to the other. + pub agent_opens: AgentOpens, /// Whether a copy of the agent guesses the person's next message after /// a turn they started, shown in the empty composer for Tab to take. pub suggest: bool, @@ -218,6 +222,7 @@ impl Default for Sonny { verbose: false, start_at_boot: false, margin_opens: MarginOpens::default(), + agent_opens: AgentOpens::default(), sources: Vec::new(), browser_confirm: BROWSER_CONFIRM.map(str::to_owned).to_vec(), } @@ -281,6 +286,17 @@ pub enum Reviewer { None, } +/// Where a click on an agent's name takes the person: `"tab"`, the +/// default, to the agent's own tab, where a lead in Rex, Herdr or cmux laid +/// it out; or `"beside"`, a look at it beside the conversation. A right +/// click takes them to the other, and an agent with no tab opens beside. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum AgentOpens { + #[default] + Tab, + Beside, +} + /// A command Sonny's lookout runs, whose every printed line is news for /// the agents subscribed to it by name. #[derive(Clone, Debug, PartialEq)] @@ -593,6 +609,7 @@ fn parse(text: &str) -> Result { "chime", "verbose", "margin_opens", + "agent_opens", "suggest", "start_at_boot", "commons", @@ -723,6 +740,16 @@ fn parse(text: &str) -> Result { }; continue; } + if key == "agent_opens" { + config.sonny.agent_opens = match value.as_str() { + "tab" => AgentOpens::Tab, + "beside" => AgentOpens::Beside, + other => { + return Err(format!("sonny.agent_opens is tab or beside, not {other}")); + } + }; + continue; + } // The daemon judges models and levels when the agents are made. *match (name.as_str(), key.as_str()) { ("judge", "model") => &mut config.judge.model, @@ -1141,6 +1168,10 @@ mod tests { assert_eq!(config.sonny.margin_opens, MarginOpens::Always); let never = parse("[sonny]\nmargin_opens = \"never\"").unwrap(); assert_eq!(never.sonny.margin_opens, MarginOpens::Never); + assert_eq!(config.sonny.agent_opens, AgentOpens::Tab); + let beside = parse("[sonny]\nagent_opens = \"beside\"").unwrap(); + assert_eq!(beside.sonny.agent_opens, AgentOpens::Beside); + assert!(parse("[sonny]\nagent_opens = \"window\"").is_err()); let closed = parse("[sonny]\nmargin_opens = \"unless-closed\"").unwrap(); assert_eq!(closed.sonny.margin_opens, MarginOpens::UnlessClosed); assert!(config.sonny.chime && !config.sonny.verbose); diff --git a/sonny/README.md b/sonny/README.md index eeb50340a..b875a1ec0 100644 --- a/sonny/README.md +++ b/sonny/README.md @@ -245,6 +245,7 @@ at its model's own effort (`AGENT_EFFORT` when its model is | A guess at your next message | `[sonny] suggest` (true, the default, or false) | | | Whether agents have the commons | `[sonny] commons` (true, the default, or false) | | | When a note opens the margin | `[sonny] margin_opens` (`always`, the default, `unless-closed` or `never`) | | +| Where a click on an agent's name goes | `[sonny] agent_opens` (`tab`, the default, or `beside`) | | | The daemon started with the machine | `[sonny] start_at_boot` (true or false, the default) | | | News from outside for the lookout | `[sonny.sources.NAME] command`, `every` | | @@ -491,7 +492,7 @@ way `/react` does, and each note in the margin has its own `↩`. - `/config` (or `/settings`) lists every key `~/.agent/config.toml` takes, with its value or what its default is, over the composer. ↑/↓ picks one; ←/→ steps a key with a few values (efforts, `web_search`, - `senders`, `side_chat`, `chime`, `verbose`, `margin_opens`, `suggest`, + `senders`, `side_chat`, `chime`, `verbose`, `margin_opens`, `agent_opens`, `suggest`, `start_at_boot`) through them and its default; Enter types one that takes a name (`model`, `provider`, `judge.model`, the profiles), empty for its default; ⌫ @@ -617,8 +618,8 @@ way `/react` does, and each note in the margin has its own `↩`. reopens it. Each agent's time is how long its running turn has been at it, or how long its last turn took. An agent with automations or follows of its own - shows `↻ 2 automations` beside its model. A click on an agent's name opens - it in the pane, as a click on a task in a roster does, and so does + shows `↻ 2 automations` beside its model. A click on an agent's name goes + to it as a click on a task in a roster does (below), and so does Enter on the agent ↑ and ↓ pick (`›`); ‹ in its title, or Ctrl+[ from the conversation, goes back to `/status`. It opens with the agent the conversation named last already picked, so @@ -1009,15 +1010,18 @@ say did not fail, but did not reply either, so it reads `no reply`, not once a turn ends, and `checked` once its goal's check passed. ![A task whose goal's check passed beside one still checking and one only done](../docs/sonny/tasks-checked.png) A click -on a task opens it in the pane: its own Sonny, live, to watch and to talk -to, as a side chat is. Ctrl+\ takes the keys back to the conversation and +on a task goes to its own tab, where the lead laid it out in Rex, Herdr or +cmux; a right click (or a click, with no tab) opens it in the pane: its own +Sonny, live, to watch and to talk to, as a side chat is. `[sonny] +agent_opens = "beside"` swaps the two. In the pane, its name in the title +goes to its tab too, as Ctrl+T does. Ctrl+\ takes the keys back to the conversation and gives them to the pane again; from the conversation Ctrl+[ hides the task and Ctrl+] shows it again. ![Three tasks started together, each with what it does now](../docs/sonny/tasks-roster.png) -An agent's name anywhere in the conversation opens that agent in the -pane, as a GitHub `@name` links to its account: in a reply ("tally.halves +An agent's name anywhere in the conversation goes to that agent, as a +click on a task does, the way a GitHub `@name` links to its account: in a reply ("tally.halves has a question for you"), in a row of task updates, in a message from another agent, and in the pane's own text. Only a name the daemon knows is one, so it is drawn in the agents' blue, and a word that only looks diff --git a/sonny/src/config.rs b/sonny/src/config.rs index 78f84a5a0..2315b33fc 100644 --- a/sonny/src/config.rs +++ b/sonny/src/config.rs @@ -134,6 +134,12 @@ pub const SETTINGS: [Setting; 21] = [ values: &["always", "unless-closed", "never"], default: "always", }, + Setting { + key: "sonny.agent_opens", + about: "where a click on an agent's name takes you: its tab in Rex, Herdr or cmux, or a look beside the conversation; a right click, the other", + values: &["tab", "beside"], + default: "tab", + }, Setting { key: "sonny.suggest", about: "after a turn you started, a copy of the agent guesses your next message for Tab to take; one small call", diff --git a/sonny/src/main.rs b/sonny/src/main.rs index ae5f90d1b..ae36d7bb1 100644 --- a/sonny/src/main.rs +++ b/sonny/src/main.rs @@ -1026,6 +1026,7 @@ async fn run() -> Result<(), String> { app.verbose = settings.verbose; app.zen = zen::focus(app.reactions_key.as_deref()); app.margin_opens = settings.margin_opens; + app.agent_opens = settings.agent_opens; // A bot keeps the model it was made with; --model runs this window's // turns on another. if let Some(model) = options.model.as_ref().filter(|m| **m != app.model) { @@ -2146,6 +2147,8 @@ struct App { /// last looked at, and whether such a note opens it. margin_posted: u64, margin_opens: agent_client::config::MarginOpens, + /// Where a click on an agent's name takes the person. + agent_opens: agent_client::config::AgentOpens, /// The pane closed last, with its `/status` or `/background`, which /// Ctrl+] opens again. closed: Option<(pane::Pane, Option)>, @@ -3119,6 +3122,7 @@ impl App { pane_keys: false, margin_posted: 0, margin_opens: Default::default(), + agent_opens: Default::default(), pane_mouse: false, closed: None, composer_at: None, @@ -4469,6 +4473,19 @@ impl App { /// The task `bot` opened in the pane: its own Sonny, to watch it and /// talk to it. + /// An agent named or picked: to its own tab, when the lead's layout + /// has one for it, or a look at it in the pane, as `[sonny] + /// agent_opens` says; `other`, a right click, the one it doesn't. With + /// no tab, it opens in the pane. + fn open_agent(&mut self, bot: &str, other: bool) { + use agent_client::config::AgentOpens; + let tab = (self.agent_opens == AgentOpens::Tab) != other; + match &self.fleet { + Some(fleet) if tab && fleet.layout.places().contains_key(bot) => fleet.go_to(bot), + _ => self.open_task(bot), + } + } + fn open_task(&mut self, bot: &str) { let name = (self.tasks.values().flatten()) .find(|task| task.bot == bot) @@ -4547,7 +4564,9 @@ impl App { /// What a button clicked in the conversation does: `fold/N` opens or /// folds the calls folded as N; `said/NODE` opens or shuts the message /// between agents at NODE; `task/BOT`, and `agent/BOT` on BOT's name - /// in the conversation, opens BOT in the pane; `answer/N/ID` answers the question + /// in the conversation, goes to BOT (see `open_agent`), and + /// `other/BOT`, a right click on either, the other way; + /// `answer/N/ID` answers the question /// call ID asked with its option N, while it is open. async fn press_row(&mut self, button: &str) { if let Some(at) = button @@ -4563,7 +4582,10 @@ impl App { } } if let Some(bot) = (button.strip_prefix("task/")).or(button.strip_prefix("agent/")) { - self.open_task(bot); + self.open_agent(bot, false); + } + if let Some(bot) = button.strip_prefix("other/") { + self.open_agent(bot, true); } if let Some(node) = button .strip_prefix("said/") @@ -7752,7 +7774,8 @@ impl App { }; if key.code == KeyCode::Enter { if let Some(at) = pick { - self.open_task(&bots[at]); + let bot = bots[at].clone(); + self.open_agent(&bot, false); } } else if let Some(watch) = &mut self.watch { watch.pick = pick; @@ -8789,6 +8812,7 @@ impl App { let settings = settings::Settings::load(); self.sounds.chime = settings.chime; self.margin_opens = settings.margin_opens; + self.agent_opens = settings.agent_opens; if self.verbose != settings.verbose { self.verbose = settings.verbose; self.repaint = true; diff --git a/sonny/src/pane.rs b/sonny/src/pane.rs index 12cead463..c83980e18 100644 --- a/sonny/src/pane.rs +++ b/sonny/src/pane.rs @@ -476,10 +476,13 @@ impl Pane { false => format!(" {switch} "), }; let mut style = Style::new(); - // A file's name is a link to it, as a laid out agent's is to its - // tab. - if self.link.is_some() || self.tab { + // A file's name is a link to it. A laid out agent's goes to its + // tab, so it is drawn as any agent's name that opens it is: in the + // agents' color, never underlined. + if self.link.is_some() { style = style.add_modifier(Modifier::UNDERLINED); + } else if self.tab { + style = style.fg(ratatui::style::Color::Blue); } if !focused { style = style.add_modifier(Modifier::DIM); diff --git a/sonny/src/screen.rs b/sonny/src/screen.rs index ebf751564..f39dbab4c 100644 --- a/sonny/src/screen.rs +++ b/sonny/src/screen.rs @@ -1695,6 +1695,16 @@ impl Screen { selection.dragged = true; } } + // A right click on an agent's name or row: the other way to it + // than a click's, its tab or a look beside. + MouseEventKind::Down(MouseButton::Right) => { + let (.., url) = (self.links.iter()).find(|(y, from, to, _)| { + *y == event.row && (*from..*to).contains(&event.column) + })?; + let bot = (url.strip_prefix(crate::links::AGENT)) + .or(url.strip_prefix(&format!("{BUTTON}task/")))?; + return Some(Action::Open(format!("{BUTTON}other/{bot}"))); + } MouseEventKind::Up(MouseButton::Left) => { let selection = self.selection.as_ref()?; if selection.dragged || selection.unit != Unit::Cell { @@ -2966,6 +2976,17 @@ mod tests { ); assert_eq!(screen.last_agent().as_deref(), Some("calc.neg")); assert!(screen.link_cells(&buf).is_empty(), "never an OSC 8 link"); + // A right click on a name is the other way to it; elsewhere, + // nothing. + let right = |screen: &mut Screen, x, y| { + screen.mouse(mouse(MouseEventKind::Down(MouseButton::Right), x, y)) + }; + assert_eq!( + right(&mut screen, 2, 0), + Some(Action::Open("sonny:other/calc.neg".into())) + ); + assert_eq!(right(&mut screen, 3, 1), None); + assert_eq!(right(&mut screen, 8, 0), None); } #[test] diff --git a/sonny/src/settings.rs b/sonny/src/settings.rs index 6ae6cd946..47a442a1d 100644 --- a/sonny/src/settings.rs +++ b/sonny/src/settings.rs @@ -11,6 +11,8 @@ pub struct Settings { pub verbose: bool, /// When a note opens the margin; `/config` changes it. pub margin_opens: agent_client::config::MarginOpens, + /// Where a click on an agent's name takes the person. + pub agent_opens: agent_client::config::AgentOpens, } impl Default for Settings { @@ -30,6 +32,7 @@ impl Settings { chime: sonny.chime, verbose: sonny.verbose, margin_opens: sonny.margin_opens, + agent_opens: sonny.agent_opens, } } From e0c03db772cb87f8e69003881e483371ea8d13e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 15:42:23 +0000 Subject: [PATCH 21/27] Sonny: /config counts agent_opens Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/src/config.rs b/sonny/src/config.rs index 2315b33fc..ff5c6b213 100644 --- a/sonny/src/config.rs +++ b/sonny/src/config.rs @@ -49,7 +49,7 @@ fn short(key: &str) -> &str { } /// Every key `agent_client::config` reads. -pub const SETTINGS: [Setting; 21] = [ +pub const SETTINGS: [Setting; 22] = [ Setting { key: "model", about: "a new agent's model, when none is named (provider/model)", From 1d55e6591489015b72aa901cd7b44533ac84efe8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 15:57:27 +0000 Subject: [PATCH 22/27] README: the pane's title, laid out and not Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- docs/sonny/agent-pane-title.png | Bin 0 -> 197633 bytes sonny/README.md | 5 ++++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 docs/sonny/agent-pane-title.png diff --git a/docs/sonny/agent-pane-title.png b/docs/sonny/agent-pane-title.png new file mode 100644 index 0000000000000000000000000000000000000000..451aec3a0f0639e0886decd62111d3e774aa5771 GIT binary patch literal 197633 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
  • oB;^Xlt}h*MYLu^1{l%An}~ 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

    R%MwHdx!bRvmy!>?J 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 literal 0 HcmV?d00001 diff --git a/sonny/README.md b/sonny/README.md index b875a1ec0..3cee713f7 100644 --- a/sonny/README.md +++ b/sonny/README.md @@ -1014,7 +1014,10 @@ on a task goes to its own tab, where the lead laid it out in Rex, Herdr or cmux; a right click (or a click, with no tab) opens it in the pane: its own Sonny, live, to watch and to talk to, as a side chat is. `[sonny] agent_opens = "beside"` swaps the two. In the pane, its name in the title -goes to its tab too, as Ctrl+T does. Ctrl+\ takes the keys back to the conversation and +goes to its tab too, drawn in the agents' blue, as Ctrl+T does; where the +title is narrow, its keys go from the last before the name is cut. + +![The pane's title at 72, 59 and 40 columns, outside a multiplexer and laid out, with the keys in the pane and in the chat](../docs/sonny/agent-pane-title.png) Ctrl+\ takes the keys back to the conversation and gives them to the pane again; from the conversation Ctrl+[ hides the task and Ctrl+] shows it again. From d31393193303b88c69495214766d45f45e22f147 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 16:52:38 +0000 Subject: [PATCH 23/27] FEATURES: a click on an agent's name goes to its tab Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index ce72652a6..541d7d172 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -88,7 +88,7 @@ names it: upgrade step `ID`. Both name the release they are deleted in. | Staged messages, the open line while a reply streams, turn timers | shipped | streaming 10-26 #172; staged 10-27 #175; timers 10-28 #266; each message a call streams drawn apart 10-30 #396 | Show what is in flight | ? | | Footer: what you talk to and where on the left (permissions mark when not auto, model and effort, the folder and branch), what wants you on the right (needs you, failed, working, ↻ background, ✎ notes, 📎, context left); every item has a long and a short form and gives way by one ladder when the row is too long, what needs you never | shipped | context 10-26 #163; branch 10-27 #182; ↻ 10-28 #267; agents at work 10-29 #367; context as % left of the window the daemon sends 10-30 #432; ✗ counts failures not yet seen in `/background` 10-30; the profile, only when not `lead` or `task` 10-31; decluttered (George, 10-31): tiers and a give-way ladder (words before marks before items, as Powerline drops segments), `⏵⏵ full access` / `⏸ ask me` / `⊘ read only` marks, the model as pickers name it, folders cut by whole parts as fish does, no agent name (the composer says it), hues only on what asks something of you, counts spaced instead of dotted, ✗ and ↻ open `/background` 10-31 | One line of state under the composer that stays readable in 44 to 100 columns | ? | | Colour: 16 terminal colours, one meaning each; marks say it first, muted gray and diff tints from the theme's own colours, `NO_COLOR` honoured | shipped | added 10-29 #326; one visual system 10-30 #405; a hue on a dim row drawn at full strength 10-31 | Colour that carries meaning, in any theme, and reads with low contrast, colour blindness or no colour | ? | -| Links, file paths and agents' names clickable | shipped | a path opens from the folder that named it 10-10 #98; web addresses 10-27 #179; link text, paths 10-27 #230; never drawn over a list 10-30 #416; copied, not opened, over SSH with no display 10-30 #437; an agent's name opens it in the pane 10-30 #435 | Open what an agent names | ? | +| Links, file paths and agents' names clickable | shipped | a path opens from the folder that named it 10-10 #98; web addresses 10-27 #179; link text, paths 10-27 #230; never drawn over a list 10-30 #416; copied, not opened, over SSH with no display 10-30 #437; an agent's name opens it in the pane 10-30 #435; a click goes to its tab when laid out, a right click opens it beside, `agent_opens` swaps them 10-31 #NEW | Open what an agent names | ? | | Find (Ctrl+F) and long histories (older pages on scroll, Home to the first turn) | shipped | Ctrl+F 10-28 #258, 10-29 #343; pages 10-29 #331; lay out near the view 10-29 #353; Home 10-29 #364 | Long conversations stay fast and searchable | ? | | One Markdown renderer for every surface; code coloured with Oniguruma; headings bold with dim `#` marks, no hue | shipped | renderer 10-29 #325; Oniguruma 10-29 #336; Markdown as prose 10-30 #405 | Same rendering everywhere, three times faster colouring | ? | | Pictures: SVG, Mermaid, math and HTML drawn in the terminal | shipped | added 10-26 #147; `\[ \]` math 10-29 #313; sent once 10-29 #338; Mermaid and math drawn by Sonny's own QuickJS, not node or bun, so nothing to install 10-31 | Agents can show, not only tell | ? | From 716c79b713c4311d271e7a7fb1680f3b39126b6e Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 16:52:55 +0000 Subject: [PATCH 24/27] FEATURES: #533 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/FEATURES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sonny/FEATURES.md b/sonny/FEATURES.md index 541d7d172..91afecc20 100644 --- a/sonny/FEATURES.md +++ b/sonny/FEATURES.md @@ -88,7 +88,7 @@ names it: upgrade step `ID`. Both name the release they are deleted in. | Staged messages, the open line while a reply streams, turn timers | shipped | streaming 10-26 #172; staged 10-27 #175; timers 10-28 #266; each message a call streams drawn apart 10-30 #396 | Show what is in flight | ? | | Footer: what you talk to and where on the left (permissions mark when not auto, model and effort, the folder and branch), what wants you on the right (needs you, failed, working, ↻ background, ✎ notes, 📎, context left); every item has a long and a short form and gives way by one ladder when the row is too long, what needs you never | shipped | context 10-26 #163; branch 10-27 #182; ↻ 10-28 #267; agents at work 10-29 #367; context as % left of the window the daemon sends 10-30 #432; ✗ counts failures not yet seen in `/background` 10-30; the profile, only when not `lead` or `task` 10-31; decluttered (George, 10-31): tiers and a give-way ladder (words before marks before items, as Powerline drops segments), `⏵⏵ full access` / `⏸ ask me` / `⊘ read only` marks, the model as pickers name it, folders cut by whole parts as fish does, no agent name (the composer says it), hues only on what asks something of you, counts spaced instead of dotted, ✗ and ↻ open `/background` 10-31 | One line of state under the composer that stays readable in 44 to 100 columns | ? | | Colour: 16 terminal colours, one meaning each; marks say it first, muted gray and diff tints from the theme's own colours, `NO_COLOR` honoured | shipped | added 10-29 #326; one visual system 10-30 #405; a hue on a dim row drawn at full strength 10-31 | Colour that carries meaning, in any theme, and reads with low contrast, colour blindness or no colour | ? | -| Links, file paths and agents' names clickable | shipped | a path opens from the folder that named it 10-10 #98; web addresses 10-27 #179; link text, paths 10-27 #230; never drawn over a list 10-30 #416; copied, not opened, over SSH with no display 10-30 #437; an agent's name opens it in the pane 10-30 #435; a click goes to its tab when laid out, a right click opens it beside, `agent_opens` swaps them 10-31 #NEW | Open what an agent names | ? | +| Links, file paths and agents' names clickable | shipped | a path opens from the folder that named it 10-10 #98; web addresses 10-27 #179; link text, paths 10-27 #230; never drawn over a list 10-30 #416; copied, not opened, over SSH with no display 10-30 #437; an agent's name opens it in the pane 10-30 #435; a click goes to its tab when laid out, a right click opens it beside, `agent_opens` swaps them 10-31 #533 | Open what an agent names | ? | | Find (Ctrl+F) and long histories (older pages on scroll, Home to the first turn) | shipped | Ctrl+F 10-28 #258, 10-29 #343; pages 10-29 #331; lay out near the view 10-29 #353; Home 10-29 #364 | Long conversations stay fast and searchable | ? | | One Markdown renderer for every surface; code coloured with Oniguruma; headings bold with dim `#` marks, no hue | shipped | renderer 10-29 #325; Oniguruma 10-29 #336; Markdown as prose 10-30 #405 | Same rendering everywhere, three times faster colouring | ? | | Pictures: SVG, Mermaid, math and HTML drawn in the terminal | shipped | added 10-26 #147; `\[ \]` math 10-29 #313; sent once 10-29 #338; Mermaid and math drawn by Sonny's own QuickJS, not node or bun, so nothing to install 10-31 | Agents can show, not only tell | ? | From 0a1ef5b693284eb839494f92544d700d2ac4933f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 16:58:30 +0000 Subject: [PATCH 25/27] Sonny: a task's window goes to an agent's tab too, its project's layout found off the screen Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/main.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/sonny/src/main.rs b/sonny/src/main.rs index ae36d7bb1..5429e584d 100644 --- a/sonny/src/main.rs +++ b/sonny/src/main.rs @@ -1952,6 +1952,9 @@ struct App { /// The read under way, of the workspace named, and when the last one /// began: the branch can change under a running turn. place_job: Option<(String, tokio::task::JoinHandle)>, + /// The agent whose tab is being looked for, from a window with no + /// layout of its own, and the look. + tab_job: Option<(String, tokio::task::JoinHandle>)>, place_read: Option<(String, Instant)>, model: String, effort: Option, @@ -3019,6 +3022,7 @@ impl App { workspace: record["workspace"].as_str().unwrap_or("").to_owned(), place: place::Place::default(), place_job: None, + tab_job: None, place_read: None, model, effort: record["effort"].as_str().map(str::to_owned), @@ -3375,6 +3379,7 @@ impl App { self.shell_ran().await; self.listed(); self.follow_place().await; + self.follow_tab().await; self.count_behind().await; self.read_tasks().await; self.read_running_tasks().await; @@ -4482,10 +4487,50 @@ impl App { let tab = (self.agent_opens == AgentOpens::Tab) != other; match &self.fleet { Some(fleet) if tab && fleet.layout.places().contains_key(bot) => fleet.go_to(bot), + // A task's window, or one in the pane, lays nothing out + // itself: its project's layout is found off the screen, and + // with no tab there the agent opens in the pane after all. + None if tab && self.tab_job.is_none() => { + let (client, socket, me) = ( + self.client.clone(), + self.options.socket.clone(), + self.bot.clone(), + ); + let lead = self.project.clone(); + let bot = bot.to_owned(); + let job = tokio::spawn({ + let bot = bot.clone(); + async move { + let project = match lead { + Some(project) => project, + None => project_of(&client, &me).await, + }; + tokio::task::spawn_blocking(move || { + mux::for_agent(&socket, &project, None)?.focus(&bot) + }) + .await + .map_err(|e| e.to_string())? + } + }); + self.tab_job = Some((bot, job)); + } _ => self.open_task(bot), } } + /// The tab looked for off the screen: with none, the agent opens in + /// the pane, as a click on it does where nothing is laid out. + async fn follow_tab(&mut self) { + if let Some((_, job)) = &self.tab_job + && job.is_finished() + { + let (bot, job) = self.tab_job.take().unwrap(); + if !matches!(job.await, Ok(Ok(()))) { + self.open_task(&bot); + } + } + } + fn open_task(&mut self, bot: &str) { let name = (self.tasks.values().flatten()) .find(|task| task.bot == bot) From 413b2ae9e3402c2a18b0bb8900d1633f4839ea34 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 01:00:39 +0000 Subject: [PATCH 26/27] Sonny: the lead looks for a clicked agent's tab on the layout worker, not in its last read Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/main.rs | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/sonny/src/main.rs b/sonny/src/main.rs index 1aeca5d0b..6b3a27113 100644 --- a/sonny/src/main.rs +++ b/sonny/src/main.rs @@ -2390,10 +2390,12 @@ struct App { } /// What a job of the lead's layout has to say: a line in the lead's view, -/// or, for something the person asked of it, a notice. +/// or, for something the person asked of it, a notice; or that an agent +/// clicked has no tab, so it opens in the pane. enum Told { Line(String), Notice(String), + Beside(String), } /// The lead's tasks, laid out in the project's home in a multiplexer: a window for @@ -2792,12 +2794,16 @@ impl Fleet { } /// Bring the task's own tab into view in the multiplexer, as the - /// person asked. - fn go_to(&self, bot: &str) { + /// person asked; looked for on the worker, after every window opened + /// so far. With none, `beside` opens it in the pane, else says so. + fn go_to(&self, bot: &str, beside: bool) { let (bot, told) = (bot.to_owned(), self.tell.clone()); self.layout.send(move |layout| { if let Err(error) = layout.focus(&bot) { - let _ = told.send(Told::Notice(error)); + let _ = told.send(match beside { + true => Told::Beside(bot), + false => Told::Notice(error), + }); } }); } @@ -3395,6 +3401,10 @@ impl App { self.notice = Some(notice); continue; } + Err(Told::Beside(bot)) => { + self.open_task(&bot); + continue; + } }; let Some(event) = event else { if let Some(fleet) = &mut self.fleet { @@ -4562,8 +4572,6 @@ impl App { turn } - /// The task `bot` opened in the pane: its own Sonny, to watch it and - /// talk to it. /// An agent named or picked: to its own tab, when the lead's layout /// has one for it, or a look at it in the pane, as `[sonny] /// agent_opens` says; `other`, a right click, the one it doesn't. With @@ -4572,7 +4580,7 @@ impl App { use agent_client::config::AgentOpens; let tab = (self.agent_opens == AgentOpens::Tab) != other; match &self.fleet { - Some(fleet) if tab && fleet.layout.places().contains_key(bot) => fleet.go_to(bot), + Some(fleet) if tab => fleet.go_to(bot, true), // A task's window, or one in the pane, lays nothing out // itself: its project's layout is found off the screen, and // with no tab there the agent opens in the pane after all. @@ -4617,6 +4625,8 @@ impl App { } } + /// The task `bot` opened in the pane: its own Sonny, to watch it and + /// talk to it. fn open_task(&mut self, bot: &str) { let name = (self.tasks.values().flatten()) .find(|task| task.bot == bot) @@ -8559,7 +8569,7 @@ impl App { "main" => self.switch_command(id).await, // Its name: its own tab, where the lead's tasks are laid out. "tab" => match &self.fleet { - Some(fleet) => fleet.go_to(id), + Some(fleet) => fleet.go_to(id, false), None => { self.notice = Some(format!( "{id} has no tab: this Sonny is not in Rex, Herdr or cmux" From ff499dd1b2ccb113d151a8c548ef50a3f473b5f8 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 1 Nov 2026 01:08:00 +0000 Subject: [PATCH 27/27] Sonny: the ungated run in the gate test isn't timed on 300 ms another test's script can take Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_013jkdGEWm1MS4yiwexivszT --- sonny/src/exec.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sonny/src/exec.rs b/sonny/src/exec.rs index 35c2b773e..fe2d7aa7f 100644 --- a/sonny/src/exec.rs +++ b/sonny/src/exec.rs @@ -1925,12 +1925,15 @@ text("done");"#; ] ); assert_eq!(asked[1]["arguments"], json!({"ms": 10})); - // With no gate to ask, every call runs. + // With no gate to ask, every call runs. Its time is not what this + // run checks: 300 ms can go waiting for one of the tool host's + // script slots, shared with every other test running at once. + let roomy = script.replace("\"timeout_ms\": 300", "\"timeout_ms\": 30000"); let said = run( &hub, Some(&dir), "shop.lead", - script, + &roomy, Duration::from_secs(30), None, )