use super::*;
use onlyne_config::{Placement, validate_drive_placement};
pub fn process_env() -> BTreeMap<String, String> {
std::env::vars().collect()
}
fn env_nonempty(env: &BTreeMap<String, String>, key: &str) -> bool {
env.get(key).is_some_and(|value| !value.is_empty())
}
fn orca_host_present(env: &BTreeMap<String, String>) -> bool {
env_nonempty(env, "ORCA_PANE_KEY")
|| env_nonempty(env, "ORCA_TERMINAL_HANDLE")
|| env_nonempty(env, "ORCA_WORKTREE_ID")
}
fn zellij_host_present(env: &BTreeMap<String, String>) -> bool {
env.contains_key("ZELLIJ")
}
fn probed_placement(env: &BTreeMap<String, String>) -> Option<Placement> {
if orca_host_present(env) {
Some(Placement::Orca)
} else if zellij_host_present(env) {
Some(Placement::Zellij)
} else {
None
}
}
pub fn detect_placement(
env: &BTreeMap<String, String>,
declared: Option<SessionPlacement>,
) -> Result<PlacementDetection> {
let explicit = env
.get("ONLYNE_BACKEND")
.map(|value| value.trim().to_string())
.filter(|value| !value.is_empty() && !value.eq_ignore_ascii_case("auto"));
if let Some(name) = &explicit {
let Some(placement) = SessionPlacement::parse(name) else {
return Err(UnknownPlacement(name.clone()).into());
};
return Ok(PlacementDetection {
placement,
source: SelectionSource::Explicit,
explicit: Some(name.clone()),
});
}
if let Some(placement) = declared {
return Ok(PlacementDetection {
placement,
source: SelectionSource::Declared,
explicit: None,
});
}
let probed = probed_placement(env);
Ok(PlacementDetection {
placement: SessionPlacement::Named(probed.unwrap_or(Placement::Headless)),
source: if probed.is_some() {
SelectionSource::Probe
} else {
SelectionSource::Fallback
},
explicit: None,
})
}
pub fn doctor_report(env: &BTreeMap<String, String>) -> Value {
let detected = detect_placement(env, None);
let found = detected.as_ref().ok();
let placement = found.map(|detected| detected.placement.as_str());
let selection = found.map(|detected| match detected.source {
SelectionSource::Explicit => "explicit",
SelectionSource::Declared => "declared",
SelectionSource::Probe => "probe",
SelectionSource::Fallback => "fallback",
});
let binary = match placement {
Some("orca") => Some(
env.get("ORCA_CLI_COMMAND")
.filter(|value| !value.is_empty())
.cloned()
.unwrap_or_else(|| "orca".into()),
),
Some("zellij") => Some(
env.get("ZELLIJ_COMMAND")
.filter(|value| !value.is_empty())
.cloned()
.unwrap_or_else(|| "zellij".into()),
),
_ => None,
};
let mut report = serde_json::json!({
"placement": placement,
"placement_selection": selection,
"binary": binary,
"explicit": found.and_then(|detected| detected.explicit.clone()),
});
if let Err(error) = &detected {
report["refusal"] = Value::String(error.to_string());
}
report
}
pub fn backend_for(
drive: onlyne_config::Drive,
placement: SessionPlacement,
runner: Arc<dyn Runner>,
policy: WorktreePolicy,
acp: &AcpOptions,
) -> Result<Box<dyn SessionBackend>> {
if let Some(named) = placement.named() {
validate_drive_placement(drive, named).map_err(|message| anyhow::anyhow!("{message}"))?;
}
Ok(match placement {
SessionPlacement::Fake => Box::new(fake::FakeBackend::new()),
SessionPlacement::Named(Placement::Orca) => {
Box::new(orca::OrcaBackend::with_policy(runner, policy))
}
SessionPlacement::Named(Placement::Zellij) => Box::new(zellij::ZellijBackend::new(runner)),
SessionPlacement::Named(Placement::Headless) => match drive {
onlyne_config::Drive::Acp => Box::new(acp::AcpBackend::new(acp.clone())),
onlyne_config::Drive::Plugin | onlyne_config::Drive::Exec => {
Box::new(exec::ExecBackend::new())
}
},
SessionPlacement::Named(Placement::External) => match drive {
onlyne_config::Drive::Plugin => Box::new(external::ExternalBackend::new()),
onlyne_config::Drive::Acp | onlyne_config::Drive::Exec => {
Box::new(exec::ExecBackend::new())
}
},
})
}