use std::path::Path;
use std::time::{Duration, Instant};
use tracing::warn;
use crate::core::pr_cleanup::{
ClaimEnder, CleanupRegistry, RealGh, RealGit, RealLanding, auth_backoff, sweep,
};
use crate::session_manager::SessionManager;
use crate::session_manager::worktree_safety::inspect_dirt;
pub struct SessionClaims<'a> {
mgr: &'a SessionManager,
}
impl<'a> SessionClaims<'a> {
pub fn new(mgr: &'a SessionManager) -> Self {
Self { mgr }
}
}
#[async_trait::async_trait]
impl ClaimEnder for SessionClaims<'_> {
async fn claims_on(&self, path: &Path) -> anyhow::Result<Vec<String>> {
Ok(self
.mgr
.list()
.await
.into_iter()
.filter(|r| r.workspace_path.as_deref() == Some(path))
.map(|r| r.id.to_string())
.collect())
}
async fn end_claim(&self, id: &str) -> anyhow::Result<()> {
let parsed = crate::session_manager::ManagedSessionId(
id.parse::<uuid::Uuid>()
.map_err(|e| anyhow::anyhow!("`{id}` is not a managed session id: {e}"))?,
);
self.mgr
.decommission_record_only(&parsed)
.await
.map_err(|e| anyhow::anyhow!("cannot tombstone session {id}: {e}"))?;
Ok(())
}
}
pub fn due(interval: Option<Duration>, last: Option<Instant>, now: Instant) -> bool {
let Some(interval) = interval else {
return false;
};
match last {
None => true,
Some(prev) => now.duration_since(prev) >= interval,
}
}
pub async fn run_sweep(mgr: &SessionManager) -> usize {
let (env, unset) = match crate::core::gh_identity::resolve_gh_env(None) {
Ok(e) => (e.vars().to_vec(), e.unset_vars().to_vec()),
Err(e) => {
warn!(
"pr cleanup sweep: no GitHub identity overlay resolved ({e}); using ambient auth"
);
(Vec::new(), Vec::new())
}
};
let gh = RealGh::new(env, unset);
let claims = SessionClaims::new(mgr);
sweep::run_sweep(
&gh,
&RealGit,
&claims,
&RealLanding,
&inspect_dirt,
&CleanupRegistry::production(),
auth_backoff::shared(),
)
.await
}