Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions apps/mark/src-tauri/src/session_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,9 +180,12 @@ pub fn start_session(
/// The working directory is read from the session record (set when the
/// session was first created), so the frontend doesn't need to pass it.
#[tauri::command]
#[allow(clippy::too_many_arguments)]
pub fn resume_session(
store: tauri::State<'_, Mutex<Option<Arc<Store>>>>,
registry: tauri::State<'_, Arc<session_runner::SessionRegistry>>,
action_executor: tauri::State<'_, Arc<ActionExecutor>>,
action_registry: tauri::State<'_, Arc<ActionRegistry>>,
app_handle: tauri::AppHandle,
session_id: String,
prompt: String,
Expand All @@ -201,6 +204,14 @@ pub fn resume_session(
let agent_session_id = session.agent_id.clone();
let working_dir = PathBuf::from(&session.working_dir);

// 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
.get_project_note_by_session(&session_id)
.ok()
.flatten()
.map(|note| note.project_id);

let transitioned = store
.transition_to_running(&session_id)
.map_err(|e| e.to_string())?;
Expand All @@ -215,7 +226,7 @@ pub fn resume_session(
status: "running".to_string(),
error_message: None,
branch_id: None,
project_id: None,
project_id: mcp_project_id.clone(),
session_type: None,
is_auto_review: false,
},
Expand All @@ -231,9 +242,17 @@ pub fn resume_session(
provider,
workspace_name: None,
extra_env: vec![],
mcp_project_id: None,
action_executor: None,
action_registry: None,
mcp_project_id: mcp_project_id.clone(),
action_executor: if mcp_project_id.is_some() {
Some(Arc::clone(&action_executor))
} else {
None
},
action_registry: if mcp_project_id.is_some() {
Some(Arc::clone(&action_registry))
} else {
None
},
remote_working_dir: None,
image_ids: image_ids.unwrap_or_default(),
},
Expand Down
48 changes: 33 additions & 15 deletions apps/mark/src/lib/features/projects/ProjectSection.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -396,24 +396,42 @@
loadProjectNotes();

let unlistenSession: (() => void) | undefined;
listen<{ sessionId: string; status: string }>('session-status-changed', (event) => {
const { sessionId, status } = event.payload;
const isTracked = activeSessionIds.has(sessionId);
// Also reload if this session belongs to a known project note (handles
// sessions that were already running when the component mounted).
const isKnownNoteSession = projectNotes.some((n) => n.sessionId === sessionId);
if (isTracked || isKnownNoteSession) {
if (status === 'completed' || status === 'error' || status === 'cancelled') {
if (isTracked) {
const next = new Set(activeSessionIds);
next.delete(sessionId);
activeSessionIds = next;
listen<{ sessionId: string; status: string; projectId?: string }>(
'session-status-changed',
(event) => {
const { sessionId, status, projectId } = event.payload;
const isTracked = activeSessionIds.has(sessionId);
// Also reload if this session belongs to a known project note (handles
// sessions that were already running when the component mounted).
const isKnownNoteSession = projectNotes.some((n) => n.sessionId === sessionId);

// Handle resumed sessions: when a project note session is resumed,
// the backend emits a "running" event with the projectId. Re-add it
// to active tracking so the row spinner and sidebar spinner appear.
if (
status === 'running' &&
!isTracked &&
isKnownNoteSession &&
(projectId === project.id || !projectId)
) {
activeSessionIds = new Set([...activeSessionIds, sessionId]);
sessionRegistry.register(sessionId, project.id, 'note');
projectStateStore.addRunningSession(project.id, sessionId);
}

if (isTracked || isKnownNoteSession) {
if (status === 'completed' || status === 'error' || status === 'cancelled') {
if (isTracked) {
const next = new Set(activeSessionIds);
next.delete(sessionId);
activeSessionIds = next;
}
// Refresh notes after session completes
loadProjectNotes();
}
// Refresh notes after session completes
loadProjectNotes();
}
}
}).then((unlisten) => {
).then((unlisten) => {
unlistenSession = unlisten;
});

Expand Down
10 changes: 5 additions & 5 deletions crates/acp-client/src/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ impl AcpDriver {
self
}

/// Set MCP servers to inject into the session via `NewSessionRequest`.
/// Set MCP servers to inject into the session via `NewSessionRequest` or `LoadSessionRequest`.
pub fn with_mcp_servers(mut self, servers: Vec<McpServer>) -> Self {
self.mcp_servers = servers;
self
Expand Down Expand Up @@ -758,10 +758,10 @@ async fn setup_acp_session(
);

connection
.load_session(LoadSessionRequest::new(
existing_id.to_string(),
working_dir.to_path_buf(),
))
.load_session(
LoadSessionRequest::new(existing_id.to_string(), working_dir.to_path_buf())
.mcp_servers(mcp_servers.to_vec()),
)
.await
.map_err(|e| format!("Failed to load ACP session: {e:?}"))?;

Expand Down
Loading