mj_controller/server/api/
subagent_backend.rs1use super::*;
2
3pub trait SubagentBackend: Send + Sync {
9 fn events(
10 &self,
11 filter: crate::database::ApiEventFilter,
12 after_seq: Option<u64>,
13 ) -> BoxFuture<'_, AnyResult<crate::database::ApiEventPage>> {
14 events::load_events(filter, after_seq)
15 }
16
17 fn profile_config(
18 &self,
19 profile: String,
20 model: Option<String>,
21 refresh: bool,
22 ) -> BoxFuture<'_, AnyResult<mj_core::worker_launch::ProfileConfig>> {
23 Box::pin(crate::controller::profile_config::discover(
24 profile, model, refresh,
25 ))
26 }
27 fn published_profile_config(
33 &self,
34 _profile: &str,
35 ) -> Option<mj_core::worker_launch::ProfileConfig> {
36 None
37 }
38 fn start_subagent(
39 &self,
40 _request: crate::controller::RegisterSubagentRequest,
41 ) -> BoxFuture<'_, AnyResult<mj_core::subagent::SubagentRecord>> {
42 Box::pin(async { anyhow::bail!("sub-agent creation is unavailable") })
43 }
44 fn list_subagents(
45 &self,
46 parent_session_id: String,
47 ) -> BoxFuture<'_, AnyResult<Vec<mj_core::subagent::SubagentRecord>>> {
48 Box::pin(async move {
49 tokio::task::spawn_blocking(move || crate::database::list_subagents(&parent_session_id))
50 .await?
51 })
52 }
53 fn read_context_file(
54 &self,
55 session_id: String,
56 path: PathBuf,
57 ) -> BoxFuture<'_, std::result::Result<Vec<u8>, ExportError>> {
58 self.read_file(session_id, path)
59 }
60 fn set_config(
61 &self,
62 session_id: String,
63 key: String,
64 value: String,
65 ) -> BoxFuture<'_, AnyResult<()>> {
66 Box::pin(async move {
67 self.session_handle(session_id)
68 .await?
69 .ok_or_else(|| anyhow::anyhow!("session has no live actor"))?
70 .set_config(key, value)
71 .await
72 })
73 }
74 fn cancel_start(&self, _session_id: String) -> BoxFuture<'_, AnyResult<()>> {
75 Box::pin(async { Ok(()) })
76 }
77 fn session_handle(&self, session_id: String)
79 -> BoxFuture<'_, AnyResult<Option<SessionHandle>>>;
80
81 fn prompt(&self, session_id: String, text: String) -> BoxFuture<'_, AnyResult<u64>>;
83
84 fn turn_state(&self, session_id: String) -> BoxFuture<'_, AnyResult<Option<TurnState>>>;
86
87 fn turn_summary(
89 &self,
90 session_id: String,
91 turn_start_position: u64,
92 ) -> BoxFuture<'_, AnyResult<TurnSummary>>;
93
94 fn start_followup(
96 &self,
97 session_id: String,
98 followup: StartFollowup,
99 ) -> BoxFuture<'_, AnyResult<()>>;
100
101 fn start_status(&self, session_id: String) -> BoxFuture<'_, AnyResult<Option<StartStatus>>>;
103
104 fn transcript(
106 &self,
107 session_id: String,
108 after_seq: u64,
109 limit: usize,
110 role: Option<mj_core::transcript::TranscriptRole>,
111 ) -> BoxFuture<'_, AnyResult<Option<TranscriptPage>>>;
112
113 fn usage(
114 &self,
115 session_id: String,
116 after_seq: u64,
117 limit: usize,
118 ) -> BoxFuture<'_, AnyResult<Option<crate::database::UsagePage>>> {
119 Box::pin(async move {
120 tokio::task::spawn_blocking(move || {
121 crate::database::load_session_usage(&session_id, after_seq, limit)
122 })
123 .await?
124 })
125 }
126
127 fn diff(&self, session_id: String) -> BoxFuture<'_, Result<String, ExportError>>;
129
130 fn read_file(
132 &self,
133 session_id: String,
134 path: PathBuf,
135 ) -> BoxFuture<'_, Result<Vec<u8>, ExportError>>;
136
137 fn write_file(
138 &self,
139 _session_id: String,
140 _path: PathBuf,
141 _bytes: Vec<u8>,
142 _overwrite: bool,
143 ) -> BoxFuture<'_, Result<(), ExportError>> {
144 Box::pin(async { Err(ExportError::Refused("file injection is unavailable".into())) })
145 }
146
147 fn push_branch(
149 &self,
150 session_id: String,
151 branch: String,
152 ) -> BoxFuture<'_, Result<PushedBranch, ExportError>>;
153
154 fn bundle(&self, session_id: String) -> BoxFuture<'_, Result<BundleExport, ExportError>>;
156
157 fn wiki_sync_is_stale(&self) -> bool {
160 false
161 }
162
163 fn wiki_request_sync(&self) {}
166
167 fn wiki_search(
168 &self,
169 _query: String,
170 _limit: usize,
171 ) -> BoxFuture<'_, AnyResult<mj_client::daemon::WikiSearchPage>> {
172 Box::pin(async { anyhow::bail!("SessionWiki search is unavailable") })
173 }
174
175 fn wiki_brief(
177 &self,
178 _wiki_id: String,
179 _max_chars: usize,
180 ) -> BoxFuture<'_, AnyResult<Option<String>>> {
181 Box::pin(async { anyhow::bail!("SessionWiki briefings are unavailable") })
182 }
183
184 fn wiki_restore(
187 &self,
188 _request: mj_client::daemon::WikiRestoreRequest,
189 ) -> BoxFuture<'_, AnyResult<Option<String>>> {
190 Box::pin(async { anyhow::bail!("SessionWiki restore is unavailable") })
191 }
192}
193
194pub(super) fn backend(state: &ServerState) -> Result<&Arc<dyn SubagentBackend>, ApiFailure> {
195 state
196 .subagent
197 .as_ref()
198 .ok_or_else(|| ApiFailure::unavailable("this server has no subagent backend installed"))
199}
200
201