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
+4 -4
View File
@@ -71,7 +71,7 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
api::ToolType::SearchCodebase,
]);
}
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => {
Some(SessionType::WormholedRemote { host_id: Some(_) }) => {
// Remote session with a known host — enable tools that route
// through RemoteServerClient. The host_id is only populated
// after a successful connection handshake, so its presence is a
@@ -81,7 +81,7 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
supported_tools.push(api::ToolType::SearchCodebase);
}
}
Some(SessionType::WarpifiedRemote { host_id: None }) => {}
Some(SessionType::WormholedRemote { host_id: None }) => {}
}
if FeatureFlag::ListSkills.is_enabled() {
@@ -113,13 +113,13 @@ fn get_supported_cli_agent_tools(params: &RequestParams) -> Vec<api::ToolType> {
supported_cli_agent_tools
.extend(&[api::ToolType::ReadFiles, api::ToolType::SearchCodebase]);
}
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => {
Some(SessionType::WormholedRemote { host_id: Some(_) }) => {
supported_cli_agent_tools.push(api::ToolType::ReadFiles);
if FeatureFlag::RemoteCodebaseIndexing.is_enabled() {
supported_cli_agent_tools.push(api::ToolType::SearchCodebase);
}
}
Some(SessionType::WarpifiedRemote { host_id: None }) => {}
Some(SessionType::WormholedRemote { host_id: None }) => {}
}
supported_cli_agent_tools
+1 -1
View File
@@ -59,7 +59,7 @@ fn request_params_with_ask_user_question_enabled(ask_user_question_enabled: bool
fn request_params_for_remote(host_id: Option<HostId>) -> RequestParams {
let mut params = request_params_with_ask_user_question_enabled(false);
params.session_context =
SessionContext::new_with_session_type_for_test(Some(SessionType::WarpifiedRemote {
SessionContext::new_with_session_type_for_test(Some(SessionType::WormholedRemote {
host_id,
}));
params
+1 -1
View File
@@ -289,7 +289,7 @@ static DEFAULT_TIPS: LazyLock<Vec<AgentTip>> = LazyLock::new(|| {
},
AgentTip {
description: "Wormhole a remote SSH session to enable the agent inside that environment.".to_string(),
link: Some("https://docs.warp.dev/terminal/warpify".to_string()),
link: None,
binding_name: None,
action: None,
kind: AgentTipKind::General,
@@ -115,7 +115,7 @@ impl ReadFilesExecutor {
// Check if this is a remote session with a connected host.
let session_type = self.active_session.as_ref(ctx).session_type(ctx);
let host_request_handle = match &session_type {
Some(SessionType::WarpifiedRemote {
Some(SessionType::WormholedRemote {
host_id: Some(host_id),
}) => Some(
remote_server::manager::RemoteServerManager::as_ref(ctx)
@@ -127,7 +127,7 @@ impl ReadFilesExecutor {
// Remote session without a usable remote server connection. File reading
// requires either local access or a connected remote server, neither
// of which is available.
if matches!(session_type, Some(SessionType::WarpifiedRemote { .. }))
if matches!(session_type, Some(SessionType::WormholedRemote { .. }))
&& host_request_handle.is_none()
{
return ActionExecution::Sync(AIAgentActionResultType::ReadFiles(
@@ -154,7 +154,7 @@ fn disconnected_remote_session_does_not_fall_back_to_client_global_bundled_skill
sessions.register_session_for_test(
SessionInfo::new_for_test()
.with_id(session_id)
.with_session_type(BootstrapSessionType::WarpifiedRemote),
.with_session_type(BootstrapSessionType::WormholedRemote),
);
});
let (_model_events_tx, model_events_rx) = unbounded();
@@ -235,7 +235,7 @@ fn remote_session_reads_remote_bundled_skill_catalog() {
sessions.register_session_for_test(
SessionInfo::new_for_test()
.with_id(session_id)
.with_session_type(BootstrapSessionType::WarpifiedRemote),
.with_session_type(BootstrapSessionType::WormholedRemote),
);
});
let session = sessions
@@ -370,7 +370,7 @@ impl RequestFileEditsExecutor {
})
.collect();
let diff_session_type = match self.active_session.as_ref(ctx).session_type(ctx) {
Some(SessionType::WarpifiedRemote {
Some(SessionType::WormholedRemote {
host_id: Some(host_id),
}) => DiffSessionType::Remote(host_id.clone()),
_ => DiffSessionType::Local,
@@ -360,7 +360,7 @@ fn format_session_location(session: &Session, working_directory: Option<&str>) -
let hostname = session.hostname();
match session_type {
SessionType::Local => Some(display_path),
SessionType::WarpifiedRemote { .. } => Some(format!("{user}@{hostname}:{display_path}")),
SessionType::WormholedRemote { .. } => Some(format!("{user}@{hostname}:{display_path}")),
}
}
@@ -510,7 +510,7 @@ fn current_working_directory_for_zero_state(terminal_model: &TerminalModel) -> O
.is_some_and(|pending_session_info| {
matches!(
pending_session_info.session_type,
BootstrapSessionType::WarpifiedRemote
BootstrapSessionType::WormholedRemote
)
});
(!terminal_model.block_list().is_bootstrapped() && !is_bootstrapping_remote_shell)
+1 -1
View File
@@ -65,7 +65,7 @@ use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher};
use crate::terminal::view::ambient_agent::{
is_cloud_agent_pre_first_exchange, AmbientAgentViewModel, AmbientAgentViewModelEvent,
};
use crate::terminal::warpify::render::LEFT_STRIPE_WIDTH;
use crate::terminal::wormhole::render::LEFT_STRIPE_WIDTH;
use crate::terminal::{
TerminalModel, CANCEL_COMMAND_KEYBINDING, TOGGLE_AUTOEXECUTE_MODE_KEYBINDING,
TOGGLE_HIDE_CLI_RESPONSES_KEYBINDING, TOGGLE_QUEUE_NEXT_PROMPT_KEYBINDING,
+5 -5
View File
@@ -314,11 +314,11 @@ impl SessionContext {
&self.current_working_directory
}
/// Returns the remote host ID if this is a `WarpifiedRemote` session with
/// Returns the remote host ID if this is a `WormholedRemote` session with
/// a connected `RemoteServerClient`.
pub fn host_id(&self) -> Option<&galaxy_core::HostId> {
match &self.session_type {
Some(SessionType::WarpifiedRemote { host_id }) => host_id.as_ref(),
Some(SessionType::WormholedRemote { host_id }) => host_id.as_ref(),
Some(SessionType::Local) | None => None,
}
}
@@ -326,17 +326,17 @@ impl SessionContext {
/// Returns `true` if this is a remote session (regardless of whether
/// the remote server client is connected).
pub fn is_remote(&self) -> bool {
matches!(self.session_type, Some(SessionType::WarpifiedRemote { .. }))
matches!(self.session_type, Some(SessionType::WormholedRemote { .. }))
}
pub fn skill_path_origin(&self) -> SkillPathOrigin {
match &self.session_type {
Some(SessionType::WarpifiedRemote {
Some(SessionType::WormholedRemote {
host_id: Some(host_id),
}) => SkillPathOrigin::Remote {
host_id: host_id.clone(),
},
Some(SessionType::WarpifiedRemote { host_id: None }) => SkillPathOrigin::Unavailable,
Some(SessionType::WormholedRemote { host_id: None }) => SkillPathOrigin::Unavailable,
Some(SessionType::Local) | None => SkillPathOrigin::Local,
}
}
@@ -1,6 +1,6 @@
//! This module contains rendering functions for various requested inline actions that have not yet
//! been transformed into a [`View`] component. This currently encompasses UI for file retrieval,
//! environmental variable collection, and SSH Warpification, to name a few.
//! environmental variable collection, and SSH Wormholing, to name a few.
//!
//! There's quite a bit of duplication between function-based inline actions and view-based inline
//! actions. Moreover, the header rendering functions here don't make use of the HeaderConfig.
@@ -408,7 +408,7 @@ impl PassiveSuggestionsModel {
.active_session
.as_ref(ctx)
.session_type(ctx)
.map(|session_type| matches!(session_type, SessionType::WarpifiedRemote { .. }))
.map(|session_type| matches!(session_type, SessionType::WormholedRemote { .. }))
.unwrap_or(true);
if !can_read_file || should_skip_for_remote {
let reason = if !can_read_file {
+151
View File
@@ -1037,6 +1037,157 @@ impl PersistedWorkspace {
);
}
/// Ensures Galaxy's own settings file has schema-backed TOML language support.
/// This managed server is intentionally not persisted as a code workspace.
#[cfg(feature = "local_fs")]
pub fn ensure_settings_toml_lsp(&mut self, file_path: PathBuf, ctx: &mut ModelContext<Self>) {
if file_path != crate::settings::user_preferences_toml_file_path() {
return;
}
let server_type = LSPServerType::Tombi;
let Some(workspace_root) = file_path.parent().map(Path::to_path_buf) else {
return;
};
if LspManagerModel::as_ref(ctx).server_registered(&workspace_root, server_type, ctx) {
LspManagerModel::handle(ctx).update(ctx, |manager, ctx| {
manager.start_all(workspace_root, ctx);
});
return;
}
if self.lsp_installation_status.get(&server_type)
== Some(&LSPInstallationStatus::Installing)
{
return;
}
self.lsp_installation_status
.insert(server_type, LSPInstallationStatus::Installing);
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
server_type,
status: LSPInstallationStatus::Installing,
});
let path_future = LocalShellState::handle(ctx).update(ctx, |shell_state, ctx| {
shell_state.get_interactive_path_env_var(ctx)
});
let http_client = ServerApiProvider::as_ref(ctx).get_http_client();
let file_path_for_install = file_path.clone();
ctx.spawn(
async move {
let path_env_var = path_future.await;
let executor = lsp::CommandBuilder::new(path_env_var.clone());
let candidate = server_type.candidate(http_client.clone());
if !candidate.is_installed(&executor).await {
let metadata = candidate.fetch_latest_server_metadata().await?;
candidate.install(metadata, &executor).await?;
}
let schema_path = crate::settings::schema_export::ensure_runtime_settings_schema()?;
let schema_uri = url::Url::from_file_path(&schema_path)
.map_err(|()| anyhow::anyhow!("Invalid settings schema path: {}", schema_path.display()))?;
let file_match = file_path_for_install.to_string_lossy().into_owned();
Ok::<_, anyhow::Error>((
path_env_var,
schema_uri.to_string(),
file_match,
))
},
move |me, result, ctx| match result {
Ok((path_env_var, schema_uri, file_match)) => {
me.lsp_installation_status
.insert(server_type, LSPInstallationStatus::Installed);
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
server_type,
status: LSPInstallationStatus::Installed,
});
let log_relative_path =
crate::code::lsp_logs::relative_log_path(server_type, &workspace_root);
let http_client = ServerApiProvider::as_ref(ctx).get_http_client();
let config = LspServerConfig::new(
server_type,
workspace_root.clone(),
path_env_var,
ChannelState::app_id().application_name().to_string(),
http_client,
)
.with_log_relative_path(log_relative_path)
.with_post_initialize_notification(
"tombi/associateSchema",
serde_json::json!({
"title": "Galaxy Settings",
"description": "Galaxy's generated settings schema",
"uri": schema_uri,
"fileMatch": [file_match],
"tomlVersion": "v1.1.0",
"force": true,
}),
);
let manager = LspManagerModel::handle(ctx);
manager.update(ctx, |manager, ctx| {
manager.register(workspace_root.clone(), config, ctx);
});
let workspace_root_display = workspace_root.display().to_string();
if let Some(server) = manager
.as_ref(ctx)
.servers_for_workspace(&workspace_root)
.and_then(|servers| servers.last())
.cloned()
{
ctx.subscribe_to_model(&server, move |_, _, event, ctx| {
if let LspEvent::Failed(error) = event {
if let Some(window_id) = WindowManager::as_ref(ctx).active_window() {
ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| {
toast_stack.add_ephemeral_toast(
DismissibleToast::error(format!(
"Failed to start TOML language support for {workspace_root_display}: {error}"
)),
window_id,
ctx,
);
});
}
}
});
}
manager.update(ctx, |manager, ctx| {
manager.start_all(workspace_root, ctx);
});
}
Err(error) => {
log::warn!("Failed to prepare TOML language support: {error:#}");
me.lsp_installation_status
.insert(server_type, LSPInstallationStatus::NotInstalled);
ctx.emit(PersistedWorkspaceEvent::InstallStatusUpdate {
server_type,
status: LSPInstallationStatus::NotInstalled,
});
if let Some(window_id) = WindowManager::as_ref(ctx).active_window() {
ToastStack::handle(ctx).update(ctx, |toast_stack, ctx| {
toast_stack.add_ephemeral_toast(
DismissibleToast::error(format!(
"Failed to prepare TOML language support: {error}"
)),
window_id,
ctx,
);
});
}
}
},
);
}
/// Starts all enabled LSP servers for the given file path.
/// This looks up the workspace root and starts any servers that are enabled but not yet running.
#[cfg(feature = "local_fs")]