mj_controller/review_host/
environment.rs1use super::*;
2
3pub trait ReviewEnvironment: Send + Sync {
10 fn check(&self, session_id: &str, profile: &str) -> Result<(), String>;
13
14 fn resolve<'a>(
15 &'a self,
16 handle: ManagedSessionHandle,
17 config: ReviewConfig,
18 cancelled: Arc<std::sync::atomic::AtomicBool>,
19 ) -> mj_client::session::BoxFuture<
20 'a,
21 Result<mj_core::review::settings::ResolvedReviewSettings, String>,
22 >;
23
24 fn stage(
27 &self,
28 session_id: &str,
29 profile: &str,
30 generation: u64,
31 mcp_servers: &[mj_core::worker_launch::ReviewMcpServer],
32 dispatch_tool: bool,
33 ) -> Result<mj_core::worker_launch::ReviewerLaunchConfig, String>;
34
35 fn load_state(&self, session_id: &str) -> Result<TurnReviewState, String>;
38
39 fn save_state(&self, session_id: &str, state: &TurnReviewState) -> Result<(), String>;
43
44 fn clear_interrupted(&self) -> Result<Vec<String>, String>;
49}
50
51#[derive(Debug, Default)]
57pub struct ControllerEnvironment;
58
59impl ReviewEnvironment for ControllerEnvironment {
60 fn check(&self, session_id: &str, profile: &str) -> Result<(), String> {
61 let controller =
62 crate::controller::Controller::load().map_err(|error| format!("{error:#}"))?;
63 let Some(reviewer) = controller.config.profiles.get(profile) else {
64 return Err(format!(
65 "turn review needs a reviewer: [review] profile {profile:?} is not a profile in config.toml"
66 ));
67 };
68 if !reviewer.enabled {
69 return Err(format!(
70 "turn review needs an enabled reviewer: [review] profile {profile:?} is disabled"
71 ));
72 }
73 validate_reviewer_assignment(
74 session_id,
75 controller.state.sessions.get(session_id),
76 profile,
77 )
78 }
79
80 fn resolve<'a>(
81 &'a self,
82 handle: ManagedSessionHandle,
83 config: ReviewConfig,
84 cancelled: Arc<std::sync::atomic::AtomicBool>,
85 ) -> mj_client::session::BoxFuture<
86 'a,
87 Result<mj_core::review::settings::ResolvedReviewSettings, String>,
88 > {
89 Box::pin(async move {
90 let specialists = config.tier == ReviewTier::Extended;
91 crate::review_selection::resolve(handle, Some(config), specialists, cancelled)
92 .await
93 .map_err(|e| format!("{e:#}"))
94 })
95 }
96
97 fn stage(
98 &self,
99 session_id: &str,
100 profile: &str,
101 generation: u64,
102 mcp_servers: &[mj_core::worker_launch::ReviewMcpServer],
103 dispatch_tool: bool,
104 ) -> Result<mj_core::worker_launch::ReviewerLaunchConfig, String> {
105 let controller =
106 crate::controller::Controller::load().map_err(|error| format!("{error:#}"))?;
107 controller
108 .stage_reviewer_profile_with_mcp(
109 session_id,
110 profile,
111 generation,
112 mcp_servers,
113 dispatch_tool,
114 )
115 .map_err(|error| format!("{error:#}"))
116 }
117
118 fn load_state(&self, session_id: &str) -> Result<TurnReviewState, String> {
119 crate::database::turn_review_state(session_id).map_err(|error| format!("{error:#}"))
120 }
121
122 fn save_state(&self, session_id: &str, state: &TurnReviewState) -> Result<(), String> {
123 crate::database::save_turn_review_state(session_id, state)
124 .map_err(|error| format!("{error:#}"))
125 }
126
127 fn clear_interrupted(&self) -> Result<Vec<String>, String> {
128 crate::database::clear_interrupted_turn_reviews().map_err(|error| format!("{error:#}"))
129 }
130}
131
132pub(crate) fn validate_reviewer_assignment(
133 session_id: &str,
134 session: Option<&mj_core::state::SessionRecord>,
135 _profile: &str,
136) -> Result<(), String> {
137 let Some(session) = session else {
138 return Err(format!(
139 "session {session_id:?} is not in the controller store"
140 ));
141 };
142 if session.archived {
143 return Err("this session is archived".to_owned());
144 }
145 Ok(())
146}