diff --git a/crates/maestro-protocol/src/client.rs b/crates/maestro-protocol/src/client.rs index 696f662..a832025 100644 --- a/crates/maestro-protocol/src/client.rs +++ b/crates/maestro-protocol/src/client.rs @@ -141,6 +141,16 @@ impl DaemonClient { json(check(resp).await?).await } + /// `POST /api/sessions/:id/resume` (#166): respawn a dormant session + /// under its own uuid and sidecar. Refused with `409` (`already_running` + /// / `cwd_missing`) when there is nothing a respawn could fix. + pub async fn resume_session(&self, id: Uuid) -> Result { + let resp = self + .send(Method::POST, &routes::session_resume(id), &[], None::<&()>) + .await?; + json(check(resp).await?).await + } + /// `POST /api/sessions/:id/activity` (#105): report an agent-observed /// state. The daemon holds it above its own inference for a TTL, then /// resumes inference. Answers `204 No Content`. diff --git a/crates/maestro-tui/src/action.rs b/crates/maestro-tui/src/action.rs index 32ad7d5..7d01e7a 100644 --- a/crates/maestro-tui/src/action.rs +++ b/crates/maestro-tui/src/action.rs @@ -51,6 +51,9 @@ pub enum Action { /// Rename the highlighted session (#125): opens a prompt pre-filled with /// its current name. RenameHighlighted, + /// Resume the highlighted dormant session (#166, #169): respawns it + /// under its own uuid and sidecar via `POST /api/sessions/:id/resume`. + ResumeHighlighted, // Chrome /// Open the `ctrl-k` command bar (`docs/COMMAND-BAR.md`) — a query line @@ -105,6 +108,7 @@ impl Action { Action::FastForwardSpawn => "Create session with defaults".into(), Action::KillHighlighted => "Kill selected session".into(), Action::RenameHighlighted => "Rename selected session".into(), + Action::ResumeHighlighted => "Resume dormant session".into(), Action::OpenCommandBar => "Go to a session".into(), Action::SendLiteralCtrlK => "Send literal Ctrl-K to session".into(), Action::SendLiteralLeader => "Send literal leader key to session".into(), @@ -134,6 +138,7 @@ impl Action { "FastForwardSpawn" => return Ok(Action::FastForwardSpawn), "KillHighlighted" => return Ok(Action::KillHighlighted), "RenameHighlighted" => return Ok(Action::RenameHighlighted), + "ResumeHighlighted" => return Ok(Action::ResumeHighlighted), "OpenCommandBar" => return Ok(Action::OpenCommandBar), "SendLiteralCtrlK" => return Ok(Action::SendLiteralCtrlK), "SendLiteralLeader" => return Ok(Action::SendLiteralLeader), @@ -192,6 +197,16 @@ mod tests { assert_eq!(Action::parse(" Noop ").unwrap(), Action::Noop); } + /// #169: resume is its own verb, distinct from rename/kill, so a dormant + /// session's sidebar card has a keyboard-reachable resume. + #[test] + fn parses_resume_highlighted() { + assert_eq!( + Action::parse("ResumeHighlighted").unwrap(), + Action::ResumeHighlighted + ); + } + #[test] fn help_labels_are_plain_language() { assert_eq!(Action::EnterInsert.help_label(), "Send keys to session"); diff --git a/crates/maestro-tui/src/config.rs b/crates/maestro-tui/src/config.rs index 5fd6687..3752e21 100644 --- a/crates/maestro-tui/src/config.rs +++ b/crates/maestro-tui/src/config.rs @@ -59,6 +59,8 @@ const DEFAULT_NORMAL: &[(&str, &str)] = &[ ("N", "FastForwardSpawn"), ("x", "KillHighlighted"), ("r", "RenameHighlighted"), + // Resume a dormant session (#166, #169). + ("u", "ResumeHighlighted"), // Focus ("ctrl-w h", "FocusSidebar"), // Session scrollback @@ -257,6 +259,10 @@ mod tests { cfg.keymap.resolve(Mode::Normal, "N"), Some(Action::FastForwardSpawn), ); + assert_eq!( + cfg.keymap.resolve(Mode::Normal, "u"), + Some(Action::ResumeHighlighted), + ); } /// AoE-style defaults (#shortcuts): `q` and `tab` are bare because Normal