diff --git a/src-tauri/crates/agent-core/src/core/session/persistence/crud/record.rs b/src-tauri/crates/agent-core/src/core/session/persistence/crud/record.rs index 07c886052..ff9e040e9 100644 --- a/src-tauri/crates/agent-core/src/core/session/persistence/crud/record.rs +++ b/src-tauri/crates/agent-core/src/core/session/persistence/crud/record.rs @@ -84,9 +84,10 @@ pub struct UnifiedSessionRecord { pub merge_status: Option, pub project_slug: Option, pub agent_definition_id: Option, - /// Agent Org roster member id for `session_type::ORG_MEMBER` rows. - /// This identifies the member instance, while `agent_definition_id` - /// identifies which AgentDefinition that member runs. + /// Agent Org roster member id. Worker rows use `session_type::ORG_MEMBER`; + /// a coordinator root keeps its primary session type and uses the + /// coordinator member id. `agent_definition_id` identifies which + /// AgentDefinition that member runs. pub org_member_id: Option, pub parent_session_id: Option, diff --git a/src-tauri/crates/agent-core/src/core/session/persistence/sidebar.rs b/src-tauri/crates/agent-core/src/core/session/persistence/sidebar.rs index 20c5d3422..259ca0c7b 100644 --- a/src-tauri/crates/agent-core/src/core/session/persistence/sidebar.rs +++ b/src-tauri/crates/agent-core/src/core/session/persistence/sidebar.rs @@ -16,10 +16,11 @@ use database::db::get_connection; pub type NativeSessionPageCursor<'a> = (&'a str, &'a str); /// Return one bounded sidebar page of unpinned coding sessions that are not -/// roots of any persisted Agent Org run. +/// members or roots of any persisted Agent Org run. /// -/// Pin state and root membership are applied before LIMIT, so pinned rows, -/// Agent Org roots, and worker rows cannot consume standalone page capacity. +/// Pin state and Agent Org membership are applied before LIMIT, so pinned +/// rows, roots, workers, and orphaned member rows cannot consume standalone +/// page capacity. pub fn list_standalone_coding_sessions_page( limit: usize, cursor: Option>, @@ -53,6 +54,10 @@ fn list_agent_sessions_page( agent_org_root: Option, ) -> Result, String> { let conn = get_connection().map_err(|err| err.to_string())?; + let member_predicate = match agent_org_root { + Some(false) => "AND s.org_member_id IS NULL", + Some(true) | None => "", + }; let root_predicate = match agent_org_root { Some(true) => { "AND EXISTS ( @@ -79,6 +84,7 @@ fn list_agent_sessions_page( AND s.status != ?2 AND s.pinned = 0 AND s.parent_session_id IS NULL + {member_predicate} {root_predicate} AND ( s.updated_at < ?3 @@ -111,6 +117,7 @@ fn list_agent_sessions_page( AND s.status != ?2 AND s.pinned = 0 AND s.parent_session_id IS NULL + {member_predicate} {root_predicate} ORDER BY s.updated_at DESC, s.session_id DESC LIMIT ?3" @@ -163,6 +170,26 @@ mod tests { type_name: &str, parent_session_id: Option<&str>, pinned: bool, + ) { + upsert_sidebar_session_with_pin_and_member( + session_id, + updated_at, + status, + type_name, + parent_session_id, + pinned, + None, + ); + } + + fn upsert_sidebar_session_with_pin_and_member( + session_id: &str, + updated_at: &str, + status: &str, + type_name: &str, + parent_session_id: Option<&str>, + pinned: bool, + org_member_id: Option<&str>, ) { ensure_runtime_schemas(); super::super::upsert_session(&UnifiedSessionRecord { @@ -174,6 +201,7 @@ mod tests { created_at: updated_at.to_string(), updated_at: updated_at.to_string(), pinned, + org_member_id: org_member_id.map(str::to_string), ..Default::default() }) .expect("upsert sidebar session"); @@ -351,6 +379,75 @@ mod tests { assert!(second.iter().all(|session| !session.pinned)); } + #[test] + fn orphan_agent_org_members_do_not_consume_standalone_page_capacity() { + let _sandbox = test_helpers::test_env::sandbox(); + let coordinator_member_id = crate::coordination::agent_org_runs::COORDINATOR_MEMBER_ID; + + for index in 0..8 { + upsert_sidebar_session_with_pin_and_member( + &format!("orphan-coordinator-{index:02}"), + &format!("2026-07-29T13:{index:02}:00Z"), + "idle", + session_type::CODING, + None, + false, + Some(coordinator_member_id), + ); + } + upsert_sidebar_session_with_pin_and_member( + "valid-agent-org-root", + "2026-07-29T13:08:00Z", + "idle", + session_type::CODING, + None, + false, + Some(coordinator_member_id), + ); + insert_agent_org_run( + "run-valid-agent-org-root", + "valid-agent-org-root", + "2026-07-29T13:08:00Z", + ); + for index in 0..11 { + upsert_sidebar_session( + &format!("regular-{index:02}"), + &format!("2026-07-29T12:{index:02}:00Z"), + "idle", + session_type::CODING, + None, + ); + } + + let first = list_standalone_coding_sessions_page(10, None).expect("first page"); + assert_eq!(first.len(), 10); + assert!(first.iter().all(|session| session.org_member_id.is_none())); + assert_eq!( + first.first().map(|session| session.session_id.as_str()), + Some("regular-10") + ); + assert_eq!( + first.last().map(|session| session.session_id.as_str()), + Some("regular-01") + ); + let cursor = first.last().expect("first page cursor"); + let second = list_standalone_coding_sessions_page( + 10, + Some((&cursor.updated_at, &cursor.session_id)), + ) + .expect("second page"); + assert_eq!(second.len(), 1); + assert_eq!(second[0].session_id, "regular-00"); + + let roots = list_agent_org_root_sessions_page(10, None).expect("Agent Org root page"); + assert_eq!(roots.len(), 1); + assert_eq!(roots[0].session_id, "valid-agent-org-root"); + assert_eq!( + roots[0].org_member_id.as_deref(), + Some(coordinator_member_id) + ); + } + #[test] fn native_sidebar_query_uses_bounded_order_and_membership_indexes() { let _sandbox = test_helpers::test_env::sandbox(); @@ -365,6 +462,7 @@ mod tests { AND s.session_type = 'sde' AND s.status != 'archived' AND s.parent_session_id IS NULL + AND s.org_member_id IS NULL AND NOT EXISTS ( SELECT 1 FROM agent_org_runs r diff --git a/src-tauri/src/agent_sessions/session_directory/aggregation.rs b/src-tauri/src/agent_sessions/session_directory/aggregation.rs index 4aa38a18a..1d865a6d1 100644 --- a/src-tauri/src/agent_sessions/session_directory/aggregation.rs +++ b/src-tauri/src/agent_sessions/session_directory/aggregation.rs @@ -1273,6 +1273,14 @@ fn list_pinned_native_sidebar_sessions( AND s.status != ?1 AND s.parent_session_id IS NULL AND s.session_type IN (?2, ?3, ?4) + AND ( + s.org_member_id IS NULL + OR EXISTS ( + SELECT 1 + FROM agent_org_runs r + WHERE r.root_session_id = s.session_id + ) + ) {agent_cursor} UNION ALL SELECT c.session_id, c.updated_at, 'cli' AS source_kind @@ -1654,11 +1662,31 @@ mod tests { } #[test] - fn pinned_native_page_merges_agent_and_cli_roots_in_stable_order() { + fn pinned_native_page_merges_roots_without_orphan_member_capacity() { let _sandbox = crate::test_utils::test_env::sandbox(); let conn = get_connection().expect("sandbox database"); + agent_core::coordination::agent_org_runs::init_schema(&conn).expect("Agent Org run schema"); + let coordinator_member_id = agent_core::coordination::agent_org_runs::COORDINATOR_MEMBER_ID; - for (session_id, session_type, updated_at, pinned, parent, status) in [ + for (session_id, session_type, updated_at, pinned, parent, status, member) in [ + ( + "orphan-coordinator-pinned", + session_type::CODING, + "2026-07-30T18:00:00Z", + true, + None, + "idle", + Some(coordinator_member_id), + ), + ( + "agent-org-root-pinned", + session_type::CODING, + "2026-07-30T17:00:00Z", + true, + None, + "idle", + Some(coordinator_member_id), + ), ( "sdeagent-pinned", session_type::CODING, @@ -1666,6 +1694,7 @@ mod tests { true, None, "idle", + None, ), ( "osagent-pinned", @@ -1674,6 +1703,7 @@ mod tests { true, None, "idle", + None, ), ( "humansession-pinned", @@ -1682,6 +1712,7 @@ mod tests { true, None, "completed", + None, ), ( "sdeagent-unpinned", @@ -1690,6 +1721,7 @@ mod tests { false, None, "idle", + None, ), ( "sdeagent-worker", @@ -1698,6 +1730,7 @@ mod tests { true, Some("sdeagent-pinned"), "running", + None, ), ( "sdeagent-archived", @@ -1706,6 +1739,7 @@ mod tests { true, None, "archived", + None, ), ] { session_persistence::upsert_session(&UnifiedSessionRecord { @@ -1714,6 +1748,7 @@ mod tests { status: status.to_string(), session_type: session_type.to_string(), parent_session_id: parent.map(str::to_string), + org_member_id: member.map(str::to_string), created_at: updated_at.to_string(), updated_at: updated_at.to_string(), pinned, @@ -1721,6 +1756,22 @@ mod tests { }) .expect("seed native session"); } + conn.execute( + "INSERT INTO agent_org_runs ( + id, org_id, coordinator_agent_id, root_session_id, + entry_mode, status, created_at, updated_at + ) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?7)", + params![ + "run-agent-org-root-pinned", + "org-pinned", + "builtin:sde", + "agent-org-root-pinned", + "standalone_session", + "running", + "2026-07-30T17:00:00Z", + ], + ) + .expect("seed pinned Agent Org run"); for (session_id, updated_at, pinned, parent) in [ ("cliagent-pinned", "2026-07-30T13:00:00Z", true, None), @@ -1742,7 +1793,7 @@ mod tests { .expect("seed CLI session"); } - let page = list_native_sidebar_sessions(NativeSidebarSessionStream::PinnedNative, None, 10) + let page = list_native_sidebar_sessions(NativeSidebarSessionStream::PinnedNative, None, 4) .expect("load global pinned page"); assert_eq!( @@ -1751,14 +1802,67 @@ mod tests { .map(|session| session.session_id.as_str()) .collect::>(), vec![ + "agent-org-root-pinned", "sdeagent-pinned", "cliagent-pinned", "osagent-pinned", - "humansession-pinned", ] ); assert!(page.sessions.iter().all(|session| session.pinned)); - assert!(!page.has_more); + assert!(page.has_more); + let root = page + .sessions + .first() + .expect("valid Agent Org root remains pinned"); + assert_eq!(root.agent_org_id.as_deref(), Some("org-pinned")); + } + + #[test] + fn pinned_native_query_uses_bounded_order_and_root_membership_indexes() { + let _sandbox = crate::test_utils::test_env::sandbox(); + let conn = get_connection().expect("sandbox database"); + agent_core::coordination::agent_org_runs::init_schema(&conn).expect("Agent Org run schema"); + let mut stmt = conn + .prepare( + "EXPLAIN QUERY PLAN + SELECT s.session_id, s.updated_at, 'agent' AS source_kind + FROM agent_sessions s + WHERE s.pinned = 1 + AND s.status != 'archived' + AND s.parent_session_id IS NULL + AND s.session_type IN ('sde', 'os', 'human') + AND ( + s.org_member_id IS NULL + OR EXISTS ( + SELECT 1 + FROM agent_org_runs r + WHERE r.root_session_id = s.session_id + ) + ) + UNION ALL + SELECT c.session_id, c.updated_at, 'cli' AS source_kind + FROM code_sessions c + WHERE c.pinned = 1 + AND c.parent_session_id IS NULL + ORDER BY updated_at DESC, session_id DESC + LIMIT 11", + ) + .expect("prepare pinned native query plan"); + let details = stmt + .query_map([], |row| row.get::<_, String>(3)) + .expect("read pinned native query plan") + .collect::, _>>() + .expect("collect pinned native query plan") + .join("\n"); + + assert!( + details.contains("idx_agent_sessions_sidebar"), + "pinned agent page did not use ordered sidebar index:\n{details}" + ); + assert!( + details.contains("idx_agent_org_runs_root_session"), + "pinned root membership probe did not use root-session index:\n{details}" + ); } #[test]