clawmates-node: host terminal uses tmux (resumable, redraw-on-attach)
The node terminal opened a bare bash PTY — bash prints its prompt once, so a freshly-attached xterm showed nothing. Switch to `tmux new-session -A -s clawmates` on the HOST (mirrors the agent terminal, but on the node itself, not in a container): tmux redraws the whole screen on attach (no blank), and the session is resumable across reopens. Starts in $HOME. Falls back to a login shell if tmux is unavailable. ensure_tmux() best-effort installs tmux via the host package manager when the daemon runs as root (systemd); otherwise logs a hint to `apt install tmux`. Rebuilt + re-hosted both binaries. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
cf6c331b02
commit
6e81eaa52b
@@ -43,6 +43,7 @@ async fn main() {
|
|||||||
std::process::exit(2);
|
std::process::exit(2);
|
||||||
}
|
}
|
||||||
tailscale_up(&ts_authkey);
|
tailscale_up(&ts_authkey);
|
||||||
|
ensure_tmux();
|
||||||
let ws_url = ws_url(&server, &token);
|
let ws_url = ws_url(&server, &token);
|
||||||
println!("clawmates-node {VERSION} connecting to {server}");
|
println!("clawmates-node {VERSION} connecting to {server}");
|
||||||
loop {
|
loop {
|
||||||
@@ -277,9 +278,26 @@ async fn open_pty(
|
|||||||
let pair = native_pty_system()
|
let pair = native_pty_system()
|
||||||
.openpty(PtySize { rows, cols, pixel_width: 0, pixel_height: 0 })
|
.openpty(PtySize { rows, cols, pixel_width: 0, pixel_height: 0 })
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
// Prefer a resumable host tmux session (like the agent terminal): `-A` attaches
|
||||||
|
// to the existing "clawmates" session or creates it, and tmux redraws the whole
|
||||||
|
// screen on attach (so the view is never blank). Fall back to a login shell.
|
||||||
|
let mut cmd = if has_tmux() {
|
||||||
|
let mut c = CommandBuilder::new("tmux");
|
||||||
|
c.arg("new-session");
|
||||||
|
c.arg("-A");
|
||||||
|
c.arg("-s");
|
||||||
|
c.arg("clawmates");
|
||||||
|
c
|
||||||
|
} else {
|
||||||
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".to_owned());
|
let shell = std::env::var("SHELL").unwrap_or_else(|_| "/bin/bash".to_owned());
|
||||||
let mut cmd = CommandBuilder::new(shell);
|
let mut c = CommandBuilder::new(shell);
|
||||||
|
c.arg("-l");
|
||||||
|
c
|
||||||
|
};
|
||||||
cmd.env("TERM", "xterm-256color");
|
cmd.env("TERM", "xterm-256color");
|
||||||
|
if let Ok(home) = std::env::var("HOME") {
|
||||||
|
cmd.cwd(home);
|
||||||
|
}
|
||||||
let child = pair.slave.spawn_command(cmd).map_err(|e| e.to_string())?;
|
let child = pair.slave.spawn_command(cmd).map_err(|e| e.to_string())?;
|
||||||
drop(pair.slave);
|
drop(pair.slave);
|
||||||
let mut reader = pair.master.try_clone_reader().map_err(|e| e.to_string())?;
|
let mut reader = pair.master.try_clone_reader().map_err(|e| e.to_string())?;
|
||||||
@@ -439,3 +457,56 @@ fn tailscale_up(authkey: &str) {
|
|||||||
])
|
])
|
||||||
.status();
|
.status();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Is tmux on PATH?
|
||||||
|
fn has_tmux() -> bool {
|
||||||
|
std::process::Command::new("tmux")
|
||||||
|
.arg("-V")
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Best-effort: install tmux via the host package manager (works when the daemon
|
||||||
|
/// runs as root, e.g. systemd). Non-interactive; if it can't, host terminals fall
|
||||||
|
/// back to a plain login shell.
|
||||||
|
fn ensure_tmux() {
|
||||||
|
if has_tmux() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mgrs: &[(&str, &[&str])] = &[
|
||||||
|
("apt-get", &["install", "-y", "tmux"]),
|
||||||
|
("dnf", &["install", "-y", "tmux"]),
|
||||||
|
("yum", &["install", "-y", "tmux"]),
|
||||||
|
("apk", &["add", "--no-cache", "tmux"]),
|
||||||
|
("pacman", &["-S", "--noconfirm", "tmux"]),
|
||||||
|
("brew", &["install", "tmux"]),
|
||||||
|
];
|
||||||
|
for (mgr, args) in mgrs {
|
||||||
|
let present = std::process::Command::new("sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(format!("command -v {mgr}"))
|
||||||
|
.output()
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !present {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if *mgr == "apt-get" {
|
||||||
|
let _ = std::process::Command::new("apt-get")
|
||||||
|
.arg("update")
|
||||||
|
.env("DEBIAN_FRONTEND", "noninteractive")
|
||||||
|
.output();
|
||||||
|
}
|
||||||
|
let _ = std::process::Command::new(mgr)
|
||||||
|
.args(*args)
|
||||||
|
.env("DEBIAN_FRONTEND", "noninteractive")
|
||||||
|
.output();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if has_tmux() {
|
||||||
|
println!("tmux ready for host terminals");
|
||||||
|
} else {
|
||||||
|
eprintln!("tmux not found (auto-install unavailable) — host terminal will use a plain shell; `apt install tmux` for resumable sessions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user