use super::{MissionControlApp, state};
use crate::config::ProviderCredential;
impl MissionControlApp {
pub(crate) fn finish_update_handoff(
&mut self,
) -> anyhow::Result<Option<crate::updates::RestartContext>> {
if self.update_restart.is_some()
&& let Some(session) = &self.state.current_session
&& !session.path().try_exists()?
{
session.append_with_outcome(&crate::sessions::SessionEvent::new_kind(
crate::sessions::SessionEventKind::Diagnostic,
session.id().to_owned(),
self.state.cwd.clone(),
serde_json::json!({"lifecycle": "created"}),
))?;
}
Ok(self.update_restart.take())
}
pub(super) fn request_update(
&mut self,
argument: Option<&str>,
ui_state: &mut state::MissionControlState,
) -> bool {
if argument.is_some() {
ui_state.status = "usage: /update (no arguments)".into();
return false;
}
if self.active_run
|| self.worker.is_some()
|| self.pending_session_switch.is_some()
|| self.pending_rewind.is_some()
|| self.pending_export.is_some()
|| self.pending_model_selection.is_some()
|| self.pending_theme_save.is_some()
|| self.settings_persistence.is_pending()
|| self.fast_mode_persistence.is_pending()
|| self.session_maintenance.is_pruning()
|| self.pending_startup_prompt.is_some()
{
ui_state.status = "Wait for the current work to finish before /update.".into();
return false;
}
let mut command = std::process::Command::new("magi-code");
command
.current_dir(&self.state.cwd)
.env("MC_HOME", &self.config.paths.root);
if let Some(session_id) = self.state.active_session_id() {
command.arg("--resume").arg(session_id);
} else {
command.arg("--no-session");
}
if let Some(provider) = &self.config.provider {
command.arg("--provider").arg(provider);
}
if let Some(model) = &self.config.model {
command.arg("--model").arg(model);
}
if self.theme_cli_override {
command.arg("--theme").arg(&self.appearance.theme.id);
}
if let Some(ProviderCredential::ApiKey { key }) = &self.config.auth {
let provider = self.config.provider.as_deref().unwrap_or_default();
let variable = if provider == crate::providers::ANTHROPIC_PROVIDER {
Some("ANTHROPIC_API_KEY")
} else {
self.config
.custom_providers
.get(provider)
.and_then(|provider| provider.api_key_env_var.as_deref())
};
let Some(variable) = variable else {
ui_state.status =
"Cannot safely preserve this credential; use --update outside Mission Control."
.into();
return false;
};
command.env(variable, key);
}
let session = self
.state
.active_session_id()
.map(|id| (self.config.paths.sessions.clone(), id.to_owned()));
self.update_restart = Some(crate::updates::RestartContext { command, session });
true
}
}
#[cfg(test)]
mod tests {
use super::super::{StartupReadiness, tests::test_app};
use super::*;
#[test]
fn update_handoff_resumes_exact_session_without_replaying_initial_prompt() {
let temp = tempfile::tempdir().unwrap();
let (sender, receiver) = crossbeam_channel::bounded(4);
let mut app = test_app(&temp, sender);
app.startup_readiness = StartupReadiness::Ready;
app.state.current_session = Some(
app.state
.session_manager
.create()
.unwrap()
.admit_standalone_writer()
.unwrap(),
);
let id = app.state.active_session_id().unwrap().to_owned();
app.config.provider = Some("anthropic".into());
app.config.model = Some("selected-model".into());
app.config.auth = Some(ProviderCredential::ApiKey {
key: "unused-test-credential".into(),
});
let mut state = state::MissionControlState::default();
assert!(app.submit(
"/update".into(),
&mut state,
&receiver,
ratatui::layout::Rect::new(0, 0, 80, 24)
));
app.cleanup_after_run().unwrap();
let restart = app.finish_update_handoff().unwrap().unwrap();
let reopened = app.state.session_manager.open_existing(&id).unwrap();
let events = reopened.read_events().unwrap();
assert_eq!(events.len(), 1);
assert_eq!(
events[0].payload,
serde_json::json!({"lifecycle": "created"})
);
let args: Vec<_> = restart.command.get_args().collect();
assert_eq!(args[0], "--resume");
assert_eq!(args[1], std::ffi::OsStr::new(&id));
assert!(
!args
.iter()
.any(|arg| *arg == "--prompt" || *arg == "--continue" || *arg == "--api-key")
);
assert_eq!(
restart.command.get_current_dir(),
Some(app.state.cwd.as_path())
);
assert!(
restart
.command
.get_envs()
.any(|(key, value)| key == "ANTHROPIC_API_KEY"
&& value == Some(std::ffi::OsStr::new("unused-test-credential")))
);
drop(app);
assert!(reopened.try_frontend_writer().unwrap().is_some());
}
#[test]
fn update_rejects_arguments_and_active_work_without_requesting_exit() {
let temp = tempfile::tempdir().unwrap();
let (sender, receiver) = crossbeam_channel::bounded(4);
let mut app = test_app(&temp, sender);
app.startup_readiness = StartupReadiness::Ready;
let mut state = state::MissionControlState::default();
let area = ratatui::layout::Rect::new(0, 0, 80, 24);
assert!(!app.submit("/update now".into(), &mut state, &receiver, area));
assert_eq!(state.status, "usage: /update (no arguments)");
app.active_run = true;
assert!(!app.submit("/update".into(), &mut state, &receiver, area));
assert!(app.update_restart.is_none());
}
}