diff --git a/crates/keiki-api/src/lib.rs b/crates/keiki-api/src/lib.rs index 1cdbf40078..36c261b0dd 100644 --- a/crates/keiki-api/src/lib.rs +++ b/crates/keiki-api/src/lib.rs @@ -1059,47 +1059,20 @@ impl Client { } } - /// Whether the conversation has a sandbox a terminal could attach to. - pub async fn terminal_available( - &self, - access_token: &str, - locator: &ConversationLocator, - ) -> Result { - #[derive(Deserialize)] - struct Availability { - available: bool, - } - let response: Availability = self - .send_json( - self.http - .get(self.terminal_endpoint(locator, None)?) - .bearer_auth(access_token), - ) - .await?; - Ok(response.available) - } - - /// A viewer for the conversation sandbox's desktop (Computer Use). - /// Offered exactly where [`Self::terminal_available`] is (same sandbox), - /// but that answer comes from the platform's records — `None` here is - /// the provider's word that the sandbox is gone. + /// A viewer for the conversation sandbox's desktop (Computer Use). The + /// platform creates or wakes the sandbox as needed, so this can take a + /// while on a conversation that has never run code. pub async fn open_desktop( &self, access_token: &str, locator: &ConversationLocator, - ) -> Result, Error> { - let request = self - .http - .post(self.conversation_endpoint(locator, Some(ConversationAction::Desktop))?) - .bearer_auth(access_token); - match self.send_json(request).await { - Ok(viewer) => Ok(Some(viewer)), - Err(Error::Api { - status: StatusCode::NOT_FOUND, - .. - }) => Ok(None), - Err(error) => Err(error), - } + ) -> Result { + self.send_json( + self.http + .post(self.conversation_endpoint(locator, Some(ConversationAction::Desktop))?) + .bearer_auth(access_token), + ) + .await } /// Open a shell in the conversation's sandbox; returns the terminal id. diff --git a/crates/ui/src/desktop.rs b/crates/ui/src/desktop.rs index e1c2cc8094..53fb610d0b 100644 --- a/crates/ui/src/desktop.rs +++ b/crates/ui/src/desktop.rs @@ -32,13 +32,6 @@ use vnc::{ use crate::icons; use crate::theme::Theme; -/// The desktop is offered wherever the platform's records say the sandbox -/// exists; the provider gets the last word when the viewer actually opens. -pub enum DesktopEvent { - /// The sandbox is gone — nothing to show, and the offer should go too. - Gone, -} - enum Status { Opening, Connected, @@ -160,8 +153,6 @@ pub struct Desktop { _session: Task<()>, } -impl EventEmitter for Desktop {} - impl Focusable for Desktop { fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle { self.focus_handle.clone() @@ -184,17 +175,7 @@ impl Desktop { }) .await; let viewer = match opened { - Ok(Some(viewer)) => viewer, - Ok(None) => { - this.update(cx, |this, cx| { - this.status = - Status::Closed("This conversation's sandbox is gone.".into()); - cx.emit(DesktopEvent::Gone); - cx.notify(); - }) - .ok(); - return; - } + Ok(viewer) => viewer, Err(message) => { this.update(cx, |this, cx| this.close(message, cx)).ok(); return; diff --git a/crates/ui/src/shell.rs b/crates/ui/src/shell.rs index 0be17edf02..f28610d038 100644 --- a/crates/ui/src/shell.rs +++ b/crates/ui/src/shell.rs @@ -27,7 +27,7 @@ use zeron_rpc::methods; use crate::avatars::{self, AvatarKey, AvatarSnapshot}; use crate::changes::{Changes, ChangesEvent}; use crate::composer::{Composer, ComposerEvent, ComposerInput, ComposerInputEvent}; -use crate::desktop::{Desktop, DesktopEvent}; +use crate::desktop::Desktop; use crate::icons::{self, icon}; use crate::loaders; use crate::motion::{self, AnimationExt as _, MotionSpec, RESIZE, SPLASH_OUT, TAB_SLIDE}; @@ -785,8 +785,6 @@ struct SubagentTab { struct DesktopTab { view: Entity, - /// A viewer that finds the sandbox gone closes its own tab. - _events: Subscription, } pub struct Shell { @@ -907,9 +905,6 @@ pub struct Shell { panels: SessionPanels, /// The panel key of the chat currently shown. active_chat: String, - /// Keiki chats whose sandbox was checked this session: the Terminal - /// surface is offered only where a sandbox exists to attach to. - keiki_terminal_available: std::collections::HashMap, /// Per Keiki chat: does the agent currently have a browser handed off? /// Re-asked on every visit — handoffs come and go within a chat. keiki_browser_available: std::collections::HashMap, @@ -1198,7 +1193,6 @@ impl Shell { settings, panels: SessionPanels::default(), active_chat: String::new(), - keiki_terminal_available: std::collections::HashMap::new(), keiki_browser_available: std::collections::HashMap::new(), sidebar_prev_order: Vec::new(), sidebar_resort: std::collections::HashMap::new(), @@ -1481,7 +1475,6 @@ impl Shell { let selected = state.read(cx).selected_chat.clone().unwrap_or_default(); if selected != self.active_chat { self.active_chat = selected; - self.check_keiki_terminal(cx); self.check_keiki_browser(cx); // Route history: a chat switch is a navigation. The very first // selection off the untouched empty state REPLACES that entry — @@ -1549,32 +1542,10 @@ impl Shell { self.state.read(cx).selected_space_git() } - /// Local chats always have a shell; a Keiki chat only once its sandbox is - /// known to exist (never assumed while the check is in flight). - fn terminal_offered(&self) -> bool { - !crate::keiki::is_keiki_chat(&self.active_chat) - || self.keiki_terminal_available.get(&self.active_chat) == Some(&true) - } - - /// The desktop lives on the same sandbox as the terminal, but only - /// Keiki conversations have one (a local chat's shell is the host's). + /// A Keiki conversation's sandbox — created or woken by the platform when + /// a surface opens on it — has a desktop; a local chat's host shell has none. fn desktop_offered(&self) -> bool { crate::keiki::is_keiki_chat(&self.active_chat) - && self.keiki_terminal_available.get(&self.active_chat) == Some(&true) - } - - fn check_keiki_terminal(&mut self, cx: &mut Context) { - if self - .keiki_terminal_available - .contains_key(&self.active_chat) - { - return; - } - self.check_keiki_availability( - |client, token, locator| async move { client.terminal_available(&token, &locator).await }, - |this| &mut this.keiki_terminal_available, - cx, - ); } fn browser_offered(&self) -> bool { @@ -1935,9 +1906,6 @@ impl Shell { /// The picker's Terminal card / the `+` menu's Terminal row: every click /// opens a fresh embedded terminal tab. fn add_terminal_surface(&mut self, cx: &mut Context) { - if !self.terminal_offered() { - return; - } let panel = self.right_terminal_panel(cx); let opened = panel.update(cx, |panel, cx| { panel.set_open(true, cx); @@ -1975,19 +1943,7 @@ impl Shell { self.desktop_seq += 1; let id = self.desktop_seq; let view = cx.new(|cx| Desktop::new(client, token, locator, cx)); - let events = cx.subscribe(&view, move |this: &mut Self, _, event, cx| match event { - DesktopEvent::Gone => { - this.keiki_terminal_available.insert(chat_id.clone(), false); - this.drop_desktop_surface(id, cx); - } - }); - self.desktops.insert( - id, - DesktopTab { - view, - _events: events, - }, - ); + self.desktops.insert(id, DesktopTab { view }); let key = self.panel_key(cx); self.right_tabs .entry(key) @@ -5548,15 +5504,13 @@ impl Shell { .flex() .flex_col() .gap(px(8.0)) - .when(self.terminal_offered(), |el| { - el.child( - row("surface-card-terminal", icons::TERMINAL, "Terminal").on_click( - cx.listener(|this, _, _, cx| { - this.add_terminal_surface(cx); - }), - ), - ) - }) + .child( + row("surface-card-terminal", icons::TERMINAL, "Terminal").on_click( + cx.listener(|this, _, _, cx| { + this.add_terminal_surface(cx); + }), + ), + ) .when(self.desktop_offered(), |el| { el.child( row("surface-card-desktop", icons::LAPTOP, "Desktop").on_click( @@ -5909,22 +5863,20 @@ impl Shell { .flex() .flex_col() .gap(px(2.0)) - .when(self.terminal_offered(), |menu| { - menu.child( - popover::menu_row(&theme, false, "right-plus-terminal") - .id("right-plus-terminal-row") - .on_click(cx.listener(|this, _, _, cx| { - this.add_terminal_surface(cx); - this.close_right_plus(cx); - })) - .child( - icon(icons::TERMINAL) - .size(px(13.0)) - .text_color(theme.text_muted), - ) - .child(SharedString::from("Terminal")), - ) - }) + .child( + popover::menu_row(&theme, false, "right-plus-terminal") + .id("right-plus-terminal-row") + .on_click(cx.listener(|this, _, _, cx| { + this.add_terminal_surface(cx); + this.close_right_plus(cx); + })) + .child( + icon(icons::TERMINAL) + .size(px(13.0)) + .text_color(theme.text_muted), + ) + .child(SharedString::from("Terminal")), + ) .when(self.desktop_offered(), |menu| { menu.child( popover::menu_row(&theme, false, "right-plus-desktop")