use super::*;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "action", rename_all = "kebab-case", deny_unknown_fields)]
pub enum ControllerAction {
New {
#[serde(default)]
create_managed_worktree: Option<bool>,
#[serde(default)]
mjolnir_subagents: Option<bool>,
#[serde(default)]
workspace_id: String,
profile_id: String,
bundle_id: String,
target_id: String,
#[serde(default)]
title: Option<String>,
#[serde(default)]
project_directory: Option<PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
dirty_ack: Vec<String>,
},
Rename {
session_id: String,
title: String,
},
CancelTurn {
session_id: String,
},
SetConfig {
session_id: String,
key: String,
value: String,
},
SetPlanMode {
session_id: String,
active: bool,
},
RefreshQuota {
profile_id: String,
},
RefreshCapacity {
target_id: String,
},
Resume {
session_id: String,
workspace_id: String,
profile_id: String,
target_id: String,
queue: ResumeQueueDisposition,
#[serde(default)]
additional_mounts: Option<Vec<AdditionalMount>>,
#[serde(default)]
resource_allocation: Option<SessionResourceAllocation>,
},
Move {
request: MoveSessionRequest,
},
Open {
session_id: String,
},
Prompt {
session_id: String,
text: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
images: Vec<ViewerPromptImage>,
},
RunShell {
session_id: String,
command: String,
},
CancelShell {
session_id: String,
shell_command_id: String,
},
Close {
session_id: String,
},
#[serde(skip)]
ForceClose {
session_id: String,
delete_branch: bool,
},
Cancel {
session_id: String,
},
StartReview {
session_id: String,
},
ResolveReview {
session_id: String,
resolution: String,
},
RemoveQueuedPrompt {
session_id: String,
queue_id: String,
},
RespondElicitation {
session_id: String,
elicitation_id: String,
response: ElicitationResponse,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ViewerPromptImage {
#[serde(default)]
pub data_base64: String,
pub mime_type: String,
pub width: u32,
pub height: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attachment: Option<AttachmentRef>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ActionOutcome {
Accepted { session_id: Option<String> },
Busy,
SessionBusy,
NotCancellable,
Refused(Refusal),
Failed { reference: String },
}
impl ActionOutcome {
pub const fn accepted() -> Self {
Self::Accepted { session_id: None }
}
pub fn session_id(&self) -> Option<&str> {
match self {
Self::Accepted { session_id } => session_id.as_deref(),
_ => None,
}
}
pub(super) fn rejection(&self) -> Option<ApiError> {
match self {
Self::Accepted { .. } => None,
Self::Busy => Some(ApiError::new(
StatusCode::TOO_MANY_REQUESTS,
"the controller is at its concurrent action limit; retry shortly",
)),
Self::SessionBusy => Some(ApiError::new(
StatusCode::CONFLICT,
"another operation is already running for this session",
)),
Self::NotCancellable => Some(ApiError::new(
StatusCode::CONFLICT,
"the session has no cancellable operation",
)),
Self::Refused(refusal) => Some(ApiError::new(
match refusal.kind() {
RefusalKind::Precondition => StatusCode::CONFLICT,
RefusalKind::Unusable => StatusCode::UNPROCESSABLE_ENTITY,
},
refusal.message().to_owned(),
)),
Self::Failed { reference } => Some(ApiError::new(
StatusCode::INTERNAL_SERVER_ERROR,
format!(
"the controller could not start this action; \
the daemon log records the reason under reference {reference}"
),
)),
}
}
}
#[derive(Debug)]
pub struct ControllerRequest {
pub action: ControllerAction,
pub reply: tokio::sync::oneshot::Sender<ActionOutcome>,
}
#[derive(Debug)]
pub struct BundleRequest {
pub source: String,
pub reply: tokio::sync::oneshot::Sender<Result<String, BundleFailure>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BundleFailure {
InvalidSource,
Controller,
}
#[derive(Debug)]
pub enum PreflightRequest {
New(NewPreflightRequest),
Resume(ResumePreflightRequest),
CompletePath(PathCompletionRequest),
}
#[derive(Debug)]
pub struct PathCompletionRequest {
pub host: CompletionHost,
pub prefix: String,
pub kind: CompletionKind,
pub reply: tokio::sync::oneshot::Sender<Result<PathCompletion, String>>,
}
#[derive(Debug)]
pub struct NewPreflightRequest {
pub bundle_id: String,
pub target_id: String,
pub project_directory: Option<PathBuf>,
pub remote_repairs: Vec<mj_core::local_git::LocalRemoteRepair>,
pub reply: tokio::sync::oneshot::Sender<Result<PreflightNew, PreflightFailure>>,
}
#[derive(Debug)]
pub struct ResumePreflightRequest {
pub session_id: String,
pub target_id: String,
pub reply: tokio::sync::oneshot::Sender<Result<PreflightResume, PreflightFailure>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "kebab-case")]
pub enum PreflightResume {
Ready,
ConvertingRawCheckout {
preview: Box<mj_core::state::RawConversionPreview>,
},
Unavailable {
detail: String,
},
}
#[derive(Debug)]
pub struct MovePreparationRequest {
pub selection: MoveSelection,
pub reply: tokio::sync::oneshot::Sender<Result<MovePreparation, String>>,
}
#[derive(Debug)]
pub enum PreflightFailure {
Validation,
InvalidRepository(String),
Controller(String),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PreflightRepository {
pub id: String,
pub fetch_url: String,
pub default_branch: String,
pub push_urls: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PreflightNew {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project_directory: Option<PathBuf>,
#[serde(default)]
pub managed_worktree: mj_core::state::ManagedWorktreeOptions,
#[serde(default)]
pub remote_repairs: Vec<mj_core::local_git::LocalRemoteRepair>,
#[serde(default)]
pub dirty_repositories: Vec<String>,
#[serde(default)]
pub remote_repositories: Vec<PreflightRepository>,
pub local_changes_excluded: bool,
}
#[derive(Debug)]
pub enum ClientStateRequest {
Read {
client_id: String,
session_id: String,
reply: tokio::sync::oneshot::Sender<Result<ViewerClientState, String>>,
},
SaveDraft {
client_id: String,
session_id: String,
draft: String,
reply: tokio::sync::oneshot::Sender<Result<(), String>>,
},
MarkWorkspaceRead {
client_id: String,
workspace_id: String,
reply: tokio::sync::oneshot::Sender<Result<(), String>>,
},
History {
session_id: String,
query: String,
scope: String,
reply: tokio::sync::oneshot::Sender<Result<ViewerPromptHistory, String>>,
},
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ViewerClientState {
pub draft: String,
pub through_event_ordinal: u64,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ViewerPromptHistory {
pub entries: Vec<String>,
pub truncated: bool,
}
#[derive(Debug)]
pub struct ReadReceiptRequest {
pub client_id: String,
pub session_id: String,
pub through: u64,
pub reply: tokio::sync::oneshot::Sender<Result<(), String>>,
}
#[derive(Debug)]
pub struct BackgroundTaskStopRequest {
pub session_id: String,
pub background_task_id: String,
pub reply: tokio::sync::oneshot::Sender<Result<(), BackgroundTaskStopFailure>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BackgroundTaskStopFailure {
SessionUnavailable,
Provider,
Internal,
}