use crate::core::session::SessionId;
use crate::core::twin_arming::{ProcessFacts, process_facts};
use crate::core::twin_identity::ClaudeProcess;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ClaudeLiveness {
Alive,
Gone(String),
Unknown(String),
}
impl ClaudeLiveness {
pub(crate) fn proves_gone(&self, session: SessionId) -> bool {
match self {
Self::Alive => false,
Self::Gone(why) => {
tracing::info!(session = ?session, "a settled session's claude is gone: {why} (#9010)");
true
}
Self::Unknown(why) => {
tracing::warn!(session = ?session, "kept a settled session, its claude's liveness is unknown: {why} (#9010)");
false
}
}
}
}
pub(crate) fn claude_liveness(claude: ClaudeProcess) -> ClaudeLiveness {
let exists = |pid| {
crate::session_manager::worktree_registry::pid_liveness(pid)
.ok_or_else(|| format!("kill(0) could not tell whether pid {pid} runs"))
};
claude_liveness_with(
claude,
exists,
process_facts,
crate::core::twin_arming::is_claude,
)
}
pub(crate) fn claude_liveness_with(
claude: ClaudeProcess,
exists: impl Fn(u32) -> Result<bool, String>,
facts: impl Fn(u32) -> Result<ProcessFacts, String>,
is_claude: impl Fn(u32) -> Result<bool, String>,
) -> ClaudeLiveness {
let pid = claude.pid;
let started = match exists(pid) {
Err(e) => return ClaudeLiveness::Unknown(e),
Ok(false) => return ClaudeLiveness::Gone(format!("no process holds pid {pid}")),
Ok(true) => match facts(pid) {
Ok(f) if f.start_time == claude.start_time => return ClaudeLiveness::Alive,
Ok(f) => f.start_time,
Err(e) => return ClaudeLiveness::Unknown(format!("pid {pid} could not be read: {e}")),
},
};
let shifted = format!(
"pid {pid} names a process started at {started}, not {}",
claude.start_time
);
match is_claude(pid) {
Ok(false) => ClaudeLiveness::Gone(format!("{shifted}, and it is not a claude")),
Ok(true) => ClaudeLiveness::Unknown(format!("{shifted}, but it is a claude")),
Err(e) => ClaudeLiveness::Unknown(format!("{shifted}, and it could not be named: {e}")),
}
}