use serde::Deserialize;
use crate::cli::SessionAction;
use crate::commands::project::resolve_dir;
use crate::formatters::session::deploy_summary_line;
pub(crate) async fn start_session(
client: &reqwest::Client,
url: &str,
dir: Option<String>,
) -> anyhow::Result<()> {
let path = resolve_dir(dir)?;
let path = path.canonicalize().unwrap_or(path);
if let Some((_source_id, _workspace, git_root)) = crate::commands::guided::derive_project(&path)
{
let new_action = SessionAction::New {
repo: git_root.to_string_lossy().to_string(),
git_ref: "HEAD".to_string(),
task: String::new(),
name_hint: None,
runtime: trusty_mpm::runtime::RuntimeKind::default(),
no_inject: false,
deliverable: None,
};
crate::commands::managed_route::run(client, url, &new_action).await?;
return Ok(());
}
refuse_outside_a_git_project(&path)?;
let fw = trusty_mpm::core::paths::FrameworkPaths::default();
start_session_in_place(client, url, &path, &fw).await
}
pub(crate) fn refuse_outside_a_git_project(path: &std::path::Path) -> anyhow::Result<()> {
if trusty_mpm::core::harness_root::harness_root_for(path).is_some() {
return Ok(());
}
anyhow::bail!(
"{} is not inside a git repository, so there is no project to hold this \
session's state.\n\
`tm` writes a session's agents, instructions and compiled prompt under the \
project's `.trusty-mpm/`; starting here would scatter them into this \
directory instead. Run `git init` here, or `cd` into the repository you \
meant to work in, and retry.",
path.display()
)
}
async fn start_session_in_place(
client: &reqwest::Client,
url: &str,
path: &std::path::Path,
fw: &trusty_mpm::core::paths::FrameworkPaths,
) -> anyhow::Result<()> {
match trusty_mpm::core::session_launch::prepare_session(fw, path) {
Ok(report) => {
println!(
"{}",
deploy_summary_line(
"Agents",
report.deploy.deployed.len(),
report.deploy.skipped.len(),
report.deploy.unchanged.len(),
)
);
println!(
"{}",
deploy_summary_line(
"Skills",
report.skill_deploy.deployed.len(),
report.skill_deploy.skipped.len(),
report.skill_deploy.unchanged.len(),
)
);
if report.instructions.claude_md_created {
println!(" Created CLAUDE.md stub in {}", path.display());
}
println!(
"Instructions: {} agents in delegation authority",
report.instructions.agent_count
);
println!(
" Merged instructions written to {}",
report.stash.display()
);
if let Some(ctx) = report.catchup_context {
println!("\n---\n\n## Recent Activity (catch-up)\n\n{ctx}");
}
for err in &report.roster_errors {
eprintln!("error: roster provisioning gap: {err}");
}
}
Err(err) if err.is_fatal() => anyhow::bail!("{err}"),
Err(err) => eprintln!("warning: session preparation failed: {err}"),
}
#[derive(Deserialize)]
struct Body {
#[serde(default)]
name: String,
}
let body: Body = client
.post(format!("{url}/sessions"))
.json(&serde_json::json!({
"project": path,
"project_path": path,
}))
.send()
.await?
.error_for_status()?
.json()
.await?;
let workdir = path.to_string_lossy().to_string();
let new_session =
trusty_mpm::core::tmux::create_managed_session(None, &body.name, Some(&workdir));
match new_session {
Ok(outcome) if outcome.output.status.success() => {
if !outcome.options_verified {
eprintln!(
"warning: tmux scrollback limit could not be verified for session {} — the \
pane may be capped at tmux's factory 2000-line history-limit instead of \
the configured value; see issue #3386",
body.name
);
}
let claude_cmd = trusty_mpm::core::spawn_disclaim::disclaim_pane_command(
&trusty_mpm::core::model_inject::build_inplace_session_command(),
);
let send = trusty_mpm::core::tmux::send_line(
None,
&trusty_mpm::core::tmux::TmuxTarget::session(&body.name),
&claude_cmd,
)
.map(|output| output.status);
match send {
Ok(s) if s.success() => {
println!("started session {} (tmux + claude)", body.name);
}
Ok(_) | Err(_) => {
eprintln!(
"warning: tmux session {} created but failed to start claude",
body.name
);
println!("started session {}", body.name);
}
}
}
Ok(_) | Err(_) => {
eprintln!(
"warning: failed to create tmux session {}; run `claude` manually in {}",
body.name, workdir
);
println!("started session {}", body.name);
}
}
Ok(())
}
#[cfg(test)]
#[path = "start_tests.rs"]
mod tests;