use serde_json::{json, Value};
use crate::agent::Agent;
pub(crate) async fn try_load_session(agent: &Agent, sid: &str, cwd: &str) -> Result<Value, String> {
const ATTEMPTS: u32 = 6;
const BACKOFF: std::time::Duration = std::time::Duration::from_millis(250);
let mut last_err = String::new();
for attempt in 0..ATTEMPTS {
let res = agent
.request(
"session/load",
json!({
"sessionId": sid,
"cwd": cwd,
"mcpServers": []
})
)
.await;
match res {
Ok(value) => return Ok(value),
Err(e) => {
last_err = format!("{e}");
if !is_stale_lock_error(&last_err) {
break;
}
let stole = steal_stale_session_lock(sid);
if stole {
eprintln!("Session {sid}: stale lock stolen on attempt {}.", attempt + 1);
continue;
}
if attempt + 1 < ATTEMPTS {
tokio::time::sleep(BACKOFF).await;
}
}
}
}
Err(last_err)
}
fn is_stale_lock_error(msg: &str) -> bool {
msg.contains("Session is active in another process")
}
fn steal_stale_session_lock(session_id: &str) -> bool {
let Ok(home) = std::env::var("HOME") else {
return false;
};
let path = std::path::PathBuf::from(home).join(format!(".kiro/sessions/cli/{session_id}.lock"));
let Ok(raw) = std::fs::read_to_string(&path) else {
return false;
};
let Ok(parsed) = serde_json::from_str::<Value>(&raw) else {
return false;
};
let Some(pid) = parsed.get("pid").and_then(Value::as_i64) else {
return false;
};
if pid_is_alive(pid as i32) {
return false;
}
match std::fs::remove_file(&path) {
Ok(()) => {
eprintln!("Stole stale Kiro session lock (pid {pid}): {}", path.display());
true
}
Err(_) => false
}
}
#[cfg(unix)]
fn pid_is_alive(pid: i32) -> bool {
unsafe { libc_kill(pid, 0) == 0 }
}
#[cfg(not(unix))]
fn pid_is_alive(_pid: i32) -> bool {
true
}
#[cfg(unix)]
extern "C" {
#[link_name = "kill"]
fn libc_kill(pid: i32, sig: i32) -> i32;
}
pub(crate) fn extract_session_info(result: &Value) -> Option<Value> {
let modes = result.get("modes").cloned();
let models = result.get("models").cloned();
if modes.is_none() && models.is_none() {
return None;
}
Some(json!({
"modes": modes,
"models": models
}))
}
pub(crate) fn short_reason(msg: &str) -> String {
if let Some(start) = msg.find("\"data\":\"") {
let rest = &msg[start + 8..];
if let Some(end) = rest.find('"') {
return rest[..end].to_string();
}
}
msg.trim_start_matches("agent error: ").trim().to_string()
}