use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::{Duration, Instant};
use anyhow::{Context as _, Result};
use tracing::{info, warn};
use trusty_mpm::core::session_launch::prepare_isolated_session;
use trusty_mpm::runtime::{RuntimeKind, build_adapter};
use trusty_mpm::session_manager::real_tmux::RealTmuxDriver;
use trusty_mpm::session_manager::{ManagedSessionId, ManagedTmuxDriver, SessionManager};
pub(crate) const POLL_INTERVAL: Duration = Duration::from_secs(2);
pub(crate) const TRANSCRIPT_LINES: usize = 400;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum PollDecision {
Continue,
TimedOut,
}
pub(crate) fn poll_decision(elapsed: Duration, timeout: Duration) -> PollDecision {
if elapsed >= timeout {
PollDecision::TimedOut
} else {
PollDecision::Continue
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum LaunchOutcome {
Exited,
TimedOut,
}
impl LaunchOutcome {
pub(crate) fn status(&self) -> &'static str {
match self {
LaunchOutcome::Exited => "exited",
LaunchOutcome::TimedOut => "timed-out",
}
}
}
#[derive(Debug)]
pub(crate) struct LaunchReport {
pub(crate) outcome: LaunchOutcome,
pub(crate) tmux_name: String,
pub(crate) transcript: String,
}
pub(crate) async fn new_session_manager(state_dir: &Path) -> Result<SessionManager> {
let tmux = RealTmuxDriver::discover()
.context("tmux not found — the metaharness POC requires tmux to host the claude session")?;
let tmux: Arc<dyn ManagedTmuxDriver> = Arc::new(tmux);
SessionManager::new(state_dir, tmux)
.await
.context("failed to initialise the standalone session manager")
}
pub(crate) async fn launch_and_wait(
mgr: &SessionManager,
project_dir: &Path,
framework_root: &Path,
task: Option<&str>,
timeout: Duration,
) -> Result<LaunchReport> {
let memory_reachable = match prepare_isolated_session(project_dir, None) {
Ok(report) => {
info!(
agents = report.deploy.deployed.len(),
project = %project_dir.display(),
"meta run: prepared session (agents/skills/CLAUDE.md/MCP deployed)"
);
Some(report.memory_reachable)
}
Err(e) if e.is_fatal() => anyhow::bail!("{e}"),
Err(e) => {
warn!(
project = %project_dir.display(),
"meta run: session preparation failed (continuing): {e}"
);
None
}
};
let id = ManagedSessionId::new();
let task_label = task.unwrap_or("metaharness session").to_string();
let record = mgr
.create_with_id(
id,
task_label,
Some(project_dir.to_path_buf()),
None,
None,
None,
None,
RuntimeKind::ClaudeCode,
false,
false, )
.await
.context("failed to create the tmux session for the metaharness")?;
info!(
id = %record.id,
tmux = %record.tmux_name,
"meta run: tmux session created (no provision — local dir used in place)"
);
let adapter = build_adapter(
RuntimeKind::ClaudeCode,
mgr.tmux_driver(),
memory_reachable,
framework_root,
);
adapter
.spawn(
&record.tmux_name,
project_dir,
&record.task,
&record.id.to_string(),
&[],
)
.context("failed to spawn the claude runtime in the tmux pane")?;
info!(tmux = %record.tmux_name, "meta run: claude runtime spawned");
if let Some(task) = task {
match mgr.inject_task_when_ready(&record.id, task).await {
Ok(true) => info!(tmux = %record.tmux_name, "meta run: demo task delivered to session"),
Ok(false) => {
warn!(tmux = %record.tmux_name, "meta run: runtime never became ready; demo task not delivered")
}
Err(e) => warn!(tmux = %record.tmux_name, "meta run: failed to inject demo task: {e}"),
}
}
let start = Instant::now();
let outcome = loop {
let alive = mgr.observe(&record.id, 1).await.map(|o| o.runtime_active);
match alive {
Ok(false) => break LaunchOutcome::Exited,
Ok(true) => {}
Err(e) => warn!(tmux = %record.tmux_name, "meta run: liveness probe failed: {e}"),
}
if poll_decision(start.elapsed(), timeout) == PollDecision::TimedOut {
break LaunchOutcome::TimedOut;
}
tokio::time::sleep(POLL_INTERVAL).await;
};
info!(
tmux = %record.tmux_name,
outcome = outcome.status(),
elapsed_secs = start.elapsed().as_secs(),
"meta run: poll loop finished"
);
let transcript = mgr
.capture_pane(&record.id, TRANSCRIPT_LINES)
.await
.unwrap_or_default();
let policy = trusty_mpm::session_manager::decommission_force::ProvisioningDirt::Refuse;
match mgr.decommission_reporting(&record.id, None, policy).await {
Ok(report) => {
if let Some(reason) = report.workspace_kept_reason {
warn!(tmux = %record.tmux_name, "meta run: workspace kept: {reason}");
}
}
Err(e) => {
warn!(tmux = %record.tmux_name, "meta run: decommission failed (best-effort): {e}");
}
}
Ok(LaunchReport {
outcome,
tmux_name: record.tmux_name,
transcript,
})
}
pub(crate) fn state_dir(project_dir: &Path) -> PathBuf {
project_dir.join(".trusty-mpm").join("meta-sessions")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn poll_decision_continues_before_deadline() {
assert_eq!(
poll_decision(Duration::from_secs(5), Duration::from_secs(10)),
PollDecision::Continue
);
}
#[test]
fn poll_decision_times_out_at_deadline() {
assert_eq!(
poll_decision(Duration::from_secs(10), Duration::from_secs(10)),
PollDecision::TimedOut
);
}
#[test]
fn poll_decision_times_out_after_deadline() {
assert_eq!(
poll_decision(Duration::from_secs(11), Duration::from_secs(10)),
PollDecision::TimedOut
);
}
#[test]
fn poll_decision_zero_timeout_times_out_immediately() {
assert_eq!(
poll_decision(Duration::from_secs(0), Duration::from_secs(0)),
PollDecision::TimedOut
);
}
#[test]
fn launch_outcome_status_tokens() {
assert_eq!(LaunchOutcome::Exited.status(), "exited");
assert_eq!(LaunchOutcome::TimedOut.status(), "timed-out");
}
#[test]
fn meta_run_hands_the_adapter_the_prepared_reachability() {
const SRC: &str = include_str!("launch.rs");
let production = SRC.split("#[cfg(test)]").next().unwrap_or(SRC);
assert!(
production.contains("Some(report.memory_reachable)"),
"the prepared reachability must be kept"
);
let args = production
.split_once("build_adapter(")
.and_then(|(_, rest)| rest.split_once(");"))
.map(|(args, _)| args)
.expect("the production half must call build_adapter");
assert!(
args.contains("memory_reachable"),
"build_adapter must receive the prepared reachability, got: {args}"
);
assert!(
!args.contains("None"),
"a `None` here makes the adapter probe trusty-memory again, got: {args}"
);
}
#[test]
fn state_dir_is_project_scoped() {
let dir = state_dir(Path::new("/work/proj"));
assert_eq!(
dir,
Path::new("/work/proj/.trusty-mpm/meta-sessions"),
"state dir must be project-scoped"
);
}
}