use std::sync::Arc;
use tracing::{info, warn};
use crate::daemon::state::DaemonState;
use crate::provisioner::WorkspaceProvisioner;
use crate::runtime::{RuntimeKind, build_adapter};
use crate::session_manager::{ManagedError, ManagedSessionId, ManagedSessionState, SessionRecord};
#[derive(Debug, Clone)]
pub struct SpawnParams {
pub repo_url: String,
pub git_ref: String,
pub task: String,
pub name_hint: Option<String>,
pub runtime: Option<String>,
}
pub async fn spawn_managed(
state: &Arc<DaemonState>,
params: SpawnParams,
) -> Result<SessionRecord, String> {
let runtime = match params.runtime.as_deref() {
None => RuntimeKind::default(),
Some(raw) => raw.parse::<RuntimeKind>().map_err(|e| e.to_string())?,
};
let config = crate::core::trusty_tools_config::TrustyToolsConfig::load();
let session_id = ManagedSessionId::new();
let prepared = match trusty_common::github_path::parse_github_path(¶ms.repo_url) {
Some(gh) => {
let project_dir = crate::core::trusty_tools_config::workspace_subpath(&config, &gh);
let provisioner = WorkspaceProvisioner::new(
crate::provisioner::RealGitBackend,
std::path::PathBuf::new(),
);
provisioner.provision_in(
&project_dir,
&session_id,
¶ms.repo_url,
¶ms.git_ref,
¶ms.task,
)
}
None => {
let workspace_root = crate::core::trusty_tools_config::workspace_root(&config);
let provisioner =
WorkspaceProvisioner::new(crate::provisioner::RealGitBackend, workspace_root);
provisioner.provision(&session_id, ¶ms.repo_url, ¶ms.git_ref, ¶ms.task)
}
}
.map_err(|e| {
warn!(id = %session_id, "spawn_managed: provision failed: {e}");
format!("workspace provisioning failed: {e}")
})?;
let mgr = state.session_manager().await;
let record = mgr
.create_with_id(
session_id,
params.task.clone(),
Some(prepared.path.clone()),
params.name_hint,
Some(prepared.path.clone()),
Some(params.repo_url.clone()),
Some(params.git_ref.clone()),
runtime,
)
.await
.map_err(|e| {
warn!(id = %session_id, "spawn_managed: session create failed: {e}");
e.to_string()
})?;
if let Err(e) = mgr
.set_workspace(
&record.id,
prepared.path.clone(),
ManagedSessionState::Active,
)
.await
{
warn!(id = %record.id, "spawn_managed: set_workspace failed: {e}");
}
let tmux_arc = mgr.tmux_driver();
let adapter = build_adapter(record.runtime, tmux_arc);
if let Err(e) = adapter.spawn(&record.tmux_name, &prepared.path, ¶ms.task) {
warn!(
id = %record.id,
name = %record.tmux_name,
runtime = %record.runtime.as_str(),
"spawn_managed: runtime adapter spawn failed: {e}"
);
let _ = mgr
.mark_errored(&record.id, &format!("spawn failed: {e}"))
.await;
} else {
info!(
id = %record.id,
name = %record.tmux_name,
path = %prepared.path.display(),
"managed session spawned successfully"
);
}
Ok(mgr.get(&record.id).await.unwrap_or(record))
}
#[derive(Debug, thiserror::Error)]
pub enum ResumeManagedError {
#[error("session not found: {0}")]
NotFound(String),
#[error("invalid state transition: {0}")]
InvalidState(String),
#[error("{0}")]
Other(String),
}
impl From<ManagedError> for ResumeManagedError {
fn from(e: ManagedError) -> Self {
match e {
ManagedError::SessionNotFound(id) => ResumeManagedError::NotFound(id),
ManagedError::InvalidState(_, reason) => ResumeManagedError::InvalidState(reason),
other => ResumeManagedError::Other(other.to_string()),
}
}
}
pub async fn resume_managed(
state: &Arc<DaemonState>,
id: &ManagedSessionId,
) -> Result<SessionRecord, ResumeManagedError> {
let mgr = state.session_manager().await;
let record = mgr.resume(id).await.map_err(ResumeManagedError::from)?;
let workspace = record
.workspace_path
.clone()
.unwrap_or_else(|| record.cwd.clone());
let tmux_arc = mgr.tmux_driver();
let adapter = build_adapter(record.runtime, tmux_arc);
if let Err(e) = adapter.spawn(&record.tmux_name, &workspace, &record.task) {
warn!(
id = %record.id,
name = %record.tmux_name,
runtime = %record.runtime.as_str(),
"resume_managed: runtime adapter spawn failed: {e}"
);
let _ = mgr
.mark_errored(&record.id, &format!("resume spawn failed: {e}"))
.await;
} else {
info!(
id = %record.id,
name = %record.tmux_name,
workspace = %workspace.display(),
"managed session resumed and runtime respawned"
);
}
Ok(mgr.get(id).await.unwrap_or(record))
}