Compare commits

..
2 Commits
Author SHA1 Message Date
Miguel Palhas cd5bbcfcb1 Merge remote-tracking branch 'origin/main' into timbre-2
ci / web (push) Failing after 1m7s
ci / flake (push) Successful in 3m55s
ci / rust (push) Has been cancelled
2026-09-12 10:59:49 +01:00
Miguel PalhasandClaude Opus 4.6 cbbf526fc4 fix(maestro): persist auto_approve across daemon restarts
Structured sessions spawned with auto_approve lost the flag on daemon
restart because it lived only in the in-memory SessionRecord. Add it to
the Session struct (sidecar JSON) so recovery reads it back instead of
hardcoding false.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-09-12 10:59:44 +01:00
10 changed files with 27 additions and 11 deletions
+1
View File
@@ -345,6 +345,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}
}
+6 -11
View File
@@ -1404,6 +1404,7 @@ impl SessionManager {
last_focused_at: None,
acp_session_id: None,
view: req.view,
auto_approve: req.auto_approve,
};
if let Err(e) = self.inner.sidecar.write(&session) {
@@ -1678,6 +1679,7 @@ impl SessionManager {
// Unknown on adoption without a sidecar; matches every
// other field's forward-compat default.
view: maestro_protocol::SessionView::Terminal,
auto_approve: false,
},
};
let fifo_path = self.inner.fifo_dir.join(format!("{id}.fifo"));
@@ -1715,7 +1717,7 @@ impl SessionManager {
acp_events: None,
structured_activity_watermark: None,
acp_event_pump: None,
auto_approve: false,
auto_approve: session.auto_approve,
pending_prompt: None,
},
);
@@ -1894,15 +1896,13 @@ impl SessionManager {
acp_events,
structured_activity_watermark: None,
acp_event_pump: None,
// Recovery cannot know the original preset's flag; the
// operator re-enables it by restarting with the preset.
auto_approve: false,
auto_approve: session.auto_approve,
pending_prompt: None,
},
);
if let Some((acp, events)) = event_pump {
let done = start_acp_event_pump(&self.inner, id, &acp, events.clone());
start_acp_permission_pump(&self.inner, id, &acp, events, false);
start_acp_permission_pump(&self.inner, id, &acp, events, session.auto_approve);
if let Some(record) = self.inner.sessions.lock().await.get_mut(&id) {
record.acp_event_pump = Some(done);
}
@@ -3023,12 +3023,7 @@ async fn settle_worktree_on_exit(
/// gone from the authoritative in-memory map, so a stray FIFO or sidecar can't
/// resurrect it. `kill_tmux` is `true` for an explicit kill and `false` for the
/// liveness reaper (tmux already lost the session).
async fn cleanup_record(
inner: &Arc<Inner>,
id: Uuid,
record: SessionRecord,
kill_tmux: bool,
) {
async fn cleanup_record(inner: &Arc<Inner>, id: Uuid, record: SessionRecord, kill_tmux: bool) {
if kill_tmux {
let tmux = inner.tmux.clone();
let _ = tmux_blocking(move || tmux.kill_session(id)).await;
+1
View File
@@ -34,6 +34,7 @@ fn sample(id: Uuid) -> Session {
last_focused_at: None,
acp_session_id: None,
view: SessionView::Terminal,
auto_approve: false,
}
}
+1
View File
@@ -744,6 +744,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}
}
+7
View File
@@ -132,6 +132,12 @@ pub struct Session {
/// the two are minted by different processes with different lifetimes.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub acp_session_id: Option<String>,
/// When `true` the daemon auto-approves every ACP permission request for
/// this session instead of surfacing them to the client. Set at spawn time
/// from the preset or the `CreateSessionRequest`; persisted so it survives
/// a daemon restart.
#[serde(default)]
pub auto_approve: bool,
}
/// A session's process shape (#133): a plain terminal, or a structured agent
@@ -1054,6 +1060,7 @@ mod tests {
last_focused_at: None,
view: SessionView::Terminal,
acp_session_id: None,
auto_approve: false,
}
}
+1
View File
@@ -50,6 +50,7 @@ pub fn session(id: Uuid, cwd: &str, command: &str) -> Session {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}
}
+6
View File
@@ -2216,6 +2216,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
})
.collect(),
});
@@ -2399,6 +2400,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}],
});
app.dispatch(Action::SidebarNext);
@@ -2428,6 +2430,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}
}
@@ -3590,6 +3593,7 @@ mod tests {
blocked_work: None,
last_focused_at: focused,
acp_session_id: None,
auto_approve: false,
};
let older = Utc::now() - chrono::Duration::seconds(60);
app.catalog.apply_event(CatalogEvent::Snapshot {
@@ -3870,6 +3874,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}],
});
app.dispatch(Action::KillHighlighted);
@@ -3907,6 +3912,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}],
});
}
+1
View File
@@ -225,6 +225,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}
}
+1
View File
@@ -811,6 +811,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}
}
+2
View File
@@ -502,6 +502,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
}
}
@@ -1272,6 +1273,7 @@ mod tests {
blocked_work: None,
last_focused_at: None,
acp_session_id: None,
auto_approve: false,
};
let sb = Sidebar {
sessions: vec![s],