use serde::Deserialize;
use super::DaemonClient;
impl DaemonClient {
pub async fn launch_session(&self, workdir: &str) -> anyhow::Result<String> {
let config_root = crate::core::alt_screen::operator_config_root();
let fw = crate::core::paths::FrameworkPaths::default();
match crate::core::session_launch::prepare_session(&fw, std::path::Path::new(workdir)) {
Ok(report) => {
crate::core::session_launch::log_prep_findings(
&report.roster_errors,
&report.asset_notices,
crate::core::session_launch::PrepScope {
kind: "connect",
session: None,
dir: std::path::Path::new(workdir),
},
);
}
Err(err) if err.is_fatal() => {
anyhow::bail!("{err}");
}
Err(err) => {
tracing::warn!(%err, "session pre-launch preparation failed");
}
}
#[derive(Deserialize)]
struct Body {
#[serde(default)]
name: String,
}
let url = format!("{}/sessions", self.base);
let body: Body = self
.http
.post(&url)
.json(&serde_json::json!({
"project": workdir,
"project_path": workdir,
}))
.send()
.await?
.error_for_status()?
.json()
.await?;
let prompt =
crate::core::session_launch::build_system_prompt_for(std::path::Path::new(workdir));
let claude_cmd = {
let path = std::env::temp_dir().join(format!(
"trusty-mpm-system-prompt-{}.txt",
uuid::Uuid::new_v4()
));
match std::fs::write(&path, &prompt) {
Ok(()) => client_claude_cmd(config_root.as_deref(), Some(&path)),
Err(err) => {
tracing::warn!(%err, "failed to write system prompt file; launching bare claude");
client_claude_cmd(config_root.as_deref(), None)
}
}
};
let new_session =
crate::core::tmux::create_managed_session(None, &body.name, Some(workdir));
match new_session {
Ok(outcome) if outcome.output.status.success() => {
crate::core::tmux::warn_if_options_unverified(&outcome, &body.name);
let send = crate::core::tmux::send_line(
None,
&crate::core::tmux::TmuxTarget::session(&body.name),
&claude_cmd,
)
.map(|output| output.status);
if !matches!(send, Ok(s) if s.success()) {
return Err(anyhow::anyhow!(
"tmux session {} created but failed to start claude",
body.name
));
}
}
Ok(_) | Err(_) => {
return Err(anyhow::anyhow!(
"failed to create tmux session {} in {}",
body.name,
workdir
));
}
}
Ok(body.name)
}
pub async fn connect_session(&self, workdir: &str) -> anyhow::Result<String> {
let config_root = crate::core::alt_screen::operator_config_root();
#[derive(Deserialize)]
struct Body {
#[serde(default)]
name: String,
}
let url = format!("{}/api/v1/sessions/connect", self.base);
let body: Body = self
.http
.post(&url)
.json(&serde_json::json!({
"project": workdir,
"project_path": workdir,
}))
.send()
.await?
.error_for_status()?
.json()
.await?;
let prompt =
crate::core::session_launch::build_system_prompt_for(std::path::Path::new(workdir));
let claude_cmd = {
let path = std::env::temp_dir().join(format!(
"trusty-mpm-system-prompt-{}.txt",
uuid::Uuid::new_v4()
));
match std::fs::write(&path, &prompt) {
Ok(()) => client_claude_cmd(config_root.as_deref(), Some(&path)),
Err(err) => {
tracing::warn!(%err, "failed to write system prompt file; launching bare claude");
client_claude_cmd(config_root.as_deref(), None)
}
}
};
let already_running =
crate::core::tmux::run_tmux(&crate::core::tmux::TmuxCommand::HasSession {
name: body.name.clone(),
})
.map(|output| output.status.success())
.unwrap_or(false);
let new_session =
crate::core::tmux::create_managed_session(None, &body.name, Some(workdir));
match new_session {
Ok(outcome) if outcome.output.status.success() => {
crate::core::tmux::warn_if_options_unverified(&outcome, &body.name);
if !already_running {
let send = crate::core::tmux::send_line(
None,
&crate::core::tmux::TmuxTarget::session(&body.name),
&claude_cmd,
)
.map(|output| output.status);
if !matches!(send, Ok(s) if s.success()) {
return Err(anyhow::anyhow!(
"tmux session {} created but failed to start claude",
body.name
));
}
}
}
Ok(_) | Err(_) => {
return Err(anyhow::anyhow!(
"failed to create tmux session {} in {}",
body.name,
workdir
));
}
}
Ok(body.name)
}
}
fn client_claude_cmd(
config_root: Option<&std::path::Path>,
prompt_file: Option<&std::path::Path>,
) -> String {
crate::core::model_inject::build_client_session_command_configured(
prompt_file,
crate::core::alt_screen::configured_alternate_screen_in(config_root),
)
}
#[cfg(test)]
mod renderer_tests {
#[test]
fn client_claude_cmd_follows_the_configured_renderer() {
for (alternate_screen, want) in [
(true, "CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=0 "),
(false, "CLAUDE_CODE_DISABLE_ALTERNATE_SCREEN=1 "),
] {
let root = tempfile::tempdir().expect("tempdir");
std::fs::write(
root.path().join("config.yaml"),
format!("tmux:\n alternate_screen: {alternate_screen}\n"),
)
.expect("write config");
for prompt in [None, Some(std::path::Path::new("/tmp/p.txt"))] {
let line = super::client_claude_cmd(Some(root.path()), prompt);
assert!(line.contains(want), "want {want:?} in: {line}");
}
}
}
}