use std::sync::Arc;
use serde_json::Value;
use crate::daemon::managed_routes::proxy::local_proxy;
use crate::daemon::managed_routes::{
ProxyFocusResponse, ProxyMessageResponse, ProxySummaryResponse, ProxyUnfocusResponse,
};
use crate::daemon::state::DaemonState;
fn to_result<T: serde::Serialize>(resp: T) -> Result<Value, String> {
serde_json::to_value(resp).map_err(|e| format!("failed to serialise proxy response: {e}"))
}
pub async fn session_proxy_focus(
state: &Arc<DaemonState>,
conversation_key: &str,
session_id: &str,
) -> Result<Value, String> {
let proxy = local_proxy(state);
let outcome = proxy.focus(conversation_key, session_id).await;
to_result(ProxyFocusResponse::from(outcome))
}
pub async fn session_proxy_unfocus(
state: &Arc<DaemonState>,
conversation_key: &str,
) -> Result<Value, String> {
let proxy = local_proxy(state);
let cleared = proxy.unfocus(conversation_key).map(Into::into);
to_result(ProxyUnfocusResponse { cleared })
}
pub async fn session_proxy_message(
state: &Arc<DaemonState>,
conversation_key: &str,
text: &str,
) -> Result<Value, String> {
let proxy = local_proxy(state);
let outcome = proxy.inject(conversation_key, text).await;
to_result(ProxyMessageResponse::from(outcome))
}
pub async fn session_proxy_summary(
state: &Arc<DaemonState>,
conversation_key: &str,
) -> Result<Value, String> {
let proxy = local_proxy(state);
let outcome = proxy.summarize(conversation_key).await;
to_result(ProxySummaryResponse::from(outcome))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::RuntimeKind;
use crate::session_manager::record::{ManagedSessionId, ManagedSessionState};
async fn seeded_state() -> (Arc<DaemonState>, String) {
let root = tempfile::tempdir().unwrap().keep();
let state = Arc::new(DaemonState::with_root_isolated_managed(root).await);
let mgr = state.session_manager().await;
let record = mgr
.create_with_id(
ManagedSessionId::new(),
"mcp proxy task".to_string(),
None,
Some("mcpproxy".to_string()),
None,
None,
None,
RuntimeKind::default(),
false,
false,
)
.await
.expect("seed session");
{
let mut store = mgr.store.write().await;
let mut r = store.get(&record.id).await.unwrap();
r.state = ManagedSessionState::Active;
store.upsert(r).await.unwrap();
}
(state, record.tmux_name)
}
#[tokio::test]
async fn mcp_proxy_focus_message_summary_unfocus_round_trip() {
let (state, name) = seeded_state().await;
let focus = session_proxy_focus(&state, "conv-1", &name).await.unwrap();
assert_eq!(focus["outcome"], "focused");
assert_eq!(focus["name"], name);
let msg = session_proxy_message(&state, "conv-1", "run the tests")
.await
.unwrap();
assert_eq!(msg["outcome"], "sent");
assert_eq!(msg["target"]["name"], name);
assert_eq!(msg["text"], "run the tests");
let summary = session_proxy_summary(&state, "conv-1").await.unwrap();
assert_eq!(summary["outcome"], "summary");
assert_eq!(summary["target"]["name"], name);
assert!(
summary["summary"]
.as_str()
.unwrap()
.contains("mcp proxy task")
);
let cleared = session_proxy_unfocus(&state, "conv-1").await.unwrap();
assert_eq!(cleared["cleared"]["name"], name);
let after = session_proxy_message(&state, "conv-1", "still there?")
.await
.unwrap();
assert_eq!(after["outcome"], "no_focus");
}
#[tokio::test]
async fn mcp_proxy_focus_empty_queries_current() {
let (state, _name) = seeded_state().await;
let body = session_proxy_focus(&state, "conv-2", "").await.unwrap();
assert_eq!(body["outcome"], "current");
assert!(body["target"].is_null());
}
#[tokio::test]
async fn mcp_proxy_focus_shares_store_with_http_surface() {
let (state, name) = seeded_state().await;
session_proxy_focus(&state, "conv-shared", &name)
.await
.unwrap();
let store = state.proxy_focus_store();
let guard = store.lock().expect("focus store lock");
let focused = guard.get("conv-shared").expect("focus present in store");
assert_eq!(focused.name, name);
}
#[tokio::test]
async fn mcp_proxy_message_no_focus() {
let (state, _name) = seeded_state().await;
let body = session_proxy_message(&state, "conv-never", "hello")
.await
.unwrap();
assert_eq!(body["outcome"], "no_focus");
}
}