use super::*;
pub trait SubagentBackend: Send + Sync {
fn events(
&self,
filter: crate::database::ApiEventFilter,
after_seq: Option<u64>,
) -> BoxFuture<'_, AnyResult<crate::database::ApiEventPage>> {
events::load_events(filter, after_seq)
}
fn profile_config(
&self,
profile: String,
model: Option<String>,
refresh: bool,
) -> BoxFuture<'_, AnyResult<mj_core::worker_launch::ProfileConfig>> {
Box::pin(crate::controller::profile_config::discover(
profile, model, refresh,
))
}
fn published_profile_config(
&self,
_profile: &str,
) -> Option<mj_core::worker_launch::ProfileConfig> {
None
}
fn start_subagent(
&self,
_request: crate::controller::RegisterSubagentRequest,
) -> BoxFuture<'_, AnyResult<mj_core::subagent::SubagentRecord>> {
Box::pin(async { anyhow::bail!("sub-agent creation is unavailable") })
}
fn list_subagents(
&self,
parent_session_id: String,
) -> BoxFuture<'_, AnyResult<Vec<mj_core::subagent::SubagentRecord>>> {
Box::pin(async move {
tokio::task::spawn_blocking(move || crate::database::list_subagents(&parent_session_id))
.await?
})
}
fn read_context_file(
&self,
session_id: String,
path: PathBuf,
) -> BoxFuture<'_, std::result::Result<Vec<u8>, ExportError>> {
self.read_file(session_id, path)
}
fn set_config(
&self,
session_id: String,
key: String,
value: String,
) -> BoxFuture<'_, AnyResult<()>> {
Box::pin(async move {
self.session_handle(session_id)
.await?
.ok_or_else(|| anyhow::anyhow!("session has no live actor"))?
.set_config(key, value)
.await
})
}
fn cancel_start(&self, _session_id: String) -> BoxFuture<'_, AnyResult<()>> {
Box::pin(async { Ok(()) })
}
fn session_handle(&self, session_id: String)
-> BoxFuture<'_, AnyResult<Option<SessionHandle>>>;
fn prompt(&self, session_id: String, text: String) -> BoxFuture<'_, AnyResult<u64>>;
fn turn_state(&self, session_id: String) -> BoxFuture<'_, AnyResult<Option<TurnState>>>;
fn turn_summary(
&self,
session_id: String,
turn_start_position: u64,
) -> BoxFuture<'_, AnyResult<TurnSummary>>;
fn start_followup(
&self,
session_id: String,
followup: StartFollowup,
) -> BoxFuture<'_, AnyResult<()>>;
fn start_status(&self, session_id: String) -> BoxFuture<'_, AnyResult<Option<StartStatus>>>;
fn transcript(
&self,
session_id: String,
after_seq: u64,
limit: usize,
role: Option<mj_core::transcript::TranscriptRole>,
) -> BoxFuture<'_, AnyResult<Option<TranscriptPage>>>;
fn usage(
&self,
session_id: String,
after_seq: u64,
limit: usize,
) -> BoxFuture<'_, AnyResult<Option<crate::database::UsagePage>>> {
Box::pin(async move {
tokio::task::spawn_blocking(move || {
crate::database::load_session_usage(&session_id, after_seq, limit)
})
.await?
})
}
fn diff(&self, session_id: String) -> BoxFuture<'_, Result<String, ExportError>>;
fn read_file(
&self,
session_id: String,
path: PathBuf,
) -> BoxFuture<'_, Result<Vec<u8>, ExportError>>;
fn write_file(
&self,
_session_id: String,
_path: PathBuf,
_bytes: Vec<u8>,
_overwrite: bool,
) -> BoxFuture<'_, Result<(), ExportError>> {
Box::pin(async { Err(ExportError::Refused("file injection is unavailable".into())) })
}
fn push_branch(
&self,
session_id: String,
branch: String,
) -> BoxFuture<'_, Result<PushedBranch, ExportError>>;
fn bundle(&self, session_id: String) -> BoxFuture<'_, Result<BundleExport, ExportError>>;
fn wiki_sync_is_stale(&self) -> bool {
false
}
fn wiki_request_sync(&self) {}
fn wiki_search(
&self,
_query: String,
_limit: usize,
) -> BoxFuture<'_, AnyResult<mj_client::daemon::WikiSearchPage>> {
Box::pin(async { anyhow::bail!("SessionWiki search is unavailable") })
}
fn wiki_brief(
&self,
_wiki_id: String,
_max_chars: usize,
) -> BoxFuture<'_, AnyResult<Option<String>>> {
Box::pin(async { anyhow::bail!("SessionWiki briefings are unavailable") })
}
fn wiki_restore(
&self,
_request: mj_client::daemon::WikiRestoreRequest,
) -> BoxFuture<'_, AnyResult<Option<String>>> {
Box::pin(async { anyhow::bail!("SessionWiki restore is unavailable") })
}
}
pub(super) fn backend(state: &ServerState) -> Result<&Arc<dyn SubagentBackend>, ApiFailure> {
state
.subagent
.as_ref()
.ok_or_else(|| ApiFailure::unavailable("this server has no subagent backend installed"))
}