diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:16 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:16 +0300 |
| commit | 86adf88351e17c6a11cac253714b169eac0db233 (patch) | |
| tree | 883d32b0126e0b69449e0e9c0a7017d279c23935 /src/app/agents.rs | |
| parent | d2aa8865911ded97a67054fb2392a1c12fae018c (diff) | |
feat: add terminal identity and agent cli facade
Diffstat (limited to 'src/app/agents.rs')
| -rw-r--r-- | src/app/agents.rs | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/src/app/agents.rs b/src/app/agents.rs new file mode 100644 index 00000000..88f7ec97 --- /dev/null +++ b/src/app/agents.rs @@ -0,0 +1,213 @@ +use super::{terminal_targets::TerminalTargetError, App, Mode}; + +impl App { + pub(super) fn collect_agent_infos(&self) -> Vec<crate::api::schema::AgentInfo> { + self.state + .workspaces + .iter() + .enumerate() + .flat_map(|(ws_idx, ws)| { + ws.tabs.iter().flat_map(move |tab| { + tab.layout + .pane_ids() + .into_iter() + .filter_map(move |pane_id| self.agent_info(ws_idx, pane_id)) + }) + }) + .filter(|agent| agent.name.is_some() || agent.agent.is_some()) + .collect() + } + + pub(super) fn agent_info_for_target( + &self, + target: &str, + ) -> Result<crate::api::schema::AgentInfo, TerminalTargetError> { + let resolved = self.resolve_terminal_target(target)?; + self.agent_info(resolved.ws_idx, resolved.pane_id) + .ok_or_else(|| TerminalTargetError::NotFound { + target: target.to_string(), + }) + } + + pub(super) fn focus_agent_target( + &mut self, + target: &str, + ) -> Result<crate::api::schema::AgentInfo, TerminalTargetError> { + let resolved = self.resolve_terminal_target(target)?; + self.state.switch_workspace(resolved.ws_idx); + self.state.switch_tab(resolved.tab_idx); + if let Some(tab) = self + .state + .workspaces + .get_mut(resolved.ws_idx) + .and_then(|ws| ws.tabs.get_mut(resolved.tab_idx)) + { + tab.layout.focus_pane(resolved.pane_id); + } + self.state.mode = Mode::Terminal; + self.agent_info(resolved.ws_idx, resolved.pane_id) + .ok_or_else(|| TerminalTargetError::NotFound { + target: target.to_string(), + }) + } + + pub(super) fn rename_agent_target( + &mut self, + target: &str, + name: Option<String>, + ) -> Result<crate::api::schema::AgentInfo, AgentRenameError> { + let resolved = self + .resolve_terminal_target(target) + .map_err(AgentRenameError::Target)?; + let normalized_name = name.and_then(|name| { + let trimmed = name.trim().to_string(); + (!trimmed.is_empty()).then_some(trimmed) + }); + + if let Some(name) = normalized_name.as_deref() { + let conflicts = self.agent_name_conflicts(name, &resolved.terminal_id); + if !conflicts.is_empty() { + return Err(AgentRenameError::DuplicateName { + name: name.to_string(), + candidates: conflicts, + }); + } + } + + let Some(ws) = self.state.workspaces.get_mut(resolved.ws_idx) else { + return Err(AgentRenameError::Target(TerminalTargetError::NotFound { + target: target.to_string(), + })); + }; + let Some(pane_state) = ws.pane_state_mut(resolved.pane_id) else { + return Err(AgentRenameError::Target(TerminalTargetError::NotFound { + target: target.to_string(), + })); + }; + match normalized_name { + Some(name) => pane_state.set_manual_label(name), + None => pane_state.clear_manual_label(), + } + self.state.mark_session_dirty(); + self.agent_info(resolved.ws_idx, resolved.pane_id) + .ok_or_else(|| { + AgentRenameError::Target(TerminalTargetError::NotFound { + target: target.to_string(), + }) + }) + } + + pub(super) fn agent_target_error_body( + &self, + err: TerminalTargetError, + ) -> crate::api::schema::ErrorBody { + match err { + TerminalTargetError::NotFound { target } => crate::api::schema::ErrorBody { + code: "agent_not_found".into(), + message: format!("agent target {target} not found"), + }, + TerminalTargetError::Ambiguous { target, candidates } => { + crate::api::schema::ErrorBody { + code: "agent_target_ambiguous".into(), + message: format!( + "agent target {target} is ambiguous; candidates: {}", + candidates + .into_iter() + .map(|candidate| format!( + "terminal_id={} pane_id={} workspace_id={} tab_id={} cwd={} status={:?}", + candidate.terminal_id, + candidate.pane_id, + candidate.workspace_id, + candidate.tab_id, + candidate.cwd.unwrap_or_else(|| "unknown".into()), + candidate.agent_status, + )) + .collect::<Vec<_>>() + .join("; ") + ), + } + } + } + } + + pub(super) fn agent_rename_error_body( + &self, + err: AgentRenameError, + ) -> crate::api::schema::ErrorBody { + match err { + AgentRenameError::Target(err) => self.agent_target_error_body(err), + AgentRenameError::DuplicateName { name, candidates } => crate::api::schema::ErrorBody { + code: "agent_name_taken".into(), + message: format!( + "agent name {name} is already used; candidates: {}", + candidates + .into_iter() + .map(|candidate| format!( + "terminal_id={} pane_id={} workspace_id={} tab_id={} cwd={} status={:?}", + candidate.terminal_id, + candidate.pane_id, + candidate.workspace_id, + candidate.tab_id, + candidate.cwd.unwrap_or_else(|| "unknown".into()), + candidate.agent_status, + )) + .collect::<Vec<_>>() + .join("; ") + ), + }, + } + } + + fn agent_info( + &self, + ws_idx: usize, + pane_id: crate::layout::PaneId, + ) -> Option<crate::api::schema::AgentInfo> { + let pane = self.pane_info(ws_idx, pane_id)?; + Some(crate::api::schema::AgentInfo { + terminal_id: pane.terminal_id, + name: pane.label, + agent: pane.agent, + agent_status: pane.agent_status, + workspace_id: pane.workspace_id, + tab_id: pane.tab_id, + pane_id: pane.pane_id, + focused: pane.focused, + cwd: pane.cwd, + revision: pane.revision, + }) + } + + fn agent_name_conflicts( + &self, + name: &str, + except_terminal_id: &str, + ) -> Vec<crate::api::schema::AgentInfo> { + self.state + .workspaces + .iter() + .enumerate() + .flat_map(|(ws_idx, ws)| { + ws.tabs.iter().flat_map(move |tab| { + tab.layout + .pane_ids() + .into_iter() + .filter_map(move |pane_id| { + let pane_state = ws.pane_state(pane_id)?; + (pane_state.manual_label.as_deref() == Some(name)) + .then(|| self.agent_info(ws_idx, pane_id))? + }) + }) + }) + .filter(|agent| agent.terminal_id != except_terminal_id) + .collect() + } +} + +pub(super) enum AgentRenameError { + Target(TerminalTargetError), + DuplicateName { + name: String, + candidates: Vec<crate::api::schema::AgentInfo>, + }, +} |
