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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")]
|
||||
|
||||
Reference in New Issue
Block a user