diff options
| author | akbash <akbash@herdr.dev> | 2026-09-14 01:11:07 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-14 02:11:07 +0400 |
| commit | 3b478e69dcbe8e416ddd46f0e24156392d4e71da (patch) | |
| tree | e4ae8adfc09dce6eb09bd036fb727cc7f19edff7 /src/client/shell/aggregate_navigation.rs | |
| parent | 4e1096b1018496a23fb9de7f9ae737d3792bb38b (diff) | |
fix: scope agent views to the selected machine (#3784)
* fix: scope agent views to the selected machine
refs #3732
* fix: preserve stable agent view ordering
refs #3732
---------
Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
Co-authored-by: JJ Liebig <jonathan.liebig@gmail.com>
Diffstat (limited to 'src/client/shell/aggregate_navigation.rs')
| -rw-r--r-- | src/client/shell/aggregate_navigation.rs | 211 |
1 files changed, 194 insertions, 17 deletions
diff --git a/src/client/shell/aggregate_navigation.rs b/src/client/shell/aggregate_navigation.rs index 5c826bf3..c0195f93 100644 --- a/src/client/shell/aggregate_navigation.rs +++ b/src/client/shell/aggregate_navigation.rs @@ -5,11 +5,13 @@ use crate::protocol::ClientShellAgent; #[derive(Clone, Copy)] pub(super) struct CachedEndpointSnapshot<'a> { + pub(super) endpoint_index: usize, pub(super) endpoint_id: &'a ClientEndpointId, pub(super) label: &'a str, pub(super) status: ClientEndpointStatus, pub(super) snapshot: &'a ClientShellSnapshot, pub(super) agent_recency: &'a HashMap<String, u64>, + pub(super) agent_presentation: &'a super::endpoint_agent_state::EndpointAgentPresentation, } impl CachedEndpointSnapshot<'_> { @@ -21,18 +23,23 @@ impl CachedEndpointSnapshot<'_> { pub(super) fn cached_endpoint_snapshots( endpoints: &[ClientShellEndpoint], ) -> impl Iterator<Item = CachedEndpointSnapshot<'_>> { - endpoints.iter().filter_map(|endpoint| { - endpoint - .snapshot - .as_deref() - .map(|snapshot| CachedEndpointSnapshot { - endpoint_id: &endpoint.endpoint_id, - label: &endpoint.label, - status: endpoint.status, - snapshot, - agent_recency: &endpoint.agent_recency, - }) - }) + endpoints + .iter() + .enumerate() + .filter_map(|(endpoint_index, endpoint)| { + endpoint + .snapshot + .as_deref() + .map(|snapshot| CachedEndpointSnapshot { + endpoint_index, + endpoint_id: &endpoint.endpoint_id, + label: &endpoint.label, + status: endpoint.status, + snapshot, + agent_recency: &endpoint.agent_recency, + agent_presentation: &endpoint.agent_presentation, + }) + }) } pub(super) struct AggregateAgentRow<'a> { @@ -46,10 +53,83 @@ pub(super) struct AggregateAgentTarget { pub(super) pane_id: String, } -pub(super) fn aggregate_agent_rows( - endpoints: &[ClientShellEndpoint], +pub(super) fn aggregate_agent_rows<'a>( + endpoints: &'a [ClientShellEndpoint], + active_endpoint_id: &ClientEndpointId, sort: crate::config::AgentPanelSortConfig, -) -> Vec<AggregateAgentRow<'_>> { +) -> Vec<AggregateAgentRow<'a>> { + let active_index = endpoints + .iter() + .position(|endpoint| &endpoint.endpoint_id == active_endpoint_id); + let active_view = active_index.and_then(|index| { + let endpoint = &endpoints[index]; + if !endpoint.agent_view_projection_supported { + return None; + } + match ClientShellState::endpoint_agent_view(endpoint) { + Some(Ok(view)) => Some(Ok(view.as_ref())), + Some(Err(())) => Some(Err(())), + None if endpoint + .snapshot + .as_deref() + .is_some_and(|snapshot| snapshot.agent_view_label.is_none()) => + { + Some(Ok(None)) + } + None => Some(Err(())), + } + }); + + if let Some(Ok(view)) = active_view { + let mut rows = cached_endpoint_snapshots(endpoints) + .flat_map(|endpoint| { + endpoint + .snapshot + .agents + .iter() + .map(move |agent| AggregateAgentRow { + recency: endpoint + .agent_recency + .get(&agent.pane_id) + .copied() + .unwrap_or_default(), + endpoint, + agent, + }) + }) + .collect::<Vec<_>>(); + if let Some(view) = view { + let context = active_index + .and_then(|index| endpoints[index].snapshot.as_deref()) + .map(|snapshot| crate::agent_view_eval::AgentViewContext { + scope: active_index.unwrap_or_default(), + workspace_id: snapshot.focused_workspace_id.clone(), + tab_id: snapshot.focused_tab_id.clone(), + }); + if let (Some(context), Some(filter)) = (context.as_ref(), view.filter.as_ref()) { + rows.retain(|row| { + crate::agent_view_eval::matches_filter( + context, + &ClientAgentViewEntry::new(row), + filter, + ) + }); + } + if !view.sort.is_empty() { + rows.sort_by(|left, right| { + crate::agent_view_eval::compare_entries( + &ClientAgentViewEntry::new(left), + &ClientAgentViewEntry::new(right), + &view.sort, + ) + }); + return rows; + } + } + sort_aggregate_rows(&mut rows, sort); + return rows; + } + let mut rows = cached_endpoint_snapshots(endpoints) .flat_map(|endpoint| { super::agent_sidebar::ordered_agent_pane_ids(endpoint.snapshot, sort) @@ -72,6 +152,14 @@ pub(super) fn aggregate_agent_rows( }) }) .collect::<Vec<_>>(); + sort_aggregate_rows(&mut rows, sort); + rows +} + +fn sort_aggregate_rows( + rows: &mut [AggregateAgentRow<'_>], + sort: crate::config::AgentPanelSortConfig, +) { if sort == crate::config::AgentPanelSortConfig::Priority { rows.sort_by_key(|row| { ( @@ -81,14 +169,103 @@ pub(super) fn aggregate_agent_rows( ) }); } - rows +} + +struct ClientAgentViewEntry<'a> { + endpoint_index: usize, + snapshot: &'a ClientShellSnapshot, + agent: &'a ClientShellAgent, + seen: bool, +} + +impl<'a> ClientAgentViewEntry<'a> { + fn new(row: &AggregateAgentRow<'a>) -> Self { + Self { + endpoint_index: row.endpoint.endpoint_index, + snapshot: row.endpoint.snapshot, + agent: row.agent, + seen: row.endpoint.agent_presentation.seen(row.agent), + } + } +} + +impl crate::agent_view_eval::AgentViewEntry for ClientAgentViewEntry<'_> { + fn scope(&self) -> usize { + self.endpoint_index + } + + fn status(&self) -> &'static str { + status_text(self.agent.agent_status) + } + + fn workspace_id(&self) -> Option<std::borrow::Cow<'_, str>> { + Some(std::borrow::Cow::Borrowed(&self.agent.workspace_id)) + } + + fn tab_id(&self) -> Option<std::borrow::Cow<'_, str>> { + Some(std::borrow::Cow::Borrowed(&self.agent.tab_id)) + } + + fn pane_id(&self) -> Option<std::borrow::Cow<'_, str>> { + Some(std::borrow::Cow::Borrowed(&self.agent.pane_id)) + } + + fn agent(&self) -> Option<&str> { + self.agent.agent.as_deref() + } + + fn seen(&self) -> bool { + self.seen + } + + fn state_change_seq(&self) -> Option<u64> { + Some(self.agent.state_change_seq) + } + + fn token(&self, token: &str) -> Option<&str> { + self.agent + .tokens + .iter() + .find(|(name, _)| name == token) + .map(|(_, value)| value.as_str()) + } + + fn workspace_order(&self) -> Option<u64> { + self.snapshot + .workspaces + .iter() + .position(|workspace| workspace.workspace_id == self.agent.workspace_id) + .map(|index| index as u64) + } + + fn tab_order(&self) -> Option<u64> { + self.snapshot + .tabs + .iter() + .find(|tab| tab.tab_id == self.agent.tab_id) + .map(|tab| tab.number as u64) + } + + fn pane_order(&self) -> Option<u64> { + let suffix = self + .agent + .pane_id + .strip_prefix(&self.agent.workspace_id)? + .strip_prefix(":p")?; + crate::workspace::decode_public_number(suffix).map(|number| number as u64) + } + + fn attention(&self) -> u64 { + u64::from(status_priority(self.agent.agent_status)) + } } pub(super) fn online_agent_targets( endpoints: &[ClientShellEndpoint], + active_endpoint_id: &ClientEndpointId, sort: crate::config::AgentPanelSortConfig, ) -> Vec<AggregateAgentTarget> { - aggregate_agent_rows(endpoints, sort) + aggregate_agent_rows(endpoints, active_endpoint_id, sort) .into_iter() .filter(|row| !row.endpoint.stale()) .map(|row| AggregateAgentTarget { |
