aboutsummaryrefslogtreecommitdiff
path: root/src/client/shell/aggregate_navigation.rs
diff options
context:
space:
mode:
authorakbash <akbash@herdr.dev>2026-09-14 01:11:07 +0300
committerGitHub <noreply@github.com>2026-09-14 02:11:07 +0400
commit3b478e69dcbe8e416ddd46f0e24156392d4e71da (patch)
treee4ae8adfc09dce6eb09bd036fb727cc7f19edff7 /src/client/shell/aggregate_navigation.rs
parent4e1096b1018496a23fb9de7f9ae737d3792bb38b (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.rs211
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 {