use crate::cli::ServeArgs;
use crate::config::resolve_config;
use crate::proxy::{ProxyServer, ResumeContext};
use std::path::PathBuf;
pub async fn run(config_path: &Option<PathBuf>, args: ServeArgs) -> anyhow::Result<()> {
let resolved = resolve_config(config_path.as_deref())?;
let mut config = resolved.agent_mcp;
if let Some(ref identity) = args.identity {
config.identity = Some(identity.clone());
}
if let Some(ref model) = args.model {
config.model = Some(model.clone());
}
if args.fast {
if let Some(ref fast_model) = config.fast_model.clone() {
config.model = Some(fast_model.clone());
}
}
if let Some(ref sandbox) = args.sandbox {
config.sandbox = sandbox.clone();
}
if let Some(ref policy) = args.approval_policy {
config.approval_policy = policy.clone();
}
if let Some(timeout_secs) = args.timeout {
config.request_timeout_secs = timeout_secs;
}
let upstream_in = tokio::io::stdin();
let upstream_out = tokio::io::stdout();
let team = resolved.core.default_team.clone();
let resume_context = if let Some(ref resume_arg) = args.resume {
let registry_path = crate::lock::sessions_dir()
.join(&team)
.join("registry.json");
load_resume_context(®istry_path, resume_arg.clone(), &team).await?
} else {
None
};
let mut proxy = ProxyServer::new_with_resume(config, team, resume_context);
proxy.run(upstream_in, upstream_out).await
}
async fn load_resume_context(
registry_path: &std::path::Path,
resume_arg: Option<String>,
team: &str,
) -> anyhow::Result<Option<ResumeContext>> {
let contents = match tokio::fs::read_to_string(registry_path).await {
Ok(c) => c,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
tracing::info!("no persisted registry found; cannot resume");
return Ok(None);
}
Err(e) => {
tracing::warn!("failed to read registry for resume: {e}");
return Ok(None);
}
};
let root: serde_json::Value = match serde_json::from_str(&contents) {
Ok(v) => v,
Err(e) => {
tracing::warn!("failed to parse registry for resume: {e}");
return Ok(None);
}
};
let sessions = match root.get("sessions").and_then(|v| v.as_array()) {
Some(s) => s,
None => {
tracing::info!("registry has no sessions array; cannot resume");
return Ok(None);
}
};
let entry = if let Some(ref target_id) = resume_arg {
sessions
.iter()
.find(|s| s.get("agent_id").and_then(|v| v.as_str()) == Some(target_id.as_str()))
} else {
sessions.iter().max_by(|a, b| {
let a_ts = a
.get("last_active")
.and_then(|v| v.as_str())
.unwrap_or("");
let b_ts = b
.get("last_active")
.and_then(|v| v.as_str())
.unwrap_or("");
a_ts.cmp(b_ts)
})
};
let Some(entry) = entry else {
tracing::info!("no matching session found for resume");
return Ok(None);
};
let agent_id = entry
.get("agent_id")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_string();
let identity = entry
.get("identity")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_string();
let backend_id = match entry.get("thread_id").and_then(|v| v.as_str()) {
Some(tid) => tid.to_string(),
None => {
tracing::info!(
agent_id = %agent_id,
"session has no thread_id; cannot resume with summary"
);
return Ok(Some(ResumeContext {
agent_id,
identity,
backend_id: String::new(),
summary: None,
}));
}
};
let summary = crate::summary::read_summary(team, &identity, &backend_id).await;
if summary.is_none() {
tracing::warn!(
agent_id = %agent_id,
identity = %identity,
"no summary file found for resume; continuing without context (FR-6.3)"
);
}
Ok(Some(ResumeContext {
agent_id,
identity,
backend_id,
summary,
}))
}