From 64b9ec1c4c70c6f9c0ec65c453b4194f3244fa21 Mon Sep 17 00:00:00 2001 From: Shibo Sheng Date: Fri, 14 Aug 2026 16:13:45 +0800 Subject: [PATCH] fix(session): exclude orphan org members from native pages Filter orphan Agent Org member rows before native sidebar LIMITs so they no longer displace standalone or pinned sessions. Preserve valid coordinator roots through persisted Agent Org run membership. Verification: - cargo test -p agent_core core::session::persistence::sidebar::tests -- --nocapture (5 passed) - cargo test -p org2 pinned_native -- --nocapture (2 passed) - cargo check -p agent_core -p org2 --all-targets (passed) - cargo clippy -p agent_core -p org2 --all-targets -- -D warnings (passed) - pnpm run tauri:build:fast (passed); manual packaged-app acceptance passed Pre-commit hook ran. Total eslint: 0, total circular: 0 --- .../core/session/persistence/crud/record.rs | 7 +- .../src/core/session/persistence/sidebar.rs | 104 +++++++++++++++- .../session_directory/aggregation.rs | 114 +++++++++++++++++- 3 files changed, 214 insertions(+), 11 deletions(-) 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 07c8860522..ff9e040e92 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 20c5d3422c..259ca0c7b8 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 4aa38a18a8..1d865a6d13 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]