diff --git a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs index e11d8003..df4dccda 100644 --- a/epiphany-core/src/bin/epiphany-mvp-coordinator.rs +++ b/epiphany-core/src/bin/epiphany-mvp-coordinator.rs @@ -959,19 +959,37 @@ fn run_coordinator(args: &Args) -> Result { } else { None }; - let launch = launch_role( - &runtime_store, - &local_verse_store, - &thread_id, - role_id, - revision, - args.max_runtime_seconds, - if role_id == "modeling" { - proposal_modeling_request_id - } else { - None - }, - )?; + let launch = if role_id == "modeling" + && proposal_modeling_request_id.is_none() + && status["currentWork"]["bodyModeling"].is_object() + { + let binding = epiphany_core::launch_current_body_modeling_work( + &runtime_store, + epiphany_core::EpiphanyBodyModelingLaunchOptions { + job_id: Uuid::new_v4().to_string(), + created_at: now(), + }, + )?; + json!({ + "bindingId": epiphany_core::EPIPHANY_MODELING_ROLE_BINDING_ID, + "backendJobId": binding.job_id, + "bodyModelingLaunchBinding": binding, + }) + } else { + launch_role( + &runtime_store, + &local_verse_store, + &thread_id, + role_id, + revision, + args.max_runtime_seconds, + if role_id == "modeling" { + proposal_modeling_request_id + } else { + None + }, + )? + }; let worker_job_id = worker_job_id_from_launch(&launch)?; push_event( &mut step, diff --git a/epiphany-core/src/bin/epiphany-mvp-status.rs b/epiphany-core/src/bin/epiphany-mvp-status.rs index bb392b4a..7f3b4a65 100644 --- a/epiphany-core/src/bin/epiphany-mvp-status.rs +++ b/epiphany-core/src/bin/epiphany-mvp-status.rs @@ -496,7 +496,9 @@ fn run_native_status(args: &Args, include_auxiliary_status: bool) -> Result Result build( @@ -1365,6 +1377,7 @@ mod tests { research_continuation_action: None, frontier_planning_stage: RepoFrontierPlanningLifecycleStage::Unavailable, proposal_modeling_request_ready: false, + body_modeling_work_ready: false, } } @@ -1454,6 +1467,20 @@ mod tests { assert!(decision.can_auto_run); } + #[test] + fn unresolved_body_work_routes_modeling_without_research_or_thread_state() { + let decision = recommend_coordinator_action(EpiphanyCoordinatorInput { + body_modeling_work_ready: true, + ..input() + }); + assert_eq!(decision.action, EpiphanyCoordinatorAction::LaunchModeling); + assert_eq!( + decision.target_role, + Some(EpiphanyCoordinatorRoleId::Modeling) + ); + assert!(decision.can_auto_run); + } + fn finding( runtime_result_id: &str, runtime_job_id: &str, diff --git a/epiphany-core/src/surfaces/coordinator_contract.rs b/epiphany-core/src/surfaces/coordinator_contract.rs index 7c72dd29..ea5559cb 100644 --- a/epiphany-core/src/surfaces/coordinator_contract.rs +++ b/epiphany-core/src/surfaces/coordinator_contract.rs @@ -166,6 +166,7 @@ pub struct EpiphanyCoordinatorInput { pub frontier_planning_stage: RepoFrontierPlanningLifecycleStage, /// True when an explicitly selected user proposal has no Modeling launch. pub proposal_modeling_request_ready: bool, + pub body_modeling_work_ready: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] @@ -217,6 +218,7 @@ pub struct EpiphanyCoordinatorStatusInput { pub research_continuation_action: Option, pub frontier_planning_stage: RepoFrontierPlanningLifecycleStage, pub proposal_modeling_request_ready: bool, + pub body_modeling_work_ready: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]