From 9728b10ffdaf4ed7f11b7f951a57348faab740f9 Mon Sep 17 00:00:00 2001 From: Miguel Palhas Date: Fri, 4 Sep 2026 08:33:08 +0100 Subject: [PATCH] feat(maestro): add preset resume commands --- .../maestro-cli/tests/against_mock_daemon.rs | 1 + crates/maestro-config/src/lib.rs | 43 +++++++++++++++++++ crates/maestro-protocol/src/lib.rs | 3 ++ crates/maestro-tui/src/draft.rs | 4 ++ crates/maestro-tui/src/fast_forward.rs | 1 + 5 files changed, 52 insertions(+) diff --git a/crates/maestro-cli/tests/against_mock_daemon.rs b/crates/maestro-cli/tests/against_mock_daemon.rs index 58066f2..309658c 100644 --- a/crates/maestro-cli/tests/against_mock_daemon.rs +++ b/crates/maestro-cli/tests/against_mock_daemon.rs @@ -28,6 +28,7 @@ async fn info_carries_version_and_presets() { id: "claude".into(), label: "Claude".into(), command: "claude".into(), + resume_command: None, }], ..Default::default() }); diff --git a/crates/maestro-config/src/lib.rs b/crates/maestro-config/src/lib.rs index c4efec0..f7817c7 100644 --- a/crates/maestro-config/src/lib.rs +++ b/crates/maestro-config/src/lib.rs @@ -313,16 +313,19 @@ pub fn builtin_presets() -> Vec { id: "claude".into(), label: "Claude".into(), command: "claude".into(), + resume_command: Some("claude --resume {agent_session}".into()), }, SpawnPreset { id: "opencode".into(), label: "opencode".into(), command: "opencode".into(), + resume_command: None, }, SpawnPreset { id: "zsh".into(), label: "Shell".into(), command: "zsh".into(), + resume_command: None, }, ] } @@ -334,6 +337,7 @@ pub fn custom_preset() -> SpawnPreset { id: CUSTOM_PRESET_ID.into(), label: "Custom…".into(), command: String::new(), + resume_command: None, } } @@ -507,6 +511,18 @@ auto_install = false assert!(custom.command.is_empty()); } + #[test] + fn builtin_claude_preset_resumes_by_agent_session() { + let claude = builtin_presets() + .into_iter() + .find(|preset| preset.id == "claude") + .expect("Claude preset"); + assert_eq!( + claude.resume_command.as_deref(), + Some("claude --resume {agent_session}") + ); + } + #[test] fn spawn_presets_override_replaces_builtins() { // Present list replaces the built-ins entirely; custom still appended. @@ -515,6 +531,7 @@ auto_install = false id: "fish".into(), label: "fish".into(), command: "fish".into(), + resume_command: None, }]), ..Default::default() }; @@ -551,6 +568,7 @@ auto_install = false id: " ".into(), label: "x".into(), command: "x".into(), + resume_command: None, }]), ..Default::default() }; @@ -561,6 +579,7 @@ auto_install = false id: "x".into(), label: "x".into(), command: "".into(), + resume_command: None, }]), ..Default::default() }; @@ -578,11 +597,13 @@ auto_install = false id: "a".into(), label: "a".into(), command: "a".into(), + resume_command: None, }, SpawnPreset { id: "a".into(), label: "a2".into(), command: "a2".into(), + resume_command: None, }, ]), ..Default::default() @@ -597,6 +618,7 @@ auto_install = false id: "custom".into(), label: "Custom".into(), command: "whatever".into(), + resume_command: None, }]), ..Default::default() }; @@ -632,6 +654,27 @@ presets = [ assert_eq!(ids, ["claude", "zsh", "custom"]); } + #[test] + fn spawn_preset_parses_resume_command_from_issue_example() { + let cfg = toml::from_str::( + r#" +[spawn] +presets = [ + { id = "claude", command = "claude --dangerously-skip-permissions", resume_command = "claude --dangerously-skip-permissions --resume {agent_session}" } +] +"#, + ) + .expect("issue #165 example parses"); + + let preset = &cfg.spawn.presets.expect("configured presets")[0]; + assert_eq!(preset.id, "claude"); + assert_eq!(preset.command, "claude --dangerously-skip-permissions"); + assert_eq!( + preset.resume_command.as_deref(), + Some("claude --dangerously-skip-permissions --resume {agent_session}") + ); + } + #[test] fn spawn_project_scan_fields_default_to_none() { // Absent [spawn].project_* → None; the daemon applies its own defaults. diff --git a/crates/maestro-protocol/src/lib.rs b/crates/maestro-protocol/src/lib.rs index 45bcb3f..3e09076 100644 --- a/crates/maestro-protocol/src/lib.rs +++ b/crates/maestro-protocol/src/lib.rs @@ -517,8 +517,11 @@ impl ApiError { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SpawnPreset { pub id: String, + #[serde(default)] pub label: String, pub command: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resume_command: Option, } /// Build provenance of the running daemon: the commit it was compiled diff --git a/crates/maestro-tui/src/draft.rs b/crates/maestro-tui/src/draft.rs index e89e562..f343044 100644 --- a/crates/maestro-tui/src/draft.rs +++ b/crates/maestro-tui/src/draft.rs @@ -1705,6 +1705,7 @@ mod tests { id: "aider".into(), label: "Aider".into(), command: "aider".into(), + resume_command: None, }]); assert_eq!( draft.command(), @@ -1727,11 +1728,13 @@ mod tests { id: "claude".into(), label: "Claude".into(), command: "claude".into(), + resume_command: None, }, SpawnPreset { id: "aider".into(), label: "Aider".into(), command: "aider".into(), + resume_command: None, }, ]); assert_eq!(draft.command(), "aider"); @@ -1751,6 +1754,7 @@ mod tests { id: "claude".into(), label: "Claude".into(), command: "claude".into(), + resume_command: None, }]); assert!(draft.is_custom()); assert_eq!(draft.command(), "bash -l"); diff --git a/crates/maestro-tui/src/fast_forward.rs b/crates/maestro-tui/src/fast_forward.rs index b4b6004..55e8288 100644 --- a/crates/maestro-tui/src/fast_forward.rs +++ b/crates/maestro-tui/src/fast_forward.rs @@ -183,6 +183,7 @@ mod tests { id: id.into(), label: id.into(), command: command.into(), + resume_command: None, } }