Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 10 additions & 37 deletions crates/keiki-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<bool, Error> {
#[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<Option<DesktopViewer>, 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<DesktopViewer, Error> {
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.
Expand Down
21 changes: 1 addition & 20 deletions crates/ui/src/desktop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -160,8 +153,6 @@ pub struct Desktop {
_session: Task<()>,
}

impl EventEmitter<DesktopEvent> for Desktop {}

impl Focusable for Desktop {
fn focus_handle(&self, _cx: &gpui::App) -> FocusHandle {
self.focus_handle.clone()
Expand All @@ -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;
Expand Down
98 changes: 25 additions & 73 deletions crates/ui/src/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -785,8 +785,6 @@ struct SubagentTab {

struct DesktopTab {
view: Entity<Desktop>,
/// A viewer that finds the sandbox gone closes its own tab.
_events: Subscription,
}

pub struct Shell {
Expand Down Expand Up @@ -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<String, bool>,
/// 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<String, bool>,
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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 —
Expand Down Expand Up @@ -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<Self>) {
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 {
Expand Down Expand Up @@ -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<Self>) {
if !self.terminal_offered() {
return;
}
let panel = self.right_terminal_panel(cx);
let opened = panel.update(cx, |panel, cx| {
panel.set_open(true, cx);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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")
Expand Down
Loading