feat: expand Galaxy agent and remote tooling

Add Wormhole remote helpers, provider and agent improvements, filesystem diagnostics, model metadata support, and schema-aware settings IntelliSense.
This commit is contained in:
2026-08-23 13:55:47 -05:00
parent f17642fc62
commit 7c106eecd5
147 changed files with 2208 additions and 1514 deletions
+15 -15
View File
@@ -67,7 +67,7 @@ pub enum OnboardingVersion {
/// This represents whether entering a subshell for a particular command should become automatic in
/// the future, or to ask again.
#[derive(Clone, Debug)]
pub enum RememberForWarpification {
pub enum RememberForWormholing {
/// If yes, need to transmit the command itself so it can be persisted to user-defaults
RememberSubshellCommand(String),
RememberSSHHost(String),
@@ -75,22 +75,22 @@ pub enum RememberForWarpification {
DoNotRememberSSHHost,
}
impl RememberForWarpification {
impl RememberForWormholing {
pub fn as_bool(&self) -> bool {
match self {
RememberForWarpification::RememberSubshellCommand(_) => true,
RememberForWarpification::RememberSSHHost(_) => true,
RememberForWarpification::DoNotRememberSubshellCommand => false,
RememberForWarpification::DoNotRememberSSHHost => false,
RememberForWormholing::RememberSubshellCommand(_) => true,
RememberForWormholing::RememberSSHHost(_) => true,
RememberForWormholing::DoNotRememberSubshellCommand => false,
RememberForWormholing::DoNotRememberSSHHost => false,
}
}
pub fn is_ssh(&self) -> bool {
match self {
RememberForWarpification::RememberSSHHost(_) => true,
RememberForWarpification::DoNotRememberSSHHost => true,
RememberForWarpification::RememberSubshellCommand(_) => false,
RememberForWarpification::DoNotRememberSubshellCommand => false,
RememberForWormholing::RememberSSHHost(_) => true,
RememberForWormholing::DoNotRememberSSHHost => true,
RememberForWormholing::RememberSubshellCommand(_) => false,
RememberForWormholing::DoNotRememberSubshellCommand => false,
}
}
}
@@ -284,8 +284,8 @@ pub enum TerminalAction {
},
/// Starts a subshell in the active session.
TriggerSubshellBootstrap,
/// If the user says "no" to Warpification, possibly requesting not to be asked again
DismissWarpifyBanner(RememberForWarpification),
/// If the user says "no" to Wormholing, possibly requesting not to be asked again
DismissWormholeBanner(RememberForWormholing),
/// Triggers the banner asking to turn the running block into a subshell. The String is the
/// command that the user entered.
ShowSubshellBanner(String),
@@ -342,7 +342,7 @@ pub enum TerminalAction {
GenerateCodebaseIndex,
/// This is for debugging, dev only for now
LoadAgentModeConversation,
ShowWarpifySettings,
ShowWormholeSettings,
/// Removes a pending attachment (image or file) by index in the unified list.
DeleteAttachment {
index: usize,
@@ -622,7 +622,7 @@ impl fmt::Debug for TerminalAction {
OpenBlockListContextMenu => f.write_str("OpenBlockListContextMenu"),
AskAIAssistant { block_index } => write!(f, "AskAIAssistant({block_index:?})"),
TriggerSubshellBootstrap => f.write_str("TriggerSubshellBootstrap"),
DismissWarpifyBanner(remember) => write!(f, "DismissWarpifyBanner({remember:?})"),
DismissWormholeBanner(remember) => write!(f, "DismissWormholeBanner({remember:?})"),
ShowSubshellBanner(_) => f.write_str("ShowSubshellBanner"),
InsertMostRecentCommandCorrection => f.write_str("InsertMostRecentCommandCorrection"),
AliasExpansionBanner(action) => write!(f, "AliasExpansionBanner({action:?}"),
@@ -682,7 +682,7 @@ impl fmt::Debug for TerminalAction {
ShowInitializationBlock => write!(f, "ShowInitializationBlock"),
GenerateCodebaseIndex => write!(f, "GenerateIndexForRepo"),
LoadAgentModeConversation => write!(f, "LoadAgentModeConversation"),
ShowWarpifySettings => write!(f, "ShowWarpifySettings"),
ShowWormholeSettings => write!(f, "ShowWormholeSettings"),
DeleteAttachment { index } => write!(f, "DeleteAttachment({index:?})"),
OpenAttachmentLightbox { index } => {
write!(f, "OpenAttachmentLightbox({index:?})")
+4 -4
View File
@@ -6,14 +6,14 @@
//! without a LayoutContext. Use the exported BLOCK_BANNER_HEIGHT const when the banner height
//! needs to be taken into account.
mod warpify;
mod wormhole;
use galaxyui::elements::{
ConstrainedBox, Container, CornerRadius, Hoverable, MouseState, MouseStateHandle,
ParentElement, Radius, Stack,
};
use galaxyui::Element;
pub use warpify::*;
pub use wormhole::*;
use crate::themes::theme::GalaxyTheme;
@@ -25,13 +25,13 @@ const BANNER_H_PADDING: f32 = 8.;
pub const BLOCK_BANNER_HEIGHT: f32 = CONSTRAINED_BANNER_HEIGHT + BANNER_TOP_MARGIN;
pub enum WithinBlockBanner {
WarpifyBanner(WarpifyBannerState),
WormholeBanner(WormholeBannerState),
}
impl WithinBlockBanner {
pub fn banner_height(&self) -> f32 {
match self {
WithinBlockBanner::WarpifyBanner(_) => BLOCK_BANNER_HEIGHT,
WithinBlockBanner::WormholeBanner(_) => BLOCK_BANNER_HEIGHT,
}
}
}
@@ -10,14 +10,14 @@ use pathfinder_color::ColorU;
use super::render_block_banner;
use crate::appearance::Appearance;
use crate::terminal::view::{RememberForWarpification, TerminalAction};
use crate::terminal::view::{RememberForWormholing, TerminalAction};
use crate::themes::theme::Fill;
use crate::ui_components::blended_colors;
const CLOSE_BUTTON_DIAMETER: f32 = 20.0;
const STANDARD_PADDING: f32 = 8.0;
pub struct WarpifyBannerState {
pub struct WormholeBannerState {
/// The subshell command that triggered the banner.
pub command: String,
pub height: f32,
@@ -25,19 +25,19 @@ pub struct WarpifyBannerState {
pub dont_ask_button_mouse_state: MouseStateHandle,
pub dismiss_button_mouse_state: MouseStateHandle,
/// This keybinding gets rendered in the Warpification banner, but we can't look it up
/// This keybinding gets rendered in the Wormholing banner, but we can't look it up
/// during render as a &mut AppContext is not available then. This needs to get
/// looked up during action handling and cached here.
pub initialize_warpify_keybinding: Option<Keystroke>,
pub initialize_wormhole_keybinding: Option<Keystroke>,
pub hover_state: MouseStateHandle,
}
impl WarpifyBannerState {
pub fn new(command: String, initialize_warpify_keybinding: Option<Keystroke>) -> Self {
impl WormholeBannerState {
pub fn new(command: String, initialize_wormhole_keybinding: Option<Keystroke>) -> Self {
Self {
command,
height: 0.0,
initialize_warpify_keybinding,
initialize_wormhole_keybinding,
accept_button_mouse_state: Default::default(),
dont_ask_button_mouse_state: Default::default(),
dismiss_button_mouse_state: Default::default(),
@@ -46,18 +46,18 @@ impl WarpifyBannerState {
}
pub fn title(&self) -> &str {
"Warpify subshell"
"Wormhole subshell"
}
pub fn action(&self) -> TerminalAction {
TerminalAction::TriggerSubshellBootstrap
}
fn remember_for_warpification(&self, should_remember: bool) -> RememberForWarpification {
fn remember_for_wormholing(&self, should_remember: bool) -> RememberForWormholing {
if should_remember {
RememberForWarpification::RememberSubshellCommand(self.command.to_owned())
RememberForWormholing::RememberSubshellCommand(self.command.to_owned())
} else {
RememberForWarpification::DoNotRememberSubshellCommand
RememberForWormholing::DoNotRememberSubshellCommand
}
}
}
@@ -65,18 +65,18 @@ impl WarpifyBannerState {
/// This banner is shown when the user runs a command which is recognized as a subshell-compatible
/// command. It asks if they want to bootstrap a subshell and, if so, whether we should ask again
/// next time they run the same command.
pub fn render_warpification_banner(
state: &WarpifyBannerState,
pub fn render_wormholing_banner(
state: &WormholeBannerState,
appearance: &Appearance,
) -> Box<dyn Element> {
let yes_button = render_yes_button(
state,
&state.initialize_warpify_keybinding,
&state.initialize_wormhole_keybinding,
&state.accept_button_mouse_state,
appearance,
);
let remember = state.remember_for_warpification(true);
let remember = state.remember_for_wormholing(true);
let dont_ask_button = Container::new(
appearance
.ui_builder()
@@ -87,7 +87,7 @@ pub fn render_warpification_banner(
.with_text_label("Do not show again".to_owned())
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner(
ctx.dispatch_typed_action(TerminalAction::DismissWormholeBanner(
remember.to_owned(),
));
})
@@ -96,7 +96,7 @@ pub fn render_warpification_banner(
.with_margin_right(16.)
.finish();
let do_not_remember = state.remember_for_warpification(false);
let do_not_remember = state.remember_for_wormholing(false);
let close_button = appearance
.ui_builder()
.close_button(
@@ -105,7 +105,7 @@ pub fn render_warpification_banner(
)
.build()
.on_click(move |ctx, _, _| {
ctx.dispatch_typed_action(TerminalAction::DismissWarpifyBanner(
ctx.dispatch_typed_action(TerminalAction::DismissWormholeBanner(
do_not_remember.to_owned(),
));
})
@@ -132,12 +132,12 @@ pub fn render_warpification_banner(
}
fn render_yes_button(
state: &WarpifyBannerState,
initialize_warpification_keybinding: &Option<Keystroke>,
state: &WormholeBannerState,
initialize_wormholing_keybinding: &Option<Keystroke>,
mouse_state: &MouseStateHandle,
appearance: &Appearance,
) -> Box<dyn Element> {
let yes_button = match initialize_warpification_keybinding {
let yes_button = match initialize_wormholing_keybinding {
Some(keystroke) => appearance
.ui_builder()
.keyboard_shortcut_button(state.title().to_owned(), keystroke, mouse_state.clone())
+3 -3
View File
@@ -81,8 +81,8 @@ pub fn init(app: &mut AppContext) {
app.register_binding_validator::<TerminalView>(is_binding_pty_compliant);
init_overlapping_keybindings(app);
// Register input mode bindings before warpify bindings so ctrl-i warpifies
// instead of opening inline agent when a warpify banner is visible.
// Register input mode bindings before wormhole bindings so ctrl-i wormholes
// instead of opening inline agent when a wormhole banner is visible.
register_input_mode_bindings(app);
app.register_fixed_bindings([
@@ -320,7 +320,7 @@ pub fn init(app: &mut AppContext) {
| (id!("Terminal") & !id!("IMEOpen") & id!(flags::CLI_AGENT_RICH_INPUT_OPEN)),
),
EditableBinding::new(
"terminal:warpify_subshell",
"terminal:wormhole_subshell",
"Wormhole subshell",
TerminalAction::TriggerSubshellBootstrap,
)
+3 -3
View File
@@ -18,7 +18,7 @@ use crate::terminal::view::init_environment::InitEnvironmentBlock;
use crate::terminal::view::ssh_remote_server_choice_view::SshRemoteServerChoiceView;
use crate::terminal::view::ssh_remote_server_failed_banner::SshRemoteServerFailedBanner;
use crate::terminal::view::ssh_tmux_deprecation_banner::SshTmuxDeprecationBanner;
use crate::terminal::warpify::success_block::WarpifySuccessBlock;
use crate::terminal::wormhole::success_block::WormholeSuccessBlock;
use crate::terminal::TerminalView;
/// Specifies where to insert rich content in the blocklist.
@@ -249,8 +249,8 @@ pub enum RichContentMetadata {
SshTmuxDeprecationBanner {
handle: ViewHandle<SshTmuxDeprecationBanner>,
},
WarpifySuccessBlock {
bootstrap_success_block_handle: ViewHandle<WarpifySuccessBlock>,
WormholeSuccessBlock {
bootstrap_success_block_handle: ViewHandle<WormholeSuccessBlock>,
},
TelemetryBanner {
telemetry_banner_handle: ViewHandle<TelemetryBanner>,
+1 -1
View File
@@ -187,7 +187,7 @@ impl FileUpload {
}
}
/// Creates an sftp command that copies a given local file into the PWD of the warpified ssh session, if any.
/// Creates an sftp command that copies a given local file into the PWD of the wormholed ssh session, if any.
fn transfer_file_sftp_command(&self, file_upload: &FileUploadInfo) -> String {
// "sftp "
let mut command = String::from("sftp ");
@@ -1,14 +1,14 @@
//! Inline block view that asks the user whether they want to install
//! Warp's SSH extension on the remote host the shell just connected to,
//! Wormhole's remote helper on the host the shell just connected to,
//! or continue without installing (falling back to the existing
//! ControlMaster warpification path).
//! ControlMaster wormholing path).
//!
//! Designed from frame 6050:2448 of the Figma file
//! [Remote session initialization](https://www.figma.com/design/r0BO9cTZCK6pDE6qerg2K0/Remote-session-initialization).
//!
//! The view owns:
//! - a child [`KeyboardNavigableButtons`] handle for the two selectable
//! cards ("Install Warp's SSH extension" / "Continue without installing"),
//! cards ("Install Wormhole helper" / "Continue without installing"),
//! - the [`SessionId`] this prompt is scoped to (used for event forwarding),
//! - the current "Don't ask me this again" checked state (purely local to
//! this prompt instance; persisted to `ssh_extension_install_mode` only
@@ -37,7 +37,7 @@ use crate::ai::blocklist::inline_action::inline_action_header::{
};
use crate::server::telemetry::TelemetryEvent;
use crate::terminal::model::session::SessionId;
use crate::terminal::warpify::settings::{SshExtensionInstallMode, WarpifySettings};
use crate::terminal::wormhole::settings::{SshExtensionInstallMode, WormholeSettings};
use crate::ui_components::blended_colors;
use crate::{send_telemetry_from_ctx, Appearance};
@@ -48,14 +48,14 @@ pub enum SshRemoteServerChoiceViewAction {
Install,
Skip,
ToggleDoNotAskAgain,
OpenWarpifySettings,
OpenWormholeSettings,
}
#[derive(Clone, Debug)]
pub enum SshRemoteServerChoiceViewEvent {
Install,
Skip,
OpenWarpifySettings,
OpenWormholeSettings,
}
/// Choice block prompting the user to install the remote-server binary on the remote host or skip.
@@ -74,7 +74,7 @@ impl SshRemoteServerChoiceView {
let buttons = ctx.add_typed_action_view(|_| {
KeyboardNavigableButtons::new(vec![
rich_navigation_button(
"Install Galaxy's SSH extension".to_string(),
"Install Wormhole helper".to_string(),
Some(
"Install Galaxy's extension to enable agent features like file browsing, \
code review, and intelligent command completions in this session."
@@ -171,14 +171,16 @@ impl SshRemoteServerChoiceView {
.with_child(Container::new(checkbox_label).with_margin_left(4.).finish())
.finish();
// Right: "Manage Warpify settings" link.
// Right: "Manage Wormhole settings" link.
let manage_settings_link = appearance
.ui_builder()
.link(
"Manage Wormhole settings".into(),
None,
Some(Box::new(|ctx| {
ctx.dispatch_typed_action(SshRemoteServerChoiceViewAction::OpenWarpifySettings);
ctx.dispatch_typed_action(
SshRemoteServerChoiceViewAction::OpenWormholeSettings,
);
})),
self.manage_settings_mouse_state.clone(),
)
@@ -264,7 +266,7 @@ impl TypedActionView for SshRemoteServerChoiceView {
SshRemoteServerChoiceViewAction::Install => {
if self.do_not_ask_again {
let mode = SshExtensionInstallMode::AlwaysInstall;
WarpifySettings::handle(ctx).update(ctx, |settings, ctx| {
WormholeSettings::handle(ctx).update(ctx, |settings, ctx| {
if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) {
log::error!("Failed to persist ssh_extension_install_mode: {e}");
}
@@ -281,7 +283,7 @@ impl TypedActionView for SshRemoteServerChoiceView {
SshRemoteServerChoiceViewAction::Skip => {
if self.do_not_ask_again {
let mode = SshExtensionInstallMode::NeverInstall;
WarpifySettings::handle(ctx).update(ctx, |settings, ctx| {
WormholeSettings::handle(ctx).update(ctx, |settings, ctx| {
if let Err(e) = settings.ssh_extension_install_mode.set_value(mode, ctx) {
log::error!("Failed to persist ssh_extension_install_mode: {e}");
}
@@ -305,8 +307,8 @@ impl TypedActionView for SshRemoteServerChoiceView {
);
ctx.notify();
}
SshRemoteServerChoiceViewAction::OpenWarpifySettings => {
ctx.emit(SshRemoteServerChoiceViewEvent::OpenWarpifySettings);
SshRemoteServerChoiceViewAction::OpenWormholeSettings => {
ctx.emit(SshRemoteServerChoiceViewEvent::OpenWormholeSettings);
}
}
}
@@ -1,5 +1,5 @@
//! Banner shown when the remote-server binary check, installation, or connection fails on the remote host.
//! We fall back to the existing Warpification behavior and display this banner so the user knows why advanced features are unavailable.
//! We fall back to the existing Wormholing behavior and display this banner so the user knows why advanced features are unavailable.
use galaxy_core::ui::theme::color::internal_colors;
use galaxy_core::ui::theme::AnsiColorIdentifier;
@@ -15,11 +15,11 @@ use crate::terminal::model::session::SessionId;
use crate::ui_components::icons::Icon;
use crate::Appearance;
const BANNER_TITLE: &str = "Couldn't connect to the Warp SSH extension";
const BANNER_TITLE: &str = "Couldn't connect to the Wormhole helper";
const BANNER_BODY: &str =
"While advanced features like file browsing and code review are currently \
disabled, the rest of your Warpified experience is fully available.";
disabled, the rest of your Wormholed experience is fully available.";
#[derive(Clone, Debug)]
pub enum SshRemoteServerFailedBannerAction {
@@ -1,6 +1,6 @@
//! One-time inline banner shown to users who had previously opted into the now-deprecated
//! tmux-based SSH warpification flow. It explains that tmux SSH warpification has been turned
//! off in favor of Warp's SSH extension (remote server) and links to the docs.
//! tmux-based SSH wormholing flow. It explains that tmux SSH wormholing has been turned
//! off in favor of Galaxy's SSH extension (remote server).
//!
//! The banner is shown at most once per affected user: it is gated on the
//! `ssh_tmux_deprecation_notice_pending` setting, which is set by a one-time migration and
@@ -8,29 +8,25 @@
use galaxy_core::ui::theme::color::internal_colors;
use warpui::elements::{
Align, ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment,
ConstrainedBox, Container, CrossAxisAlignment, Flex, Hoverable, MainAxisAlignment,
MainAxisSize, MouseStateHandle, ParentElement, Shrinkable, Text,
};
use warpui::platform::Cursor;
use warpui::ui_components::components::{UiComponent, UiComponentStyles};
use warpui::{AppContext, Element, Entity, SingletonEntity, TypedActionView, View, ViewContext};
use crate::terminal::model::session::SessionId;
use crate::terminal::warpify::render::SSH_DOCS_URL;
use crate::ui_components::icons::Icon;
use crate::Appearance;
const BANNER_TITLE: &str = "Tmux SSH warpification has been deprecated";
const BANNER_TITLE: &str = "Legacy tmux SSH wormholing has been retired";
const BANNER_BODY: &str = "Warp now connects to remote sessions using the SSH extension, which is \
const BANNER_BODY: &str =
"Galaxy now connects to remote sessions using the SSH extension, which is \
more robust than the tmux-based flow. The tmux option has been removed.";
const LEARN_MORE_LABEL: &str = "Learn more";
#[derive(Clone, Debug)]
pub enum SshTmuxDeprecationBannerAction {
Dismiss,
LearnMore,
}
#[derive(Clone, Debug)]
@@ -40,7 +36,6 @@ pub enum SshTmuxDeprecationBannerEvent {
pub struct SshTmuxDeprecationBanner {
session_id: SessionId,
learn_more_mouse_state: MouseStateHandle,
close_mouse_state: MouseStateHandle,
}
@@ -48,7 +43,6 @@ impl SshTmuxDeprecationBanner {
pub fn new(session_id: SessionId) -> Self {
Self {
session_id,
learn_more_mouse_state: MouseStateHandle::default(),
close_mouse_state: MouseStateHandle::default(),
}
}
@@ -72,13 +66,12 @@ impl View for SshTmuxDeprecationBanner {
let theme = appearance.theme();
let fg_color = theme.foreground().into_solid();
let muted_color = internal_colors::neutral_5(theme);
let accent_color = theme.accent().into_solid();
let font_size = appearance.monospace_font_size();
let small_font_size = font_size - 2.;
// Warp icon to match the other warpification blocks.
// Galaxy icon to match the other wormholing blocks.
let icon = Container::new(
ConstrainedBox::new(Icon::Warp.to_warpui_icon(fg_color.into()).finish())
ConstrainedBox::new(Icon::GalaxyLogo.to_warpui_icon(fg_color.into()).finish())
.with_width(16.)
.with_height(16.)
.finish(),
@@ -103,26 +96,6 @@ impl View for SshTmuxDeprecationBanner {
.with_color(muted_color)
.finish();
let learn_more = appearance
.ui_builder()
.link(
LEARN_MORE_LABEL.into(),
None,
Some(Box::new(|ctx| {
ctx.dispatch_typed_action(SshTmuxDeprecationBannerAction::LearnMore);
})),
self.learn_more_mouse_state.clone(),
)
.soft_wrap(false)
.with_style(UiComponentStyles {
font_size: Some(small_font_size),
font_family_id: Some(appearance.ui_font_family()),
font_color: Some(accent_color),
..Default::default()
})
.build()
.finish();
// Close (X) button
let close_icon_color = muted_color;
let close = Hoverable::new(self.close_mouse_state.clone(), move |_| {
@@ -158,26 +131,17 @@ impl View for SshTmuxDeprecationBanner {
.with_child(close_container)
.finish();
// Body text + learn more link, indented past the icon to align with the title.
// Body text, indented past the icon to align with the title.
let body_container = Container::new(body)
.with_margin_top(2.)
.with_margin_left(24.)
.finish();
// Wrap the link in a left-aligned `Align` so its hover/underline region hugs the
// link text instead of stretching to the full banner width (the parent column uses
// `CrossAxisAlignment::Stretch`).
let learn_more_container = Container::new(Align::new(learn_more).left().finish())
.with_margin_top(4.)
.with_margin_left(24.)
.finish();
let content = Flex::column()
.with_main_axis_size(MainAxisSize::Min)
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
.with_child(header_row)
.with_child(body_container)
.with_child(learn_more_container)
.finish();
Container::new(content)
@@ -195,10 +159,6 @@ impl TypedActionView for SshTmuxDeprecationBanner {
SshTmuxDeprecationBannerAction::Dismiss => {
ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed);
}
SshTmuxDeprecationBannerAction::LearnMore => {
ctx.open_url(SSH_DOCS_URL);
ctx.emit(SshTmuxDeprecationBannerEvent::Dismissed);
}
}
}
}
+36 -36
View File
@@ -16,7 +16,7 @@ use crate::terminal::shared_session::{
SharedSessionActionSource, SharedSessionScrollbackType, SharedSessionSource,
};
use crate::util::image::{infer_mime_type, MAX_IMAGE_SIZE_BYTES_FOR_CLI_AGENT, MIME_SNIFF_BYTES};
mod warpify_footer;
mod wormhole_footer;
use std::path::Path;
use std::sync::{Arc, LazyLock};
@@ -44,7 +44,7 @@ use galaxyui::{
};
use parking_lot::FairMutex;
use pathfinder_color::ColorU;
use warpify_footer::{WarpifyFooterView, WarpifyFooterViewEvent};
use wormhole_footer::{WormholeFooterView, WormholeFooterViewEvent};
use super::{RichContentInsertionPosition, TerminalAction, TerminalView};
use crate::ai::blocklist::agent_view::agent_view_bg_fill;
@@ -267,11 +267,11 @@ impl TerminalView {
UseAgentToolbarEvent::HideRichInput => {
self.close_cli_agent_rich_input_and_disable_auto_toggle(ctx);
}
UseAgentToolbarEvent::Warpify => {
UseAgentToolbarEvent::Wormhole => {
self.hide_use_agent_footer_in_blocklist(ctx);
self.handle_action(&TerminalAction::TriggerSubshellBootstrap, ctx);
send_telemetry_from_ctx!(
TelemetryEvent::WarpifyFooterAcceptedWarpify { is_ssh: false },
TelemetryEvent::WormholeFooterAcceptedWormhole { is_ssh: false },
ctx
);
}
@@ -295,8 +295,8 @@ impl TerminalView {
) -> bool {
let ai_settings = AISettings::as_ref(app);
// If the warpify footer is active, a subshell was detected and we should show the footer.
if self.use_agent_footer.as_ref(app).is_warpify_active(app) {
// If the wormhole footer is active, a subshell was detected and we should show the footer.
if self.use_agent_footer.as_ref(app).is_wormhole_active(app) {
return true;
}
@@ -421,7 +421,7 @@ impl TerminalView {
if !self.model.lock().is_alt_screen_active() {
self.use_agent_footer.update(ctx, |footer, ctx| {
footer.clear_warpify(ctx);
footer.clear_wormhole(ctx);
});
self.hide_use_agent_footer_in_blocklist(ctx);
}
@@ -1046,8 +1046,8 @@ pub struct UseAgentToolbar {
// Shared agent input footer (renders CLI agent mode when a CLI session is active).
agent_input_footer: ViewHandle<AgentInputFooter>,
// Warpify footer UI (shown when a subshell/SSH command is detected).
warpify_footer_view: ViewHandle<WarpifyFooterView>,
// Wormhole footer UI (shown when a subshell/SSH command is detected).
wormhole_footer_view: ViewHandle<WormholeFooterView>,
// `true` if the user has dismissed the footer.
//
@@ -1120,11 +1120,11 @@ impl UseAgentToolbar {
me.handle_agent_input_footer_event(event, ctx);
});
let warpify_footer_view =
ctx.add_typed_action_view(|ctx| WarpifyFooterView::new(terminal_model.clone(), ctx));
let wormhole_footer_view =
ctx.add_typed_action_view(|ctx| WormholeFooterView::new(terminal_model.clone(), ctx));
ctx.subscribe_to_view(&warpify_footer_view, |me, _, event, ctx| {
me.handle_warpify_footer_event(event, ctx);
ctx.subscribe_to_view(&wormhole_footer_view, |me, _, event, ctx| {
me.handle_wormhole_footer_event(event, ctx);
});
ctx.subscribe_to_model(model_event_dispatcher, |me, _, event, ctx| {
@@ -1150,7 +1150,7 @@ impl UseAgentToolbar {
dismiss_button,
dont_show_again_button,
agent_input_footer,
warpify_footer_view,
wormhole_footer_view,
terminal_model,
did_user_dismiss: false,
}
@@ -1186,19 +1186,19 @@ impl UseAgentToolbar {
}
}
fn handle_warpify_footer_event(
fn handle_wormhole_footer_event(
&mut self,
event: &WarpifyFooterViewEvent,
event: &WormholeFooterViewEvent,
ctx: &mut ViewContext<Self>,
) {
match event {
WarpifyFooterViewEvent::Warpify => {
ctx.emit(UseAgentToolbarEvent::Warpify);
WormholeFooterViewEvent::Wormhole => {
ctx.emit(UseAgentToolbarEvent::Wormhole);
}
WarpifyFooterViewEvent::UseAgent => {
WormholeFooterViewEvent::UseAgent => {
ctx.emit(UseAgentToolbarEvent::UseAgent);
}
WarpifyFooterViewEvent::Dismiss => {
WormholeFooterViewEvent::Dismiss => {
ctx.emit(UseAgentToolbarEvent::Dismiss);
}
}
@@ -1207,7 +1207,7 @@ impl UseAgentToolbar {
pub(in crate::terminal) fn notify_and_notify_children(&mut self, ctx: &mut ViewContext<Self>) {
ctx.notify();
self.agent_input_footer.update(ctx, |_, ctx| ctx.notify());
self.warpify_footer_view.update(ctx, |_, ctx| ctx.notify());
self.wormhole_footer_view.update(ctx, |_, ctx| ctx.notify());
self.button.update(ctx, |_, ctx| ctx.notify());
self.give_control_back_button
.update(ctx, |_, ctx| ctx.notify());
@@ -1227,26 +1227,26 @@ impl UseAgentToolbar {
.map(|session| session.agent)
}
/// Activates the warpify footer. When active, the footer shows the
/// warpify view instead of the CLI agent or regular "Use agent" views.
pub(in crate::terminal) fn show_warpify(&mut self, ctx: &mut ViewContext<Self>) {
self.warpify_footer_view.update(ctx, |view, ctx| {
/// Activates the wormhole footer. When active, the footer shows the
/// wormhole view instead of the CLI agent or regular "Use agent" views.
pub(in crate::terminal) fn show_wormhole(&mut self, ctx: &mut ViewContext<Self>) {
self.wormhole_footer_view.update(ctx, |view, ctx| {
view.show(ctx);
});
ctx.notify();
}
/// Deactivates the warpify footer so it reverts to its default behavior.
pub(in crate::terminal) fn clear_warpify(&mut self, ctx: &mut ViewContext<Self>) {
self.warpify_footer_view.update(ctx, |view, ctx| {
/// Deactivates the wormhole footer so it reverts to its default behavior.
pub(in crate::terminal) fn clear_wormhole(&mut self, ctx: &mut ViewContext<Self>) {
self.wormhole_footer_view.update(ctx, |view, ctx| {
view.clear(ctx);
});
ctx.notify();
}
/// Returns whether the warpify footer is currently active.
pub(in crate::terminal) fn is_warpify_active(&self, app: &AppContext) -> bool {
self.warpify_footer_view.as_ref(app).is_active()
/// Returns whether the wormhole footer is currently active.
pub(in crate::terminal) fn is_wormhole_active(&self, app: &AppContext) -> bool {
self.wormhole_footer_view.as_ref(app).is_active()
}
/// Returns whether there's a current CLI agent (like Claude Code).
@@ -1272,8 +1272,8 @@ pub enum UseAgentToolbarEvent {
OpenRichInput,
/// Hide the rich input editor (same as Escape).
HideRichInput,
/// User chose to warpify the subshell.
Warpify,
/// User chose to wormhole the subshell.
Wormhole,
/// User chose to use the agent.
UseAgent,
StartRemoteControl {
@@ -1292,9 +1292,9 @@ impl View for UseAgentToolbar {
}
fn render(&self, app: &AppContext) -> Box<dyn Element> {
// If the warpify footer is active, delegate rendering to the warpify footer view.
if self.warpify_footer_view.as_ref(app).is_active() {
return ChildView::new(&self.warpify_footer_view).finish();
// If the wormhole footer is active, delegate rendering to the wormhole footer view.
if self.wormhole_footer_view.as_ref(app).is_active() {
return ChildView::new(&self.wormhole_footer_view).finish();
}
// Hide the toolbar entirely when CLI rich input is open,
@@ -15,28 +15,28 @@ use crate::view_components::action_button::{
};
/// Footer view rendered for detected subshell commands, offering both
/// "Warpify" and "Use agent" buttons in a horizontal row.
pub(super) struct WarpifyFooterView {
/// "Wormhole" and "Use agent" buttons in a horizontal row.
pub(super) struct WormholeFooterView {
terminal_model: Arc<FairMutex<TerminalModel>>,
warpify_button: ViewHandle<ActionButton>,
wormhole_button: ViewHandle<ActionButton>,
use_agent_button: ViewHandle<ActionButton>,
dismiss_button: ViewHandle<ActionButton>,
/// Whether the footer is currently offering subshell warpification.
/// Whether the footer is currently offering subshell wormholing.
is_active: bool,
}
impl WarpifyFooterView {
impl WormholeFooterView {
pub fn new(terminal_model: Arc<FairMutex<TerminalModel>>, ctx: &mut ViewContext<Self>) -> Self {
let button_size = ButtonSize::XSmall;
let warpify_button = ctx.add_typed_action_view(|_ctx| {
let wormhole_button = ctx.add_typed_action_view(|_ctx| {
ActionButton::new("Wormhole subshell", AgentFooterButtonTheme::new(None))
.with_icon(Icon::Warp)
.with_icon(Icon::GalaxyLogo)
.with_size(button_size)
.with_tooltip("Enable Galaxy shell integration in this session")
.with_tooltip_alignment(TooltipAlignment::Left)
.on_click(|ctx| {
ctx.dispatch_typed_action(WarpifyFooterViewAction::Warpify);
ctx.dispatch_typed_action(WormholeFooterViewAction::Wormhole);
})
});
@@ -48,7 +48,7 @@ impl WarpifyFooterView {
.with_tooltip("Ask the Galaxy agent to assist")
.with_tooltip_alignment(TooltipAlignment::Left)
.on_click(|ctx| {
ctx.dispatch_typed_action(WarpifyFooterViewAction::UseAgent);
ctx.dispatch_typed_action(WormholeFooterViewAction::UseAgent);
})
});
@@ -56,24 +56,24 @@ impl WarpifyFooterView {
ActionButton::new("Dismiss", AgentFooterButtonTheme::new(None))
.with_size(button_size)
.on_click(|ctx| {
ctx.dispatch_typed_action(WarpifyFooterViewAction::Dismiss);
ctx.dispatch_typed_action(WormholeFooterViewAction::Dismiss);
})
});
Self {
terminal_model,
warpify_button,
wormhole_button,
use_agent_button,
dismiss_button,
is_active: false,
}
}
/// Activates the footer so it offers subshell warpification.
/// Activates the footer so it offers subshell wormholing.
pub fn show(&mut self, ctx: &mut ViewContext<Self>) {
self.warpify_button.update(ctx, |button, ctx| {
self.wormhole_button.update(ctx, |button, ctx| {
button.set_keybinding(
Some(KeystrokeSource::Binding("terminal:warpify_subshell")),
Some(KeystrokeSource::Binding("terminal:wormhole_subshell")),
ctx,
);
});
@@ -81,7 +81,7 @@ impl WarpifyFooterView {
ctx.notify();
}
/// Returns whether the footer is currently offering subshell warpification.
/// Returns whether the footer is currently offering subshell wormholing.
pub fn is_active(&self) -> bool {
self.is_active
}
@@ -89,7 +89,7 @@ impl WarpifyFooterView {
/// Deactivates the footer.
pub fn clear(&mut self, ctx: &mut ViewContext<Self>) {
self.is_active = false;
self.warpify_button.update(ctx, |button, ctx| {
self.wormhole_button.update(ctx, |button, ctx| {
button.set_keybinding(None, ctx);
});
ctx.notify();
@@ -97,25 +97,25 @@ impl WarpifyFooterView {
}
#[derive(Debug, Clone)]
pub enum WarpifyFooterViewAction {
Warpify,
pub enum WormholeFooterViewAction {
Wormhole,
UseAgent,
Dismiss,
}
pub enum WarpifyFooterViewEvent {
Warpify,
pub enum WormholeFooterViewEvent {
Wormhole,
UseAgent,
Dismiss,
}
impl Entity for WarpifyFooterView {
type Event = WarpifyFooterViewEvent;
impl Entity for WormholeFooterView {
type Event = WormholeFooterViewEvent;
}
impl View for WarpifyFooterView {
impl View for WormholeFooterView {
fn ui_name() -> &'static str {
"WarpifyFooterView"
"WormholeFooterView"
}
fn render(&self, _app: &AppContext) -> Box<dyn Element> {
@@ -125,7 +125,7 @@ impl View for WarpifyFooterView {
.with_spacing(4.)
.with_main_axis_size(MainAxisSize::Max)
.with_cross_axis_alignment(CrossAxisAlignment::Center)
.with_child(ChildView::new(&self.warpify_button).finish())
.with_child(ChildView::new(&self.wormhole_button).finish())
.with_child(ChildView::new(&self.use_agent_button).finish())
.with_child(Expanded::new(1., Empty::new().finish()).finish())
.with_child(ChildView::new(&self.dismiss_button).finish());
@@ -144,24 +144,24 @@ impl View for WarpifyFooterView {
}
}
impl TypedActionView for WarpifyFooterView {
type Action = WarpifyFooterViewAction;
impl TypedActionView for WormholeFooterView {
type Action = WormholeFooterViewAction;
fn handle_action(&mut self, action: &Self::Action, ctx: &mut ViewContext<Self>) {
match action {
WarpifyFooterViewAction::Warpify => {
WormholeFooterViewAction::Wormhole => {
if self.is_active {
self.clear(ctx);
ctx.emit(WarpifyFooterViewEvent::Warpify);
ctx.emit(WormholeFooterViewEvent::Wormhole);
}
}
WarpifyFooterViewAction::UseAgent => {
WormholeFooterViewAction::UseAgent => {
self.clear(ctx);
ctx.emit(WarpifyFooterViewEvent::UseAgent);
ctx.emit(WormholeFooterViewEvent::UseAgent);
}
WarpifyFooterViewAction::Dismiss => {
WormholeFooterViewAction::Dismiss => {
self.clear(ctx);
ctx.emit(WarpifyFooterViewEvent::Dismiss);
ctx.emit(WormholeFooterViewEvent::Dismiss);
}
}
}