use super::*;
pub trait ReviewEnvironment: Send + Sync {
fn check(&self, session_id: &str, profile: &str) -> Result<(), String>;
fn stage(
&self,
session_id: &str,
profile: &str,
generation: u64,
mcp_servers: &[mj_core::worker_launch::ReviewMcpServer],
dispatch_tool: bool,
) -> Result<mj_core::worker_launch::ReviewerLaunchConfig, String>;
fn load_state(&self, session_id: &str) -> Result<TurnReviewState, String>;
fn save_state(&self, session_id: &str, state: &TurnReviewState) -> Result<(), String>;
fn clear_interrupted(&self) -> Result<Vec<String>, String>;
}
#[derive(Debug, Default)]
pub struct ControllerEnvironment;
impl ReviewEnvironment for ControllerEnvironment {
fn check(&self, session_id: &str, profile: &str) -> Result<(), String> {
let controller =
crate::controller::Controller::load().map_err(|error| format!("{error:#}"))?;
let Some(reviewer) = controller.config.profiles.get(profile) else {
return Err(format!(
"turn review needs a reviewer: [review] profile {profile:?} is not a profile in config.toml"
));
};
if !reviewer.enabled {
return Err(format!(
"turn review needs an enabled reviewer: [review] profile {profile:?} is disabled"
));
}
validate_reviewer_assignment(
session_id,
controller.state.sessions.get(session_id),
profile,
)
}
fn stage(
&self,
session_id: &str,
profile: &str,
generation: u64,
mcp_servers: &[mj_core::worker_launch::ReviewMcpServer],
dispatch_tool: bool,
) -> Result<mj_core::worker_launch::ReviewerLaunchConfig, String> {
let controller =
crate::controller::Controller::load().map_err(|error| format!("{error:#}"))?;
controller
.stage_reviewer_profile_with_mcp(
session_id,
profile,
generation,
mcp_servers,
dispatch_tool,
)
.map_err(|error| format!("{error:#}"))
}
fn load_state(&self, session_id: &str) -> Result<TurnReviewState, String> {
crate::database::turn_review_state(session_id).map_err(|error| format!("{error:#}"))
}
fn save_state(&self, session_id: &str, state: &TurnReviewState) -> Result<(), String> {
crate::database::save_turn_review_state(session_id, state)
.map_err(|error| format!("{error:#}"))
}
fn clear_interrupted(&self) -> Result<Vec<String>, String> {
crate::database::clear_interrupted_turn_reviews().map_err(|error| format!("{error:#}"))
}
}
pub(crate) fn validate_reviewer_assignment(
session_id: &str,
session: Option<&mj_core::state::SessionRecord>,
profile: &str,
) -> Result<(), String> {
let Some(session) = session else {
return Err(format!(
"session {session_id:?} is not in the controller store"
));
};
if session.archived {
return Err("this session is archived".to_owned());
}
if session.last_profile == profile {
return Err(format!(
"turn review profile {profile:?} is also this session's primary profile; choose a different [review] profile"
));
}
Ok(())
}