From f7ba6bcf6a5fe0d457c3b8a860a3df0dd59cf8c8 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 2 Apr 2026 13:37:21 +1100 Subject: [PATCH 1/2] fix: restore resume session spinner metadata --- apps/staged/src-tauri/src/session_commands.rs | 109 +++++++++++++++--- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/apps/staged/src-tauri/src/session_commands.rs b/apps/staged/src-tauri/src/session_commands.rs index d66dfe12..89cb59ff 100644 --- a/apps/staged/src-tauri/src/session_commands.rs +++ b/apps/staged/src-tauri/src/session_commands.rs @@ -207,11 +207,14 @@ pub async fn resume_session( // Check if this session is linked to a project note — if so, we need // to start the MCP server so the agent has access to project tools. - let mcp_project_id = store + let project_note = store .get_project_note_by_session(&session_id) .ok() - .flatten() - .map(|note| note.project_id); + .flatten(); + let mcp_project_id = project_note.as_ref().map(|note| note.project_id.clone()); + let linked_commit = store.get_commit_by_session(&session_id).ok().flatten(); + let linked_note = store.get_note_by_session(&session_id).ok().flatten(); + let linked_review = store.get_review_by_session(&session_id).ok().flatten(); // If a branch_id is provided, look up the branch to get workspace_name // and resolve remote_working_dir. This takes priority over the @@ -220,25 +223,45 @@ pub async fn resume_session( .as_deref() .and_then(|bid| store.get_branch(bid).ok().flatten()); + let linked_branch = if branch_from_id.is_some() { + branch_from_id.clone() + } else if let Some(commit) = &linked_commit { + store.get_branch(&commit.branch_id).ok().flatten() + } else if let Some(note) = &linked_note { + store.get_branch(¬e.branch_id).ok().flatten() + } else if let Some(review) = &linked_review { + store.get_branch(&review.branch_id).ok().flatten() + } else { + None + }; + + let session_type = if project_note.is_some() { + Some("note".to_string()) + } else if linked_commit.is_some() { + Some("commit".to_string()) + } else if linked_note.is_some() { + Some("note".to_string()) + } else if linked_review.is_some() { + Some("review".to_string()) + } else { + infer_branch_resume_session_type(&session.prompt).map(str::to_string) + }; + let event_branch_id = linked_branch.as_ref().map(|branch| branch.id.clone()); + let event_project_id = if let Some(note) = &project_note { + Some(note.project_id.clone()) + } else { + linked_branch + .as_ref() + .map(|branch| branch.project_id.clone()) + }; + // If this session is linked to a commit, capture the current HEAD so we // can detect new or amended commits when the session completes. // This applies both when the commit already has a SHA (amend case) and // when it's still pending (no SHA — the previous run didn't produce a // commit, so we need to detect if this resumed run does). let (pre_head_sha, workspace_name) = { - // Determine the branch to use: explicit branch_id takes priority, - // otherwise fall back to the commit-linked branch. - let branch = if branch_from_id.is_some() { - branch_from_id.clone() - } else { - store - .get_commit_by_session(&session_id) - .ok() - .flatten() - .and_then(|commit| store.get_branch(&commit.branch_id).ok().flatten()) - }; - - if let Some(ref branch) = branch { + if let Some(ref branch) = linked_branch { let ws_name = branch.workspace_name.clone(); let head = if let Some(ref ws) = ws_name { let ws = ws.clone(); @@ -299,9 +322,9 @@ pub async fn resume_session( status: "running".to_string(), error_message: None, completion_reason: None, - branch_id: None, - project_id: mcp_project_id.clone(), - session_type: None, + branch_id: event_branch_id, + project_id: event_project_id.or(mcp_project_id.clone()), + session_type, is_auto_review: false, }, ); @@ -338,6 +361,20 @@ pub async fn resume_session( Ok(()) } +fn infer_branch_resume_session_type(prompt: &str) -> Option<&'static str> { + if prompt.contains("Create a draft pull request for the current branch.") + || prompt.contains("Create a pull request for the current branch.") + { + Some("pr") + } else if prompt.contains("Push the current branch to the remote using force-with-lease.") + || prompt.contains("Push the current branch to the remote.") + { + Some("push") + } else { + None + } +} + #[tauri::command] pub fn cancel_session( registry: tauri::State<'_, Arc>, @@ -2313,6 +2350,40 @@ Rules: mod tests { use super::*; + #[test] + fn infer_branch_resume_session_type_detects_pr_prompts() { + assert_eq!( + infer_branch_resume_session_type("Create a pull request for the current branch."), + Some("pr") + ); + assert_eq!( + infer_branch_resume_session_type("Create a draft pull request for the current branch."), + Some("pr") + ); + } + + #[test] + fn infer_branch_resume_session_type_detects_push_prompts() { + assert_eq!( + infer_branch_resume_session_type("Push the current branch to the remote."), + Some("push") + ); + assert_eq!( + infer_branch_resume_session_type( + "Push the current branch to the remote using force-with-lease." + ), + Some("push") + ); + } + + #[test] + fn infer_branch_resume_session_type_ignores_other_prompts() { + assert_eq!( + infer_branch_resume_session_type("Write a project note."), + None + ); + } + #[test] fn review_prompt_requires_strict_fence_lines() { let prompt = build_full_prompt( From 5cc1e8a5ce771af245e2bf98a0b5d38aa2ce746e Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 2 Apr 2026 16:19:18 +1100 Subject: [PATCH 2/2] fix: address resume session review follow-up --- apps/staged/src-tauri/src/session_commands.rs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/staged/src-tauri/src/session_commands.rs b/apps/staged/src-tauri/src/session_commands.rs index 89cb59ff..5c70b2f7 100644 --- a/apps/staged/src-tauri/src/session_commands.rs +++ b/apps/staged/src-tauri/src/session_commands.rs @@ -236,6 +236,9 @@ pub async fn resume_session( }; let session_type = if project_note.is_some() { + // Project notes and branch notes intentionally share the "note" + // session type because the frontend only needs a single "note work is + // running" signal for project-level activity indicators. Some("note".to_string()) } else if linked_commit.is_some() { Some("commit".to_string()) @@ -255,22 +258,26 @@ pub async fn resume_session( .map(|branch| branch.project_id.clone()) }; - // If this session is linked to a commit, capture the current HEAD so we - // can detect new or amended commits when the session completes. - // This applies both when the commit already has a SHA (amend case) and - // when it's still pending (no SHA — the previous run didn't produce a - // commit, so we need to detect if this resumed run does). + // Only resumed commit sessions need a pre-run HEAD snapshot. The + // completion hook ignores non-commit sessions anyway, but keeping this + // narrow makes the intent explicit and avoids unnecessary git lookups. let (pre_head_sha, workspace_name) = { if let Some(ref branch) = linked_branch { let ws_name = branch.workspace_name.clone(); - let head = if let Some(ref ws) = ws_name { - let ws = ws.clone(); - run_blox_blocking(move || crate::blox::ws_exec(&ws, &["git", "rev-parse", "HEAD"])) + let head = if linked_commit.is_some() { + if let Some(ref ws) = ws_name { + let ws = ws.clone(); + run_blox_blocking(move || { + crate::blox::ws_exec(&ws, &["git", "rev-parse", "HEAD"]) + }) .await .map(|s| s.trim().to_string()) .ok() + } else { + crate::git::get_head_sha(&working_dir).ok() + } } else { - crate::git::get_head_sha(&working_dir).ok() + None }; (head, ws_name) } else { @@ -362,6 +369,7 @@ pub async fn resume_session( } fn infer_branch_resume_session_type(prompt: &str) -> Option<&'static str> { + // Keep these checks aligned with the action prompts built in `prs.rs`. if prompt.contains("Create a draft pull request for the current branch.") || prompt.contains("Create a pull request for the current branch.") {