diff --git a/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs b/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs index 21f7af01..734d47f7 100644 --- a/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +++ b/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs @@ -229,7 +229,7 @@ got {status:?}" #[test] #[serial(t3_event)] fn t3_unknown_business_status_is_preserved_without_normalized_event() { - let harness = McpSimHarness::new(); + let harness = McpSimHarness::new_without_tmux(); let mut worker = harness.spawn_mcp_client("worker_a", "teamA"); let status = "garbage-status-xyz"; let call = worker.call_tool( diff --git a/crates/team-agent/tests/support/mcp_sim_harness.rs b/crates/team-agent/tests/support/mcp_sim_harness.rs index 65a58b0d..d7e287bf 100644 --- a/crates/team-agent/tests/support/mcp_sim_harness.rs +++ b/crates/team-agent/tests/support/mcp_sim_harness.rs @@ -60,27 +60,8 @@ pub struct McpSimHarness { impl McpSimHarness { pub fn new() -> Self { - static N: AtomicU64 = AtomicU64::new(0); - let run_tag = format!( - "{}-{}", - std::process::id(), - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system clock after unix epoch") - .as_nanos() - ); - let workspace = std::env::temp_dir().join(format!( - "ta-rs-mcp-sim-{run_tag}-{}", - N.fetch_add(1, Ordering::Relaxed) - )); - let _ = std::fs::remove_dir_all(&workspace); - std::fs::create_dir_all(&workspace).unwrap(); - let workspace = std::fs::canonicalize(workspace).unwrap(); + let (workspace, session) = fresh_mcp_workspace(); let backend = TmuxBackend::for_workspace(&workspace); - let session = SessionName::new(format!( - "team-mcp-sim-{run_tag}-{}", - N.fetch_add(1, Ordering::Relaxed) - )); let mut harness = Self { workspace, backend, @@ -97,6 +78,23 @@ impl McpSimHarness { harness } + /// Build an MCP-only fixture for contracts whose assertion does not exercise + /// pane delivery. This keeps the stdio ingress test runnable on hosts without + /// a tmux binary (for example, the Grok full-test image). + pub fn new_without_tmux() -> Self { + let (workspace, session) = fresh_mcp_workspace(); + let backend = TmuxBackend::for_workspace(&workspace); + let harness = Self { + workspace, + backend, + session, + panes: BTreeMap::new(), + }; + harness.seed_mcp_report_state(); + let _ = MessageStore::open(&harness.workspace).unwrap(); + harness + } + pub fn spawn_mcp_client(&self, worker_id: &str, owner_team_id: &str) -> McpClient { spawn_mcp_client(&self.workspace, worker_id, owner_team_id) } @@ -332,6 +330,47 @@ impl McpSimHarness { ) .unwrap(); } + + fn seed_mcp_report_state(&self) { + team_agent::state::persist::save_runtime_state( + &self.workspace, + &json!({ + "active_team_key": "teamA", + "teams": { + "teamA": { + "tasks": [ + {"id": "task_mcp", "assignee": "worker_a", "status": "pending"} + ] + } + } + }), + ) + .unwrap(); + } +} + +fn fresh_mcp_workspace() -> (PathBuf, SessionName) { + static N: AtomicU64 = AtomicU64::new(0); + let run_tag = format!( + "{}-{}", + std::process::id(), + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system clock after unix epoch") + .as_nanos() + ); + let workspace = std::env::temp_dir().join(format!( + "ta-rs-mcp-sim-{run_tag}-{}", + N.fetch_add(1, Ordering::Relaxed) + )); + let _ = std::fs::remove_dir_all(&workspace); + std::fs::create_dir_all(&workspace).unwrap(); + let workspace = std::fs::canonicalize(workspace).unwrap(); + let session = SessionName::new(format!( + "team-mcp-sim-{run_tag}-{}", + N.fetch_add(1, Ordering::Relaxed) + )); + (workspace, session) } impl Drop for McpSimHarness {