1use super::*;
2
3#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(deny_unknown_fields)]
6pub struct DiffOptions {
7 pub base: Option<String>,
8 #[serde(default)]
9 pub json: bool,
10}
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct ApiBackgroundWork {
15 pub known: Option<bool>,
16 pub tasks: Vec<mj_core::relay::BackgroundCommand>,
17}
18
19impl From<&mj_core::relay::RelayOperationalState> for ApiBackgroundWork {
20 fn from(state: &mj_core::relay::RelayOperationalState) -> Self {
21 Self {
22 known: state.background_work_known,
23 tasks: state.background_commands.clone(),
24 }
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub struct ApiSession {
33 #[serde(default)]
35 pub background_tasks: Vec<crate::server::ViewerBackgroundTask>,
36 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub assessment: Option<mj_core::assessment::Summary>,
38 #[serde(default)]
39 pub subagents: mj_core::subagent::SubagentPolicy,
40 #[serde(default, skip_serializing_if = "Option::is_none")]
44 pub at: Option<String>,
45 #[serde(default, skip_serializing_if = "Option::is_none")]
47 pub branch: Option<String>,
48 #[serde(default, skip_serializing_if = "Option::is_none")]
50 pub base: Option<String>,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub background_work: Option<ApiBackgroundWork>,
53 pub id: String,
54 pub workspace_id: String,
55 pub title: String,
56 pub harness_kind: String,
57 pub profile_id: String,
58 pub target_id: String,
59 pub bundle_id: String,
60 pub state: String,
61 pub lifecycle: ViewerLifecycleCategory,
62 pub chat_phase: crate::server::ViewerChatPhase,
63 pub is_idle: bool,
64 #[serde(default, skip_serializing_if = "Option::is_none")]
69 pub activity_state: Option<mj_core::activity::ActivityState>,
70 pub has_error: bool,
71 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub error: Option<String>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub storage_problem: Option<String>,
80 pub created_at: String,
81 pub updated_at: String,
82 #[serde(default, skip_serializing_if = "Option::is_none")]
86 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub last_turn_diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
89 #[serde(default)]
90 pub config_options: Vec<crate::server::ViewerConfigOption>,
91 #[serde(default, skip_serializing_if = "Vec::is_empty")]
92 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
93}
94
95impl From<&ViewerSession> for ApiSession {
96 fn from(session: &ViewerSession) -> Self {
97 Self {
98 background_tasks: session.background_tasks.clone(),
99 assessment: None,
100 subagents: session.subagents.clone(),
101 background_work: None,
102 at: session.at.clone(),
103 branch: session.branch.clone(),
104 base: session.base.clone(),
105 id: session.id.clone(),
106 workspace_id: session.workspace_id.clone(),
107 title: session.title.clone(),
108 harness_kind: session.harness_kind.clone(),
109 profile_id: session.profile_id.clone(),
110 target_id: session.target_id.clone(),
111 bundle_id: session.bundle_id.clone(),
112 state: session.state.clone(),
113 lifecycle: session.lifecycle,
114 chat_phase: session.chat_phase,
115 is_idle: session.is_idle,
116 activity_state: session.activity_state.clone(),
117 has_error: session.has_error,
118 error: session.launch_error.clone(),
119 storage_problem: session.storage_problem.clone(),
120 created_at: session.created_at.clone(),
121 updated_at: session.updated_at.clone(),
122 last_turn_outcome: None,
123 last_turn_diagnostic: None,
124 config_options: session.config_options.clone(),
125 pending_elicitations: session.pending_elicitations.clone(),
126 }
127 }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[serde(deny_unknown_fields)]
132pub struct StopBackgroundTaskRequest {
133 pub background_task_id: String,
134}
135
136#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
137pub struct SessionListResponse {
138 pub sessions: Vec<ApiSession>,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144pub struct WorkspaceListResponse {
145 pub workspaces: Vec<mj_core::workspace::WorkspaceRecord>,
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
152#[serde(deny_unknown_fields)]
153pub struct CreateWorkspaceRequest {
154 pub name: String,
155}
156
157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
158pub struct CreateWorkspaceResponse {
159 pub workspace: mj_core::workspace::WorkspaceRecord,
160}
161
162#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
175#[serde(deny_unknown_fields)]
176pub struct StartSessionRequest {
177 #[serde(default)]
178 pub create_managed_worktree: Option<bool>,
179 #[serde(default, skip_serializing_if = "Option::is_none")]
182 pub at: Option<String>,
183 #[serde(default, skip_serializing_if = "Option::is_none")]
186 pub branch: Option<String>,
187 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub base: Option<String>,
191 #[serde(default)]
193 pub subagents: Option<mj_core::subagent::SubagentPolicy>,
194 #[serde(default, skip_serializing_if = "Option::is_none")]
196 pub review: Option<mj_core::config::SessionReview>,
197 #[serde(default)]
198 pub workspace_id: Option<String>,
199 #[serde(default)]
202 pub profile_id: Option<String>,
203 #[serde(default)]
205 pub target_id: Option<String>,
206 #[serde(default)]
207 pub bundle_id: Option<String>,
208 #[serde(default)]
209 pub project_directory: Option<PathBuf>,
210 #[serde(default)]
211 pub title: Option<String>,
212 #[serde(default)]
213 pub model: Option<String>,
214 #[serde(default)]
215 pub effort: Option<String>,
216 #[serde(default)]
217 pub prompt: Option<String>,
218 #[serde(default, skip_serializing_if = "Option::is_none")]
221 pub cpus: Option<u64>,
222 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub memory_bytes: Option<u64>,
225}
226
227#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
232#[serde(deny_unknown_fields)]
233pub struct ResumeSessionRequest {
234 #[serde(default)]
236 pub profile_id: Option<String>,
237 #[serde(default)]
239 pub target_id: Option<String>,
240 #[serde(default)]
242 pub workspace_id: Option<String>,
243 #[serde(default)]
247 pub queue: Option<mj_core::state::ResumeQueueDisposition>,
248}
249
250#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
253pub struct ResumeSessionResponse {
254 pub session_id: String,
255 pub workspace_id: String,
256 pub profile_id: String,
257 pub target_id: String,
258}
259
260#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
263pub struct SuspendSessionResponse {
264 #[serde(default)]
265 pub session_id: String,
266 #[serde(default)]
268 pub stopped_subagents: usize,
269 #[serde(default)]
271 pub subagents_not_handed_back: usize,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
275 pub warning: Option<String>,
276}
277
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279pub struct StartSessionResponse {
280 pub session_id: String,
281 #[serde(default, skip_serializing_if = "Option::is_none")]
284 pub turn_id: Option<u64>,
285}
286
287#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
288#[serde(deny_unknown_fields)]
289pub struct SpawnSubagentRequest {
290 pub task_name: String,
291 pub instructions: String,
292 #[serde(default)]
293 pub profile_id: Option<String>,
294 #[serde(default)]
295 pub model: Option<String>,
296 #[serde(default)]
297 pub effort: Option<String>,
298 #[serde(default)]
299 pub working_directory: Option<PathBuf>,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
304pub struct SubagentCandidate {
305 pub profile_id: String,
306 pub harness: mj_core::config::HarnessKind,
307 pub choices: mj_core::worker_launch::ProfileConfig,
308 pub remaining_percent: Option<u8>,
312}
313
314#[derive(Debug, Clone, Default, PartialEq, Eq)]
317pub struct SubagentCandidates {
318 pub offered: Vec<SubagentCandidate>,
319 pub unavailable: Vec<(String, String)>,
320}
321
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct SubagentView {
324 pub parent_session_id: String,
325 pub task_name: String,
326 pub session: ApiSession,
327}
328
329#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
330pub struct SubagentListResponse {
331 pub subagents: Vec<SubagentView>,
332}
333
334#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
335#[serde(deny_unknown_fields)]
336pub struct PromptRequest {
337 pub text: String,
338}
339
340#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
341pub struct PromptResponse {
342 pub turn_id: u64,
345}
346
347#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
348#[serde(deny_unknown_fields)]
349pub struct WaitRequest {
350 #[serde(default)]
352 pub return_on_input: bool,
353 #[serde(default)]
357 pub turn_id: Option<u64>,
358 #[serde(default)]
359 pub timeout_secs: Option<u64>,
360}
361
362#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
364#[serde(rename_all = "snake_case")]
365pub enum WaitOutcome {
366 InputRequired,
368 Finished,
370 Error,
372 Cancelled,
374 QuotaLimit,
376 Timeout,
378 Stopped,
380}
381
382#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
383pub struct WaitCapacityRetry {
384 pub attempt: u32,
385 pub retry_at_ms: i64,
386}
387
388impl From<&CapacityRetry> for WaitCapacityRetry {
389 fn from(retry: &CapacityRetry) -> Self {
390 Self {
391 attempt: retry.attempt,
392 retry_at_ms: retry.retry_at_ms,
393 }
394 }
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
403#[serde(rename_all = "snake_case")]
404pub enum RelayState {
405 Connected,
407 Disconnected,
409 Unreachable,
411 TargetMissing,
413 ProjectionIntegrity,
415}
416
417#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
418pub struct RelayHealth {
419 pub state: RelayState,
420 #[serde(default, skip_serializing_if = "Option::is_none")]
422 pub detail: Option<String>,
423}
424
425impl From<&mj_client::session::ManagedSessionView> for RelayHealth {
426 fn from(view: &mj_client::session::ManagedSessionView) -> Self {
427 use mj_client::session::ViewError;
428 match &view.error {
431 Some(error) => Self {
432 state: match error {
433 ViewError::Unreachable(_) => RelayState::Unreachable,
434 ViewError::TargetMissing(_) => RelayState::TargetMissing,
435 ViewError::ProjectionIntegrity(_) => RelayState::ProjectionIntegrity,
436 },
437 detail: Some(error.detail().to_owned()),
438 },
439 None if view.connected => Self {
440 state: RelayState::Connected,
441 detail: None,
442 },
443 None => Self {
444 state: RelayState::Disconnected,
445 detail: None,
446 },
447 }
448 }
449}
450
451#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
452pub struct WaitResponse {
453 #[serde(default, skip_serializing_if = "Option::is_none")]
454 pub diagnostic: Option<mj_core::diagnostic::TurnDiagnostic>,
455
456 #[serde(default, skip_serializing_if = "Vec::is_empty")]
457 pub pending_elicitations: Vec<mj_core::elicitation::ElicitationRequest>,
458 #[serde(default, skip_serializing_if = "Option::is_none")]
459 pub usage: Option<mj_core::usage::TokenUsage>,
460 pub outcome: WaitOutcome,
461 #[serde(default, skip_serializing_if = "Option::is_none")]
463 pub stop_reason: Option<String>,
464 #[serde(default, skip_serializing_if = "Option::is_none")]
466 pub message: Option<String>,
467 #[serde(default, skip_serializing_if = "Option::is_none")]
470 pub final_message: Option<String>,
471 #[serde(default, skip_serializing_if = "Option::is_none")]
475 pub report_source: Option<String>,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
479 pub turn_id: Option<u64>,
480 #[serde(default, skip_serializing_if = "Option::is_none")]
483 pub requested_turn_id: Option<u64>,
484 #[serde(default, skip_serializing_if = "Option::is_none")]
486 pub turn_number: Option<u64>,
487 #[serde(default, skip_serializing_if = "Option::is_none")]
488 pub elapsed_ms: Option<i64>,
489 #[serde(default, skip_serializing_if = "Option::is_none")]
492 pub tool_calls: Option<u64>,
493 #[serde(default, skip_serializing_if = "Option::is_none")]
495 pub capacity_retry: Option<WaitCapacityRetry>,
496 #[serde(default, skip_serializing_if = "Option::is_none")]
497 pub server_retry: Option<WaitCapacityRetry>,
498 #[serde(default)]
499 pub retry_assessment_pending: bool,
500 #[serde(default, skip_serializing_if = "Option::is_none")]
501 pub quota_recovery: Option<mj_core::continuation::QuotaRecovery>,
502 #[serde(default, skip_serializing_if = "Option::is_none")]
506 pub relay: Option<RelayHealth>,
507 pub session: ApiSession,
508}
509
510pub const DEFAULT_TRANSCRIPT_LIMIT: usize = 200;
515pub const MAX_TRANSCRIPT_LIMIT: usize = 1_000;
516
517#[derive(Debug, Clone, Default, PartialEq, Serialize)]
518pub struct TranscriptQuery {
519 pub role: Vec<mj_core::transcript::TranscriptRole>,
520 pub finished_only: bool,
522 pub after_seq: Option<u64>,
524 pub limit: Option<usize>,
525}
526
527impl<'de> serde::Deserialize<'de> for TranscriptQuery {
528 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
529 where
530 D: serde::Deserializer<'de>,
531 {
532 struct TranscriptQueryVisitor;
533
534 impl<'de> serde::de::Visitor<'de> for TranscriptQueryVisitor {
535 type Value = TranscriptQuery;
536
537 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
538 formatter.write_str("transcript query parameters")
539 }
540
541 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
542 where
543 M: serde::de::MapAccess<'de>,
544 {
545 let mut role = Vec::new();
546 let mut finished_only = None;
547 let mut after_seq = None;
548 let mut limit = None;
549
550 while let Some(key) = map.next_key::<String>()? {
551 match key.as_str() {
552 "role" => role.push(map.next_value()?),
553 "finished_only" => {
554 if finished_only.replace(map.next_value()?).is_some() {
555 return Err(serde::de::Error::duplicate_field("finished_only"));
556 }
557 }
558 "after_seq" => {
559 if after_seq.replace(map.next_value()?).is_some() {
560 return Err(serde::de::Error::duplicate_field("after_seq"));
561 }
562 }
563 "limit" => {
564 if limit.replace(map.next_value()?).is_some() {
565 return Err(serde::de::Error::duplicate_field("limit"));
566 }
567 }
568 _ => {
569 let _: serde::de::IgnoredAny = map.next_value()?;
570 }
571 }
572 }
573
574 Ok(TranscriptQuery {
575 role,
576 finished_only: finished_only.unwrap_or_default(),
577 after_seq,
578 limit,
579 })
580 }
581 }
582
583 deserializer.deserialize_map(TranscriptQueryVisitor)
584 }
585}
586
587#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
588pub struct TranscriptItemView {
589 pub stable_id: String,
590 pub position: u64,
591 pub seq: u64,
594 pub role: String,
595 pub text: String,
597 pub created_at_ms: i64,
598 pub last_changed_at_ms: i64,
599 pub body: mj_core::transcript::TranscriptBody,
601}
602
603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
604pub struct TranscriptResponse {
605 #[serde(default)]
606 pub next_after_seq: u64,
607 pub session_id: String,
608 pub latest_seq: u64,
611 pub execution: MaterializedExecutionState,
612 pub items: Vec<TranscriptItemView>,
613}
614
615#[derive(Debug, Clone, PartialEq, Eq)]
622pub struct TurnState {
623 pub execution: MaterializedExecutionState,
624 pub active_turn: Option<MaterializedTurn>,
625 pub last_turn_outcome: Option<MaterializedTurnOutcome>,
626}
627
628pub use crate::database::TurnSummary;
629
630#[derive(Debug, Clone, Default, PartialEq, Eq)]
633pub struct StartFollowup {
634 pub model: Option<String>,
635 pub effort: Option<String>,
636 pub prompt: Option<String>,
637 pub fast_mode: bool,
638}
639
640#[derive(Debug, Clone, PartialEq, Eq)]
642pub enum StartStatus {
643 Pending,
645 Submitted { turn_id: u64 },
647 Failed { message: String },
649}
650
651pub use crate::database::TranscriptPage;
653
654#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
656pub struct PushedBranch {
657 pub branch: String,
658 pub remote: String,
659}
660
661#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
663pub struct FileQuery {
664 pub path: String,
666}
667
668#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
670#[serde(rename_all = "snake_case")]
671pub enum ExportKind {
672 Patch,
674 Branch,
676 Bundle,
678}
679
680#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
681pub struct ExportRequest {
682 pub kind: ExportKind,
683 #[serde(default, skip_serializing_if = "Option::is_none")]
685 pub branch: Option<String>,
686}
687
688#[derive(Debug, Clone)]
690pub struct BundleExport {
691 pub repository: String,
692 pub bytes: Vec<u8>,
693}
694
695#[derive(Debug)]
697pub enum ExportError {
698 Refused(String),
701 Failed(anyhow::Error),
703}
704
705impl From<ExportError> for ApiFailure {
706 fn from(error: ExportError) -> Self {
707 match error {
708 ExportError::Refused(message) => Self::conflict(message),
709 ExportError::Failed(error) => Self::from(error),
710 }
711 }
712}
713
714#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
717pub struct StartReviewResponse {
718 pub session_id: String,
719 pub started: bool,
720}
721
722#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
724pub struct ResolveReviewResponse {
725 pub session_id: String,
726 pub resolution: String,
728}
729
730#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
734pub struct ReviewStatusResponse {
735 pub session_id: String,
736 #[serde(default)]
737 pub review: Option<crate::server::ViewerTurnReview>,
738}
739
740#[derive(Debug, Default, Deserialize)]
745#[serde(deny_unknown_fields)]
746pub struct WikiSearchQuery {
747 #[serde(default)]
748 pub q: Option<String>,
749 #[serde(default)]
750 pub limit: Option<usize>,
751}
752
753#[derive(Debug, Default, Deserialize)]
754#[serde(deny_unknown_fields)]
755pub struct WikiBriefQuery {
756 #[serde(default)]
757 pub max_chars: Option<usize>,
758}
759
760#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
761pub struct WikiBriefResponse {
762 pub markdown: String,
763}
764
765#[derive(Debug, Default, Deserialize)]
767#[serde(deny_unknown_fields)]
768pub struct WikiHitsQuery {
769 pub q: String,
770 #[serde(default)]
771 pub context_messages: Option<usize>,
772 #[serde(default)]
773 pub per_message_chars: Option<usize>,
774}
775
776#[derive(Debug, Clone, Default, Serialize, Deserialize)]
779#[serde(deny_unknown_fields)]
780pub struct WikiRestoreBody {
781 #[serde(default)]
782 pub workspace_id: Option<String>,
783 pub profile_id: String,
784 pub target_id: String,
785 #[serde(default)]
786 pub project_directory: Option<PathBuf>,
787 #[serde(default)]
788 pub model: Option<String>,
789 #[serde(default)]
790 pub effort: Option<String>,
791}
792
793#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
807#[serde(deny_unknown_fields)]
808pub struct LaunchOptions {
809 pub revision: u64,
813 pub profiles: Vec<LaunchProfile>,
814 pub targets: Vec<LaunchTarget>,
815 pub bundles: Vec<LaunchBundle>,
816 #[serde(default, skip_serializing_if = "Vec::is_empty")]
819 pub hosts: Vec<LaunchHost>,
820 #[serde(default, skip_serializing_if = "Option::is_none")]
824 pub default: Option<LaunchDefault>,
825}
826
827#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
829#[serde(deny_unknown_fields)]
830pub struct LaunchProfile {
831 pub id: String,
832 pub harness: String,
835}
836
837#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
839#[serde(deny_unknown_fields)]
840pub struct LaunchTarget {
841 pub id: String,
842 pub kind: String,
844 pub requires_project_directory: bool,
847 pub availability: LaunchAvailability,
848 #[serde(default, skip_serializing_if = "Option::is_none")]
852 pub unavailable_reason: Option<String>,
853 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
858 pub runtime_missing: bool,
859 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
864 pub default_candidate: bool,
865 #[serde(default, skip_serializing_if = "Option::is_none")]
867 pub host: Option<String>,
868}
869
870#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
877#[serde(rename_all = "lowercase")]
878pub enum LaunchAvailability {
879 Ready,
881 Stale,
883 Unavailable,
885 Unknown,
887}
888
889#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
891#[serde(deny_unknown_fields)]
892pub struct LaunchBundle {
893 pub id: String,
894 pub primary_repository: String,
895 pub repositories: Vec<LaunchRepository>,
896}
897
898#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
899#[serde(deny_unknown_fields)]
900pub struct LaunchRepository {
901 pub id: String,
902 #[serde(default, skip_serializing_if = "Option::is_none")]
906 pub github: Option<String>,
907 pub destination: String,
908}
909
910#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
913#[serde(deny_unknown_fields)]
914pub struct LaunchHost {
915 pub id: String,
916 pub label: String,
917 pub targets: Vec<String>,
919 pub stale: bool,
920 pub refreshing: bool,
921 pub has_error: bool,
922}
923
924#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
926#[serde(deny_unknown_fields)]
927pub struct LaunchDefault {
928 pub profile_id: String,
929 pub target_id: String,
930}