use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::{Duration, Instant};
use anyhow::{anyhow, bail, Context, Result};
use serde_json::Value;
use supercode_harness::mail_route::{Door, LiveSessions};
use supercode_harness::mailbox::MailAddress;
use supercode_harness::{DiscoveryQuery, HarnessHomes, HarnessId, StorageLocator};
#[derive(clap::Args)]
pub(crate) struct OpenArgs {
#[arg(required_unless_present = "new")]
pub(crate) session: Option<String>,
#[arg(long, value_name = "PROGRAM", conflicts_with_all = ["session", "relocate"])]
pub(crate) new: Option<String>,
#[arg(long, value_name = "KIND", requires = "new")]
pub(crate) kind: Option<String>,
#[arg(long, value_name = "KEY", requires = "new")]
pub(crate) key: Option<String>,
#[arg(long, value_name = "TEXT", requires = "new")]
pub(crate) input: Option<String>,
#[arg(long, value_name = "MACHINE", conflicts_with = "relocate")]
pub(crate) on: Option<String>,
#[arg(long, conflicts_with = "take_control")]
pub(crate) observe: bool,
#[arg(long, value_enum, default_value_t)]
pub(crate) ui: crate::frontend_client::FrontendClientId,
#[arg(long)]
pub(crate) take_control: bool,
#[arg(long)]
pub(crate) remote_instructions: bool,
#[arg(long, value_name = "TOKEN", conflicts_with = "credential_fd")]
pub(crate) token: Option<String>,
#[arg(long, value_name = "FD", conflicts_with = "token")]
pub(crate) credential_fd: Option<i32>,
#[arg(long, value_name = "ID")]
pub(crate) client_id: Option<String>,
#[arg(long)]
pub(crate) acp: bool,
#[arg(long, value_name = "ID", requires = "acp")]
pub(crate) acp_session: Option<String>,
#[arg(long, value_name = "SEQUENCE", requires = "acp")]
pub(crate) after_sequence: Option<u64>,
#[arg(long, value_name = "HARNESS", requires = "receipt_session")]
pub(crate) harness: Option<String>,
#[arg(long = "session", value_name = "SESSION", requires = "harness")]
pub(crate) receipt_session: Option<String>,
#[arg(long, conflicts_with_all = ["new", "relocate"])]
pub(crate) native: bool,
#[arg(long = "move")]
pub(crate) relocate: bool,
#[arg(long)]
pub(crate) detach: bool,
#[arg(last = true)]
pub(crate) args: Vec<String>,
}
enum Resolved {
Session(Target),
Runtime(String),
}
async fn open_runtime(cli: &crate::Cli, open: &OpenArgs, runtime: &str) -> Result<()> {
if open.on.is_some() {
bail!("a runtime on another machine is opened by its URL there");
}
crate::attach_runtime_client_cmd(
cli,
runtime,
open.ui,
open.observe,
open.take_control,
open.remote_instructions,
)
.await
}
struct Target {
harness: String,
id: String,
name: Option<String>,
cwd: Option<PathBuf>,
transcript: Option<PathBuf>,
pid: Option<u32>,
pane: Option<String>,
tmux: Option<String>,
}
pub(crate) async fn run(cli: &crate::Cli, open: &OpenArgs) -> Result<()> {
let view = View {
detach: open.detach,
observe: open.observe,
on: open.on.as_deref(),
context: cli.context.as_deref(),
};
if let Some(program) = &open.new {
return open_new(program, open, cli.cwd.as_deref(), &view);
}
let wanted = open.session.as_deref().unwrap_or_default();
if wanted.starts_with("http://") || wanted.starts_with("https://") {
return crate::attach_cmd(
cli,
crate::AttachConnectOptions {
base_url: wanted,
token: open.token.as_deref(),
credential_fd: open.credential_fd,
client_id: open.client_id.as_deref(),
through_acp: open.acp,
session_id: open.acp_session.clone(),
after_sequence: open.after_sequence,
},
)
.await;
}
if wanted.starts_with("supercode-live://") {
let (Some(harness), Some(session)) = (&open.harness, &open.receipt_session) else {
bail!("a supercode-live:// receipt is opened with --harness and --session, which it is checked against");
};
return crate::harness_attach_cmd(cli, wanted, harness, session).await;
}
if is_daemon_pane(wanted) {
return attach(wanted, &view, wanted);
}
if let Some(record) = supercode_harness::runtime_mail::controlled_runtimes()
.into_iter()
.find(|record| record.runtime_session_id == wanted || record.source.session_id == wanted)
{
return open_runtime(cli, open, &record.runtime_session_id).await;
}
if let Some(machine) = &open.on {
bail!(
"on another machine a pane is named by its id (`p_…`, from `supercode teams panes \
ls --machine {machine}`); `{wanted}` is not one"
);
}
let homes = HarnessHomes::default();
let target = match resolve(&homes, wanted)? {
Resolved::Session(target) => target,
Resolved::Runtime(runtime) => return open_runtime(cli, open, &runtime).await,
};
let label = target.name.clone().unwrap_or_else(|| short(&target.id));
if open.native {
return open_native(&target, open, &view, &label);
}
if let Some(pane) = &target.pane {
return attach(pane, &view, &label);
}
if let Some(pid) = target.pid {
if !open.relocate {
bail!(
"{label} is running outside supercode (pid {pid}{}). Nothing was opened: a \
session has one writer. End it there (`/exit`), or run `supercode open {} --move` \
to end it and open it here.",
target
.tmux
.as_ref()
.map(|tmux| format!(", tmux session {tmux}"))
.unwrap_or_default(),
wanted
);
}
end_process(pid)?;
}
let (program, arguments) = launch_command(&target, &open.args)?;
let key = format!("open:{}:{}", target.harness, target.id);
let mut request = vec![
"launch".to_string(),
format!("--launch-key={key}"),
format!("--pane={}", pane_for(&key)),
format!("--kind={}", target.harness),
];
if let Some(cwd) = &target.cwd {
request.push(format!("--cwd={}", cwd.display()));
}
request.push(program);
request.push("--".into());
request.extend(arguments);
let launched = fleet_json(&request)?;
let pane = launched["pane"]
.as_str()
.ok_or_else(|| anyhow!("the machine daemon opened no pane: {launched}"))?
.to_string();
attach(&pane, &view, &label)
}
fn open_native(target: &Target, open: &OpenArgs, view: &View, label: &str) -> Result<()> {
let transcript = target
.transcript
.as_ref()
.ok_or_else(|| anyhow!("{label} has no transcript file supercode can continue from"))?;
let program = std::env::current_exe().context("locate this supercode")?;
let key = format!("open:native:{}:{}", target.harness, target.id);
let mut request = vec![
"launch".to_string(),
format!("--launch-key={key}"),
format!("--pane={}", pane_for(&key)),
"--kind=supercode".to_string(),
];
if let Some(cwd) = &target.cwd {
request.push(format!("--cwd={}", cwd.display()));
}
request.push(program.display().to_string());
request.push("--".into());
request.push("resume".into());
request.push(transcript.display().to_string());
request.extend(open.args.iter().cloned());
let launched = fleet_json(&request)?;
let pane = launched["pane"]
.as_str()
.ok_or_else(|| anyhow!("the machine daemon opened no pane: {launched}"))?
.to_string();
attach(&pane, view, label)
}
struct View<'a> {
detach: bool,
observe: bool,
on: Option<&'a str>,
context: Option<&'a str>,
}
fn open_new(program: &str, open: &OpenArgs, cwd: Option<&Path>, view: &View) -> Result<()> {
let mut request = vec!["launch".to_string()];
if let Some(machine) = view.on {
request.extend(["--on".to_string(), machine.to_string()]);
if let Some(context) = view.context {
request.push(format!("--context={context}"));
}
}
if let Some(input) = &open.input {
request.push(format!("--input={input}"));
}
if let Some(key) = &open.key {
request.push(format!("--launch-key={key}"));
}
let kind = open.kind.clone().unwrap_or_else(|| match program {
"claude" => HarnessId::CLAUDE_CODE.to_string(),
other => other.to_string(),
});
request.push(format!("--kind={kind}"));
let cwd = match cwd {
Some(cwd) => Some(cwd.to_path_buf()),
None if view.on.is_none() => std::env::current_dir().ok(),
None => None,
};
if let Some(cwd) = cwd {
request.push(format!("--cwd={}", cwd.display()));
}
request.push(program.to_string());
request.push("--".into());
if view.on.is_none() && launcher_skips_prompts() {
let flag = match kind.as_str() {
HarnessId::CLAUDE_CODE => Some("--dangerously-skip-permissions"),
HarnessId::CODEX => Some("--dangerously-bypass-approvals-and-sandbox"),
_ => None,
};
if let Some(flag) = flag.filter(|flag| !open.args.iter().any(|arg| arg == flag)) {
request.push(flag.to_string());
}
}
request.extend(open.args.iter().cloned());
let launched = fleet_json(&request)?;
let pane = launched["pane"]
.as_str()
.ok_or_else(|| anyhow!("the machine daemon opened no pane: {launched}"))?
.to_string();
attach(&pane, view, program)
}
fn resolve(homes: &HarnessHomes, wanted: &str) -> Result<Resolved> {
let live = LiveSessions::read(homes);
let address = MailAddress::parse(wanted).ok();
let running = live.all().iter().find(|session| {
address.as_ref() == Some(&session.address)
|| session.name == wanted
|| session.name.split('@').next() == Some(wanted)
|| session.address.session_id == wanted
});
if let Some(session) = running {
let name = session.name.split('@').next().unwrap_or(&session.name);
if let Door::Runtime(record) = &session.door {
return Ok(Resolved::Runtime(record.runtime_session_id.clone()));
}
let harness = session.address.harness.clone();
let id = session.address.session_id.clone();
let tmux = session
.tmux
.as_deref()
.and_then(|tmux| tmux.split(':').next())
.map(str::to_string);
let pane = tmux.clone().filter(|session| is_daemon_pane(session));
let pid = match session.pid {
Some(pid) => Some(pid),
None if harness == HarnessId::CODEX => codex_pid(&id),
None => None,
};
let mut target = persisted(homes, &harness, &id).unwrap_or(Target {
harness,
id,
name: None,
cwd: None,
transcript: None,
pid: None,
pane: None,
tmux: None,
});
target.name = Some(name.to_string());
if target.cwd.is_none() {
target.cwd = session.cwd.clone();
}
target.pid = pid;
target.tmux = tmux.filter(|_| pane.is_none());
target.pane = pane;
return Ok(Resolved::Session(target));
}
let query = DiscoveryQuery {
harnesses: vec![
HarnessId::new(HarnessId::CLAUDE_CODE),
HarnessId::new(HarnessId::CODEX),
],
homes: homes.clone(),
query: Some(wanted.to_string()),
..Default::default()
};
let mut found: Vec<_> = supercode_harness::discover_sessions(&query)?
.into_iter()
.filter(|descriptor| {
let id = &descriptor.locator.session_id;
id == wanted
|| (wanted.len() >= 6 && id.starts_with(wanted))
|| descriptor.title.as_deref() == Some(wanted)
})
.collect();
found.sort_by_key(|descriptor| std::cmp::Reverse(descriptor.updated_at_ms));
let ids: std::collections::BTreeSet<_> = found
.iter()
.map(|descriptor| descriptor.locator.session_id.clone())
.collect();
if ids.len() > 1 && !found.iter().any(|d| d.locator.session_id == wanted) {
let listed: Vec<_> = ids.iter().take(5).map(|id| short(id)).collect();
bail!(
"`{wanted}` names {} sessions ({}). Name one by its id.",
ids.len(),
listed.join(", ")
);
}
let descriptor = found.into_iter().next().ok_or_else(|| {
anyhow!("no session is named `{wanted}`: not running, and no saved Claude Code or Codex session has that id or name")
})?;
let transcript = match &descriptor.locator.storage {
StorageLocator::File { path } => Some(path.clone()),
_ => None,
};
Ok(Resolved::Session(Target {
harness: descriptor.locator.harness.0.clone(),
id: descriptor.locator.session_id.clone(),
name: descriptor.title.clone(),
cwd: descriptor.cwd.clone(),
transcript,
pid: None,
pane: None,
tmux: None,
}))
}
fn persisted(homes: &HarnessHomes, harness: &str, id: &str) -> Option<Target> {
let query = DiscoveryQuery {
harnesses: vec![HarnessId::new(harness)],
homes: homes.clone(),
..Default::default()
};
let descriptor = supercode_harness::discover_sessions(&query)
.ok()?
.into_iter()
.find(|descriptor| descriptor.locator.session_id == id)?;
let transcript = match &descriptor.locator.storage {
StorageLocator::File { path } => Some(path.clone()),
_ => None,
};
Some(Target {
harness: harness.to_string(),
id: id.to_string(),
name: descriptor.title,
cwd: descriptor.cwd,
transcript,
pid: None,
pane: None,
tmux: None,
})
}
fn launch_command(target: &Target, extra: &[String]) -> Result<(String, Vec<String>)> {
let mut arguments = Vec::new();
let program = match target.harness.as_str() {
HarnessId::CLAUDE_CODE => {
arguments.extend(["--resume".to_string(), target.id.clone()]);
if let Some(name) = &target.name {
arguments.extend(["--name".to_string(), name.clone()]);
}
if let Some(mode) = target
.transcript
.as_deref()
.and_then(|path| last_record_field(path, "permission-mode", &["permissionMode"]))
{
arguments.extend(["--permission-mode".to_string(), mode]);
}
"claude"
}
HarnessId::CODEX => {
arguments.extend(["resume".to_string(), target.id.clone()]);
if let Some(path) = target.transcript.as_deref() {
if let Some(policy) =
last_record_field(path, "turn_context", &["payload", "approval_policy"])
{
arguments.extend(["--ask-for-approval".to_string(), policy]);
}
if let Some(sandbox) =
last_record_field(path, "turn_context", &["payload", "sandbox_policy", "type"])
{
arguments.extend(["--sandbox".to_string(), sandbox]);
}
}
"codex"
}
other => {
bail!("`supercode open` opens Claude Code and Codex sessions; this one is {other}")
}
};
arguments.extend(extra.iter().cloned());
Ok((program.to_string(), arguments))
}
fn launcher_skips_prompts() -> bool {
let homes = HarnessHomes::default();
let Ok(caller) = supercode_harness::mail_route::resolve_caller(
&homes,
&supercode_harness::mail_route::process_ancestry(),
) else {
return false;
};
let query = DiscoveryQuery {
harnesses: vec![HarnessId::new(caller.address.harness.as_str())],
homes: homes.clone(),
query: Some(caller.address.session_id.clone()),
..Default::default()
};
let Some(transcript) = supercode_harness::discover_sessions(&query)
.ok()
.and_then(|found| {
found
.into_iter()
.find(|descriptor| descriptor.locator.session_id == caller.address.session_id)
})
.map(|descriptor| descriptor.locator.storage.path().to_path_buf())
else {
return false;
};
match caller.address.harness.as_str() {
HarnessId::CLAUDE_CODE => {
last_record_field(&transcript, "permission-mode", &["permissionMode"]).as_deref()
== Some("bypassPermissions")
}
HarnessId::CODEX => {
last_record_field(&transcript, "turn_context", &["payload", "approval_policy"])
.as_deref()
== Some("never")
&& last_record_field(
&transcript,
"turn_context",
&["payload", "sandbox_policy", "type"],
)
.as_deref()
== Some("danger-full-access")
}
_ => false,
}
}
fn last_record_field(path: &Path, kind: &str, field: &[&str]) -> Option<String> {
use std::io::{Seek, SeekFrom};
const TAIL: u64 = 4 * 1024 * 1024;
let mut file = std::fs::File::open(path).ok()?;
let length = file.metadata().ok()?.len();
file.seek(SeekFrom::Start(length.saturating_sub(TAIL)))
.ok()?;
let mut tail = String::new();
file.take(TAIL).read_to_string(&mut tail).ok()?;
let marker = format!("\"type\":\"{kind}\"");
tail.lines()
.rev()
.filter(|line| line.contains(&marker))
.find_map(|line| {
let mut value = serde_json::from_str::<Value>(line).ok()?;
for key in field {
value = value.get(key)?.clone();
}
value.as_str().map(str::to_string)
})
}
fn pane_for(launch_key: &str) -> String {
format!("p_{}", &blake3::hash(launch_key.as_bytes()).to_hex()[..16])
}
fn is_daemon_pane(name: &str) -> bool {
name.strip_prefix("p_").is_some_and(|rest| {
!rest.is_empty()
&& rest
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
})
}
fn codex_pid(id: &str) -> Option<u32> {
let output = std::process::Command::new("ps")
.args(["-axo", "pid=,comm="])
.output()
.ok()?;
String::from_utf8_lossy(&output.stdout)
.lines()
.filter_map(|line| {
let mut fields = line.split_whitespace();
let pid: u32 = fields.next()?.parse().ok()?;
let command = fields.next()?;
Path::new(command)
.file_name()
.is_some_and(|name| name == "codex")
.then_some(pid)
})
.find(|pid| {
supercode_harness::codex_peer::session_of_process(*pid)
.is_some_and(|(session, _)| session == id)
})
}
fn end_process(pid: u32) -> Result<()> {
#[cfg(unix)]
{
let alive = |signal| unsafe { libc::kill(pid as libc::pid_t, signal) == 0 };
alive(libc::SIGTERM);
let deadline = Instant::now() + Duration::from_secs(10);
while Instant::now() < deadline {
if !alive(0) {
return Ok(());
}
std::thread::sleep(Duration::from_millis(100));
}
bail!("the session's process (pid {pid}) did not end within 10 seconds; nothing was opened")
}
#[cfg(not(unix))]
bail!("`--move` ends a process by signal, which this platform does not have (pid {pid})")
}
fn attach(pane: &str, view: &View, label: &str) -> Result<()> {
if view.detach {
println!(
"{}",
serde_json::json!({"pane": pane, "session": label, "machine": view.on})
);
return Ok(());
}
let mode = if view.observe { "observe" } else { "control" };
if let Some(machine) = view.on {
let mut request = vec![
"attach".to_string(),
"--on".into(),
machine.to_string(),
pane.to_string(),
format!("--mode={mode}"),
];
if let Some(context) = view.context {
request.push(format!("--context={context}"));
}
return crate::run_fleet(&request);
}
let bound = std::process::Command::new("tmux")
.env_remove("TMUX")
.args([
"bind-key",
"-n",
"C-]",
"if-shell",
"-F",
"#{m:p_*,#{session_name}}",
"detach-client",
"send-keys C-]",
])
.status()
.context("run tmux")?;
if !bound.success() {
bail!("tmux refused the detach key for {pane}");
}
eprintln!("[{label}: ctrl-] detaches; the session keeps running]");
let mut client = std::process::Command::new("tmux");
client.args(["-T", "RGB", "attach-session"]);
if view.observe {
client.arg("-r");
}
client
.args(["-t", pane])
.env("TERM", "screen-256color")
.env_remove("TMUX");
#[cfg(unix)]
{
use std::os::unix::process::CommandExt;
let error = client.exec();
bail!("could not attach to {pane}: {error}")
}
#[cfg(not(unix))]
{
let status = client.status().context("run tmux")?;
if !status.success() {
bail!("tmux could not attach to {pane}");
}
Ok(())
}
}
fn fleet_json(args: &[String]) -> Result<Value> {
let entry = crate::fleet_entry()?;
let output = std::process::Command::new(crate::teams_node_bin())
.arg(&entry)
.args(args)
.stdin(std::process::Stdio::null())
.output()
.context("run the teams CLI")?;
if !output.status.success() {
bail!(
"the machine daemon refused: {}",
String::from_utf8_lossy(&output.stderr).trim()
);
}
serde_json::from_slice(&output.stdout).context("read the machine daemon's answer")
}
fn short(id: &str) -> String {
id.chars().take(8).collect()
}