use super::*;
use crate::core::agent::{Delegation, DelegationStatus};
use crate::core::paths::FrameworkPaths;
fn hermetic() -> (Arc<DaemonState>, tempfile::TempDir, SessionId) {
let dir = tempfile::tempdir().expect("temp dir");
let paths = FrameworkPaths::under(dir.path());
let state = Arc::new(DaemonState::with_paths(&paths));
(state, dir, SessionId(uuid::Uuid::new_v4()))
}
fn insert(
state: &DaemonState,
session: SessionId,
agent: &str,
cwd: &str,
isolation: Option<&str>,
tool_use_id: Option<&str>,
status: DelegationStatus,
) {
let mut d = Delegation::observed(session, agent, "task", tool_use_id.map(str::to_string));
d.cwd = Some(PathBuf::from(cwd));
d.isolation = isolation.map(str::to_string);
d.status = status;
state.upsert_delegation(d);
}
fn dispatch(
cwd: &str,
agent: &str,
isolation: Option<&str>,
tool_use_id: Option<&str>,
) -> SharedTreeDispatchRequest {
let mut input = serde_json::json!({"subagent_type": agent, "description": "go"});
if let Some(i) = isolation {
input["isolation"] = Value::String(i.to_string());
}
let mut payload = serde_json::json!({"cwd": cwd, "tool": "Agent", "input": input});
if let Some(id) = tool_use_id {
payload["tool_use_id"] = Value::String(id.to_string());
}
SharedTreeDispatchRequest { payload }
}
async fn call(
state: &Arc<DaemonState>,
session: SessionId,
req: SharedTreeDispatchRequest,
) -> SharedTreeWritersResponse {
let Json(body) =
shared_tree_dispatch_route(State(state.clone()), Path(session.0.to_string()), Json(req))
.await
.expect("route succeeds");
body
}
#[tokio::test]
async fn shared_tree_dispatch_route_reports_live_unisolated_writers() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_A"),
DelegationStatus::Running,
);
insert(
&state,
session,
"rust-engineer",
"/repo",
Some("worktree"),
Some("toolu_B"),
DelegationStatus::Running,
);
insert(
&state,
session,
"research",
"/repo",
None,
Some("toolu_C"),
DelegationStatus::Running,
);
insert(
&state,
session,
"python-engineer",
"/repo",
None,
Some("toolu_D"),
DelegationStatus::Completed,
);
insert(
&state,
session,
"python-engineer",
"/repo",
None,
Some("toolu_E"),
DelegationStatus::Stale,
);
insert(
&state,
session,
"python-engineer",
"/elsewhere",
None,
Some("toolu_F"),
DelegationStatus::Running,
);
insert(
&state,
SessionId(uuid::Uuid::new_v4()),
"rust-engineer",
"/repo",
None,
Some("toolu_G"),
DelegationStatus::Running,
);
let body = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_NEW")),
)
.await;
assert_eq!(body.total, 1, "only the live unisolated engineer counts");
assert_eq!(body.agents.len(), 1);
assert_eq!(body.agents[0].agent, "rust-engineer");
assert_eq!(body.agents[0].count, 1);
assert!(!body.claimed, "a denied dispatch must not claim the tree");
}
#[tokio::test]
async fn shared_tree_dispatch_route_excludes_the_callers_own_dispatch() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_SELF"),
DelegationStatus::Running,
);
let body = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_SELF")),
)
.await;
assert_eq!(body.total, 0, "the caller must not find itself");
assert_eq!(state.delegations_for(session).len(), 1);
}
#[tokio::test]
async fn shared_tree_dispatch_route_counts_concurrent_same_agent_dispatches() {
let (state, _dir, session) = hermetic();
for id in ["toolu_1", "toolu_2"] {
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some(id),
DelegationStatus::Running,
);
}
let body = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_3")),
)
.await;
assert_eq!(body.total, 2);
assert_eq!(body.agents.len(), 1, "one row per distinct name");
assert_eq!(body.agents[0].count, 2);
}
#[tokio::test]
async fn shared_tree_dispatch_route_is_empty_for_an_unknown_session() {
let (state, _dir, _session) = hermetic();
let Json(body) = shared_tree_dispatch_route(
State(state),
Path(uuid::Uuid::new_v4().to_string()),
Json(dispatch("/repo", "rust-engineer", None, Some("toolu_X"))),
)
.await
.expect("route succeeds");
assert_eq!(body.total, 0);
}
#[tokio::test]
async fn shared_tree_dispatch_route_rejects_a_malformed_session_id() {
let (state, _dir, _session) = hermetic();
let err = shared_tree_dispatch_route(
State(state),
Path("not-a-uuid".to_string()),
Json(dispatch("/repo", "rust-engineer", None, Some("toolu_X"))),
)
.await
.expect_err("a malformed id is a client error");
assert!(matches!(err, DaemonError::InvalidRequest(_)));
}
#[tokio::test]
async fn shared_tree_dispatch_route_denies_the_second_claim() {
let (state, _dir, session) = hermetic();
let first = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_A")),
)
.await;
assert_eq!(first.total, 0, "the first dispatch must be admitted");
assert!(first.claimed, "and it must take the directory");
let second = call(
&state,
session,
dispatch("/repo", "python-engineer", None, Some("toolu_B")),
)
.await;
assert_eq!(second.total, 1, "the second must find the first");
assert_eq!(second.agents[0].agent, "rust-engineer");
assert!(!second.claimed);
}
#[tokio::test]
async fn shared_tree_dispatch_route_reserves_the_tree_on_an_empty_answer() {
let (state, _dir, session) = hermetic();
call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_A")),
)
.await;
let records = state.delegations_for(session);
assert_eq!(records.len(), 1);
assert_eq!(records[0].agent, "rust-engineer");
assert_eq!(records[0].tool_use_id.as_deref(), Some("toolu_A"));
assert_eq!(
records[0].cwd.as_deref(),
Some(PathBuf::from("/repo").as_path())
);
assert_eq!(records[0].status, DelegationStatus::Running);
}
#[tokio::test]
async fn shared_tree_dispatch_route_does_not_reserve_when_it_denies() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_A"),
DelegationStatus::Running,
);
let body = call(
&state,
session,
dispatch("/repo", "python-engineer", None, Some("toolu_B")),
)
.await;
assert!(!body.claimed);
assert_eq!(
state.delegations_for(session).len(),
1,
"the denied dispatch must leave no record of its own"
);
}
#[tokio::test]
async fn shared_tree_dispatch_route_does_not_reserve_a_read_only_agent() {
for (agent, isolation) in [
("research", None),
("code-critic", None),
("rust-engineer", Some("worktree")),
("rust-engineer", Some("remote")),
("some-project-agent", None),
] {
let (state, _dir, session) = hermetic();
let body = call(
&state,
session,
dispatch("/repo", agent, isolation, Some("toolu_A")),
)
.await;
assert!(
!body.claimed,
"{agent}/{isolation:?} must not claim the tree"
);
assert!(state.delegations_for(session).is_empty());
}
}
#[tokio::test]
async fn shared_tree_dispatch_route_does_not_reserve_a_non_dispatch_tool() {
let (state, _dir, session) = hermetic();
let mut req = dispatch("/repo", "rust-engineer", None, Some("toolu_A"));
req.payload["tool"] = Value::String("Read".into());
let body = call(&state, session, req).await;
assert!(!body.claimed);
assert!(state.delegations_for(session).is_empty());
}
#[tokio::test]
async fn shared_tree_dispatch_route_is_empty_without_a_cwd() {
let (state, _dir, session) = hermetic();
insert(
&state,
session,
"rust-engineer",
"/repo",
None,
Some("toolu_A"),
DelegationStatus::Running,
);
let mut req = dispatch("/repo", "rust-engineer", None, Some("toolu_B"));
req.payload["cwd"] = Value::String(String::new());
let body = call(&state, session, req).await;
assert_eq!(body.total, 0);
assert!(!body.claimed);
assert_eq!(state.delegations_for(session).len(), 1);
}
#[tokio::test]
async fn shared_tree_dispatch_route_claim_is_idempotent_on_redelivery() {
let (state, _dir, session) = hermetic();
for _ in 0..3 {
let body = call(
&state,
session,
dispatch("/repo", "rust-engineer", None, Some("toolu_A")),
)
.await;
assert_eq!(body.total, 0, "a redelivery must stay admitted");
}
assert_eq!(state.delegations_for(session).len(), 1);
}