v1.4.0: Auto-compact streaming, Bedrock summarization support, subagent orchestration, and Galaxy rebrand continuation
Major features: - Auto-compact: triggers conversation summarization when context window >= 85%, compacts Bedrock message history to a summary pair, and tracks live context tokens - Bedrock summarization: plumbs `is_summarization` flag through translator/client/response pipeline, handles SummarizeConversation input type, and marks `summarized` in metadata - Session restore: rebuilds bedrock_message_history from persisted task messages via newly-public `convert_proto_message`, preventing empty history on reconnect - Subagent orchestration: adds SubagentQuestion/Answer/CompletionSummary event types, parent-child question routing with depth limits, retry counting, and drain methods - Summarization UI: inline SummarizationView in AI blocks with progress/finished states Refactors: - Rename WarpTheme → GalaxyTheme across ~100 files (rebrand continuation) - Rename warp_home_config_dir → galaxy_home_config_dir and related path functions - Predefined rules: replace "System Defined Rule #N" with descriptive names (e.g. "Correctness Over Speed", "Never Guess") and add lookup helpers - Usage view: replace cumulative input/output token display with live context tokens, cache hit rate calculation, and separate cache read/write stats - Telemetry: remove verbose doc comments, simplify trait definitions - Facts view: simplify delete permission check (always allow local deletion) - Remove warp_managed_paths_watcher.rs (dead code) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
eaa2ddc75e
commit
6f54e2cb30
Generated
+1
-1
@@ -5191,7 +5191,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "galaxy"
|
||||
version = "1.2.1"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"aho-corasick",
|
||||
|
||||
+1
-1
@@ -394,7 +394,7 @@ fn embed_resource_file(target_dir: &Path) {
|
||||
use std::io::Write;
|
||||
|
||||
let version = env::var("GIT_RELEASE_TAG").unwrap_or("v0".to_owned());
|
||||
let app_name = env::var("WARP_APP_NAME").unwrap_or("Warp".to_owned());
|
||||
let app_name = env::var("GALAXY_APP_NAME").unwrap_or("Galaxy".to_owned());
|
||||
let bin_name = env::var("CARGO_BIN_NAME").unwrap_or("local".to_owned());
|
||||
|
||||
let icon_path = Path::new("channels")
|
||||
|
||||
@@ -139,6 +139,8 @@ pub struct RequestParams {
|
||||
/// can store them back into the conversation for the next request cycle.
|
||||
pub bedrock_messages_sent:
|
||||
std::sync::Arc<std::sync::Mutex<Vec<crate::ai::bedrock::convert::ConversationMessage>>>,
|
||||
/// Whether this request is a conversation summarization/compaction.
|
||||
pub is_summarization: bool,
|
||||
}
|
||||
|
||||
pub type Event = Result<warp_multi_agent_api::ResponseEvent, Arc<AIApiError>>;
|
||||
@@ -327,6 +329,7 @@ impl RequestParams {
|
||||
agent_name: None,
|
||||
bedrock_message_history: Vec::new(),
|
||||
bedrock_messages_sent: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||
is_summarization: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +154,7 @@ pub async fn generate_multi_agent_output(
|
||||
root_task_id: params.root_task_id.clone(),
|
||||
bedrock_message_history: params.bedrock_message_history.clone(),
|
||||
bedrock_messages_sent: params.bedrock_messages_sent.clone(),
|
||||
is_summarization: params.is_summarization,
|
||||
};
|
||||
|
||||
match translator::execute(translator_request, &mut request).await {
|
||||
|
||||
@@ -42,6 +42,7 @@ fn request_params_with_ask_user_question_enabled(ask_user_question_enabled: bool
|
||||
agent_name: None,
|
||||
bedrock_message_history: Vec::new(),
|
||||
bedrock_messages_sent: std::sync::Arc::new(std::sync::Mutex::new(Vec::new())),
|
||||
is_summarization: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ use galaxy_core::features::FeatureFlag;
|
||||
use galaxy_core::send_telemetry_from_ctx;
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::theme::color::internal_colors;
|
||||
use galaxy_core::ui::theme::WarpTheme;
|
||||
use galaxy_core::ui::theme::GalaxyTheme;
|
||||
use galaxyui::color::ColorU;
|
||||
use galaxyui::{EntityId, ModelContext, SingletonEntity};
|
||||
use uuid::Uuid;
|
||||
@@ -235,6 +235,19 @@ pub struct AIConversation {
|
||||
/// tool calls, and tool results sent to/received from Bedrock across all
|
||||
/// request cycles. This is the source of truth for what Bedrock sees.
|
||||
bedrock_message_history: Vec<crate::ai::bedrock::convert::ConversationMessage>,
|
||||
|
||||
/// Live context token count from the most recent Bedrock response.
|
||||
/// This is the actual input_tokens reported by Bedrock — represents the current
|
||||
/// context window size, NOT a cumulative total.
|
||||
current_context_tokens: u32,
|
||||
|
||||
/// Guards against repeated auto-compact triggers within the same high-usage window.
|
||||
/// Set to true when auto-compact fires; reset when summarization completes.
|
||||
has_pending_auto_compact: bool,
|
||||
|
||||
/// Number of times this child agent conversation has been automatically
|
||||
/// restarted after a transient error. Capped at MAX_SUBAGENT_RETRIES.
|
||||
subagent_retry_count: u8,
|
||||
}
|
||||
|
||||
pub(crate) fn artifact_from_fork_proto(
|
||||
@@ -287,6 +300,9 @@ impl AIConversation {
|
||||
is_remote_child: false,
|
||||
last_event_sequence: None,
|
||||
bedrock_message_history: Vec::new(),
|
||||
current_context_tokens: 0,
|
||||
has_pending_auto_compact: false,
|
||||
subagent_retry_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +330,20 @@ impl AIConversation {
|
||||
.cmp(depths.get(a.as_str()).unwrap_or(&0))
|
||||
});
|
||||
|
||||
// Collect all task messages for rebuilding bedrock_message_history on restore.
|
||||
// We must do this before consuming the tasks into exchanges.
|
||||
let all_task_messages: Vec<&api::Message> = api_tasks_by_id
|
||||
.values()
|
||||
.flat_map(|task| task.messages.iter())
|
||||
.collect();
|
||||
let bedrock_message_history: Vec<crate::ai::bedrock::convert::ConversationMessage> =
|
||||
all_task_messages
|
||||
.iter()
|
||||
.filter_map(|msg| {
|
||||
crate::ai::bedrock::request_translator::convert_proto_message(msg)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut api_tasks_and_exchanges_by_id: HashMap<_, _> = api_tasks_by_id
|
||||
.into_iter()
|
||||
.map(|(id, task)| {
|
||||
@@ -469,7 +499,10 @@ impl AIConversation {
|
||||
parent_conversation_id,
|
||||
is_remote_child: false,
|
||||
last_event_sequence,
|
||||
bedrock_message_history: Vec::new(),
|
||||
bedrock_message_history,
|
||||
current_context_tokens: 0,
|
||||
has_pending_auto_compact: false,
|
||||
subagent_retry_count: 0,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -521,6 +554,26 @@ impl AIConversation {
|
||||
self.conversation_usage_metadata.context_window_usage
|
||||
}
|
||||
|
||||
pub fn set_context_window_usage(&mut self, value: f32) {
|
||||
self.conversation_usage_metadata.context_window_usage = value;
|
||||
}
|
||||
|
||||
pub fn current_context_tokens(&self) -> u32 {
|
||||
self.current_context_tokens
|
||||
}
|
||||
|
||||
pub fn set_current_context_tokens(&mut self, tokens: u32) {
|
||||
self.current_context_tokens = tokens;
|
||||
}
|
||||
|
||||
pub fn has_pending_auto_compact(&self) -> bool {
|
||||
self.has_pending_auto_compact
|
||||
}
|
||||
|
||||
pub fn set_has_pending_auto_compact(&mut self, value: bool) {
|
||||
self.has_pending_auto_compact = value;
|
||||
}
|
||||
|
||||
pub fn credits_spent(&self) -> f32 {
|
||||
(self.conversation_usage_metadata.credits_spent * 10.0).round() / 10.0
|
||||
}
|
||||
@@ -859,6 +912,14 @@ impl AIConversation {
|
||||
self.is_remote_child = true;
|
||||
}
|
||||
|
||||
pub fn subagent_retry_count(&self) -> u8 {
|
||||
self.subagent_retry_count
|
||||
}
|
||||
|
||||
pub fn increment_subagent_retry_count(&mut self) {
|
||||
self.subagent_retry_count = self.subagent_retry_count.saturating_add(1);
|
||||
}
|
||||
|
||||
/// Returns a flat list of linearized messages across all tasks, interpolating subtask messages
|
||||
/// in between subagent tool calls and results, effectively corresponding to the order in which
|
||||
/// the messages were created and added to the conversation.
|
||||
@@ -1566,6 +1627,17 @@ impl AIConversation {
|
||||
if was_user_initiated_request {
|
||||
self.last_block_token_usage_by_model.clear();
|
||||
}
|
||||
|
||||
// Update live context token count from this response's input tokens.
|
||||
// This represents the actual current context window size (not cumulative).
|
||||
let live_input: u32 = token_usage
|
||||
.iter()
|
||||
.map(|u| u.total_input + u.input_cache_read + u.input_cache_write)
|
||||
.sum();
|
||||
if live_input > 0 {
|
||||
self.current_context_tokens = live_input;
|
||||
}
|
||||
|
||||
for usage in token_usage.into_iter() {
|
||||
let entry = self
|
||||
.total_token_usage_by_model
|
||||
@@ -1666,6 +1738,7 @@ impl AIConversation {
|
||||
// so we only update the summarized flag if it's going from false to true.
|
||||
if usage_metadata.summarized && !self.conversation_usage_metadata.was_summarized {
|
||||
self.conversation_usage_metadata.was_summarized = usage_metadata.summarized;
|
||||
self.has_pending_auto_compact = false;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -3844,7 +3917,7 @@ impl ConversationStatus {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status_icon_and_color(&self, theme: &WarpTheme) -> (Icon, ColorU) {
|
||||
pub fn status_icon_and_color(&self, theme: &GalaxyTheme) -> (Icon, ColorU) {
|
||||
match self {
|
||||
ConversationStatus::InProgress => (Icon::ClockLoader, theme.ansi_fg_magenta()),
|
||||
ConversationStatus::Success => (Icon::Check, theme.ansi_fg_green()),
|
||||
|
||||
@@ -29,7 +29,7 @@ use galaxy_cli::agent::Harness;
|
||||
use galaxy_core::execution_mode::AppExecutionMode;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxy_core::report_error;
|
||||
use galaxy_core::ui::theme::{color::internal_colors, WarpTheme};
|
||||
use galaxy_core::ui::theme::{color::internal_colors, GalaxyTheme};
|
||||
use galaxyui::color::ColorU;
|
||||
use galaxyui::r#async::Timer;
|
||||
use galaxyui::windowing::{StateEvent, WindowManager};
|
||||
@@ -334,7 +334,7 @@ impl AgentRunDisplayStatus {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn status_icon_and_color(&self, theme: &WarpTheme) -> (Icon, ColorU) {
|
||||
pub fn status_icon_and_color(&self, theme: &GalaxyTheme) -> (Icon, ColorU) {
|
||||
match self {
|
||||
AgentRunDisplayStatus::TaskQueued
|
||||
| AgentRunDisplayStatus::TaskPending
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use galaxy_core::ui::icons::Icon;
|
||||
use galaxy_core::ui::theme::{Fill, WarpTheme};
|
||||
use galaxy_core::ui::theme::{Fill, GalaxyTheme};
|
||||
use galaxyui::clipboard::ClipboardContent;
|
||||
use galaxyui::elements::{
|
||||
ChildView, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, DispatchEventResult,
|
||||
@@ -331,7 +331,7 @@ fn render_timestamp_with_dot(item: &NotificationItem, appearance: &Appearance) -
|
||||
fn render_expand_chevron(
|
||||
expanded: bool,
|
||||
on_click: OnExpandClick,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
) -> Box<dyn Element> {
|
||||
let icon = if expanded {
|
||||
Icon::ChevronDown
|
||||
@@ -411,7 +411,7 @@ const NOTIFICATION_AVATAR_SIZING: IconWithStatusSizing = IconWithStatusSizing {
|
||||
fn render_agent_avatar(
|
||||
agent: NotificationSourceAgent,
|
||||
category: NotificationCategory,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
) -> Box<dyn Element> {
|
||||
let status = notification_category_to_conversation_status(category);
|
||||
let variant = match agent {
|
||||
|
||||
@@ -134,14 +134,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh Warp Drive before executing an operation.
|
||||
/// Refresh Galaxy Drive before executing an operation.
|
||||
pub fn refresh_warp_drive(
|
||||
ctx: &AppContext,
|
||||
) -> impl Future<Output = anyhow::Result<()>> + Send + 'static {
|
||||
UpdateManager::as_ref(ctx)
|
||||
.initial_load_complete()
|
||||
.with_timeout(WARP_DRIVE_SYNC_TIMEOUT)
|
||||
.map_err(|_| anyhow::anyhow!("Timed out waiting for Warp Drive to sync"))
|
||||
.map_err(|_| anyhow::anyhow!("Timed out waiting for Galaxy Drive to sync"))
|
||||
}
|
||||
|
||||
/// Fetch the conversation's server metadata and validate that its harness matches the caller's
|
||||
@@ -213,7 +213,7 @@ pub enum EnvironmentChoice {
|
||||
|
||||
impl EnvironmentChoice {
|
||||
/// Resolve the environment to use when creating an agent integration.
|
||||
/// Warp Drive *must* have been synced first.
|
||||
/// Galaxy Drive *must* have been synced first.
|
||||
pub fn resolve_for_create(
|
||||
args: EnvironmentCreateArgs,
|
||||
ctx: &AppContext,
|
||||
@@ -279,7 +279,7 @@ Without an environment, the agent will not be able to access private repositorie
|
||||
|
||||
/// Resolve the environment to use when updating an agent integration. If the user did not
|
||||
/// request any changes to the environment, this returns `Ok(None)`.
|
||||
/// Warp Drive *must* have been synced first.
|
||||
/// Galaxy Drive *must* have been synced first.
|
||||
pub fn resolve_for_update(
|
||||
args: EnvironmentUpdateArgs,
|
||||
ctx: &AppContext,
|
||||
|
||||
@@ -357,7 +357,7 @@ pub enum AgentDriverError {
|
||||
#[error("Agent profile \"{0}\" not found")]
|
||||
ProfileError(String),
|
||||
#[error(
|
||||
"Failed to authenticate with server - please log in via 'oz login', provide an API key via '--api-key <key>', or set the WARP_API_KEY environment variable"
|
||||
"Failed to authenticate with server - please log in via 'oz login', provide an API key via '--api-key <key>', or set the GALAXY_API_KEY environment variable"
|
||||
)]
|
||||
NotLoggedIn,
|
||||
#[error("Saved prompt not found for id {0}")]
|
||||
@@ -369,7 +369,7 @@ pub enum AgentDriverError {
|
||||
#[source]
|
||||
error: terminal::ShareSessionError,
|
||||
},
|
||||
#[error("Error syncing Warp Drive")]
|
||||
#[error("Error syncing Galaxy Drive")]
|
||||
WarpDriveSyncFailed,
|
||||
#[error("Requested environment not found: {0}")]
|
||||
EnvironmentNotFound(String),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::ai::agent::RenderableAIError;
|
||||
use crate::ai::blocklist::task_status_sync_model::classify_renderable_error;
|
||||
use crate::server::server_api::ai::TaskStatusUpdate;
|
||||
use galaxy_graphql::ai::{AgentTaskState, PlatformErrorCode};
|
||||
@@ -66,7 +67,7 @@ pub fn classify_driver_error(error: &AgentDriverError) -> (AgentTaskState, TaskS
|
||||
AgentDriverError::WarpDriveSyncFailed => (
|
||||
AgentTaskState::Error,
|
||||
TaskStatusUpdate::with_error_code(
|
||||
"Warp Drive failed to sync. Please check your network connection and try again.",
|
||||
"Galaxy Drive failed to sync. Please check your network connection and try again.",
|
||||
PlatformErrorCode::InternalError,
|
||||
),
|
||||
),
|
||||
@@ -76,7 +77,7 @@ pub fn classify_driver_error(error: &AgentDriverError) -> (AgentTaskState, TaskS
|
||||
AgentTaskState::Error,
|
||||
TaskStatusUpdate::with_error_code(
|
||||
format!(
|
||||
"Authentication required. Log in via '{bin} login', provide an API key via '--api-key', or set the WARP_API_KEY environment variable."
|
||||
"Authentication required. Log in via '{bin} login', provide an API key via '--api-key', or set the GALAXY_API_KEY environment variable."
|
||||
),
|
||||
PlatformErrorCode::AuthenticationRequired,
|
||||
),
|
||||
@@ -95,7 +96,7 @@ pub fn classify_driver_error(error: &AgentDriverError) -> (AgentTaskState, TaskS
|
||||
AgentTaskState::Failed,
|
||||
TaskStatusUpdate::with_error_code(
|
||||
format!(
|
||||
"MCP server {uuid} was not found. Verify the server exists in your Warp Drive and the UUID is correct."
|
||||
"MCP server {uuid} was not found. Verify the server exists in your Galaxy Drive and the UUID is correct."
|
||||
),
|
||||
PlatformErrorCode::EnvironmentSetupFailed,
|
||||
),
|
||||
@@ -125,7 +126,7 @@ pub fn classify_driver_error(error: &AgentDriverError) -> (AgentTaskState, TaskS
|
||||
AgentTaskState::Failed,
|
||||
TaskStatusUpdate::with_error_code(
|
||||
format!(
|
||||
"Agent profile \"{name}\" not found. Check the profile ID and ensure it exists in your team's Warp Drive."
|
||||
"Agent profile \"{name}\" not found. Check the profile ID and ensure it exists in your team's Galaxy Drive."
|
||||
),
|
||||
PlatformErrorCode::ResourceNotFound,
|
||||
),
|
||||
@@ -134,7 +135,7 @@ pub fn classify_driver_error(error: &AgentDriverError) -> (AgentTaskState, TaskS
|
||||
AgentTaskState::Failed,
|
||||
TaskStatusUpdate::with_error_code(
|
||||
format!(
|
||||
"Saved prompt not found for ID {id}. Verify the prompt exists in your Warp Drive."
|
||||
"Saved prompt not found for ID {id}. Verify the prompt exists in your Galaxy Drive."
|
||||
),
|
||||
PlatformErrorCode::ResourceNotFound,
|
||||
),
|
||||
@@ -302,6 +303,31 @@ pub fn classify_driver_error(error: &AgentDriverError) -> (AgentTaskState, TaskS
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if an `AgentDriverError` represents a transient condition that
|
||||
/// a subagent can recover from by retrying (e.g., network issues, rate limits,
|
||||
/// server overload). Permanent errors (auth, config, cancelled) return false.
|
||||
pub fn is_self_recoverable(error: &AgentDriverError) -> bool {
|
||||
match error {
|
||||
AgentDriverError::ConversationError { error: renderable } => {
|
||||
matches!(
|
||||
renderable,
|
||||
RenderableAIError::ServerOverloaded
|
||||
| RenderableAIError::Other {
|
||||
will_attempt_resume: true,
|
||||
..
|
||||
}
|
||||
)
|
||||
}
|
||||
AgentDriverError::WarpDriveSyncFailed
|
||||
| AgentDriverError::TeamMetadataRefreshTimeout
|
||||
| AgentDriverError::ShareSessionFailed {
|
||||
error: ShareSessionError::Timeout,
|
||||
..
|
||||
} => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "error_classification_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -194,7 +194,7 @@ impl EnvironmentCommandRunner {
|
||||
ctx.spawn(initial_sync, move |_, result, ctx| {
|
||||
if result.is_err() {
|
||||
super::report_fatal_error(
|
||||
anyhow::anyhow!("Timed out waiting for Warp Drive to sync"),
|
||||
anyhow::anyhow!("Timed out waiting for Galaxy Drive to sync"),
|
||||
ctx,
|
||||
);
|
||||
return;
|
||||
@@ -265,7 +265,7 @@ impl EnvironmentCommandRunner {
|
||||
ctx.spawn(initial_sync, move |_, result, ctx| {
|
||||
if result.is_err() {
|
||||
super::report_fatal_error(
|
||||
anyhow::anyhow!("Timed out waiting for Warp Drive to sync"),
|
||||
anyhow::anyhow!("Timed out waiting for Galaxy Drive to sync"),
|
||||
ctx,
|
||||
);
|
||||
return;
|
||||
@@ -474,7 +474,7 @@ impl EnvironmentCommandRunner {
|
||||
ctx.spawn(initial_sync, move |_, result, ctx| {
|
||||
if result.is_err() {
|
||||
super::report_fatal_error(
|
||||
anyhow::anyhow!("Timed out waiting for Warp Drive to sync"),
|
||||
anyhow::anyhow!("Timed out waiting for Galaxy Drive to sync"),
|
||||
ctx,
|
||||
);
|
||||
return;
|
||||
@@ -856,7 +856,7 @@ impl EnvironmentCommandRunner {
|
||||
ctx.spawn(initial_sync, move |_, result, ctx| {
|
||||
if result.is_err() {
|
||||
super::report_fatal_error(
|
||||
anyhow::anyhow!("Timed out waiting for Warp Drive to sync"),
|
||||
anyhow::anyhow!("Timed out waiting for Galaxy Drive to sync"),
|
||||
ctx,
|
||||
);
|
||||
return;
|
||||
@@ -1035,7 +1035,7 @@ impl EnvironmentCommandRunner {
|
||||
ctx.spawn(initial_sync, move |_, result, ctx| {
|
||||
if result.is_err() {
|
||||
super::report_fatal_error(
|
||||
anyhow::anyhow!("Timed out waiting for Warp Drive to sync"),
|
||||
anyhow::anyhow!("Timed out waiting for Galaxy Drive to sync"),
|
||||
ctx,
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -175,7 +175,7 @@ fn dispatch_command(
|
||||
schedule::run(ctx, global_options, schedule_cmd)
|
||||
}
|
||||
CliCommand::Secret(secret_cmd) => {
|
||||
if !FeatureFlag::WarpManagedSecrets.is_enabled() {
|
||||
if !FeatureFlag::GalaxyManagedSecrets.is_enabled() {
|
||||
return Err(anyhow::anyhow!("invalid value 'secret'"));
|
||||
}
|
||||
secret::run(ctx, global_options, secret_cmd)
|
||||
@@ -547,7 +547,7 @@ impl AgentDriverRunner {
|
||||
// Ensure we've synced team state before starting the driver.
|
||||
Self::refresh_team_metadata(&foreground).await?;
|
||||
|
||||
// Wait for Warp Drive to sync before building the task config, since
|
||||
// Wait for Galaxy Drive to sync before building the task config, since
|
||||
// prompt resolution (SavedPrompt -> workflow lookup) and environment
|
||||
// resolution (CloudAmbientAgentEnvironment lookup) depend on it.
|
||||
if foreground
|
||||
@@ -1328,7 +1328,7 @@ fn launch_command(
|
||||
dispatched = true;
|
||||
let auth_state = AuthStateProvider::handle(ctx).as_ref(ctx).get();
|
||||
let message = if auth_state.is_api_key_authenticated() {
|
||||
"Your API key is invalid. Please provide a valid key via '--api-key' or the WARP_API_KEY environment variable.".to_string()
|
||||
"Your API key is invalid. Please provide a valid key via '--api-key' or the GALAXY_API_KEY environment variable.".to_string()
|
||||
} else {
|
||||
format!("Your credentials are invalid. Please log in again with `{cli_name} login`.")
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ pub fn run(
|
||||
global_options: GlobalOptions,
|
||||
command: SecretCommand,
|
||||
) -> Result<()> {
|
||||
if !FeatureFlag::WarpManagedSecrets.is_enabled() {
|
||||
if !FeatureFlag::GalaxyManagedSecrets.is_enabled() {
|
||||
return Err(anyhow::anyhow!("This feature is not enabled"));
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ static DEFAULT_TIPS: LazyLock<Vec<AgentTip>> = LazyLock::new(|| {
|
||||
description: "Store reusable workflows, notebooks, and prompts in your".to_string(),
|
||||
link: Some("https://docs.warp.dev/knowledge-and-collaboration/warp-drive".to_string()),
|
||||
binding_name: None,
|
||||
action: Some(WorkspaceAction::OpenWarpDrive),
|
||||
action: Some(WorkspaceAction::OpenGalaxyDrive),
|
||||
kind: AgentTipKind::WarpDrive,
|
||||
},
|
||||
AgentTip {
|
||||
@@ -129,7 +129,7 @@ static DEFAULT_TIPS: LazyLock<Vec<AgentTip>> = LazyLock::new(|| {
|
||||
kind: AgentTipKind::General,
|
||||
},
|
||||
AgentTip {
|
||||
description: "`@` to add context from files, blocks, or Warp Drive objects to your prompt.".to_string(),
|
||||
description: "`@` to add context from files, blocks, or Galaxy Drive objects to your prompt.".to_string(),
|
||||
link: Some("https://docs.warp.dev/agent-platform/local-agents/agent-context/using-to-add-context".to_string()),
|
||||
binding_name: None,
|
||||
action: None,
|
||||
@@ -297,7 +297,7 @@ static DEFAULT_TIPS: LazyLock<Vec<AgentTip>> = LazyLock::new(|| {
|
||||
kind: AgentTipKind::General,
|
||||
},
|
||||
AgentTip {
|
||||
description: "`/init` to generate a `WARP.md` file and define project rules for the agent.".to_string(),
|
||||
description: "`/init` to generate a `GALAXY.md` file and define project rules for the agent.".to_string(),
|
||||
link: Some("https://docs.warp.dev/agent-platform/capabilities/rules".to_string()),
|
||||
binding_name: None,
|
||||
action: None,
|
||||
@@ -411,7 +411,7 @@ impl WorkspaceAction {
|
||||
pub fn display_text(&self) -> Option<String> {
|
||||
match self {
|
||||
WorkspaceAction::OpenPalette { .. } => Some("Open palette".to_string()),
|
||||
WorkspaceAction::OpenWarpDrive => Some("Warp Drive.".to_string()),
|
||||
WorkspaceAction::OpenGalaxyDrive => Some("Galaxy Drive.".to_string()),
|
||||
WorkspaceAction::ToggleRightPanel => Some("Show diff view".to_string()),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -610,7 +610,7 @@ impl AIDocumentView {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let ui_builder = appearance.ui_builder().clone();
|
||||
let tooltip = ui_builder
|
||||
.tool_tip("Save and auto-sync this plan to your Warp Drive".to_string())
|
||||
.tool_tip("Save and auto-sync this plan to your Galaxy Drive".to_string())
|
||||
.build()
|
||||
.finish();
|
||||
let sync_button_mouse_state = self.sync_button_mouse_state.clone();
|
||||
@@ -663,7 +663,7 @@ impl AIDocumentView {
|
||||
let color = theme.nonactive_ui_detail().into_solid();
|
||||
let ui_builder = appearance.ui_builder().clone();
|
||||
let tooltip_text =
|
||||
"This plan is synced to your Warp Drive and will auto save any edits you make."
|
||||
"This plan is synced to your Galaxy Drive and will auto save any edits you make."
|
||||
.to_string();
|
||||
let synced_status_mouse_state = self.synced_status_mouse_state.clone();
|
||||
Container::new(
|
||||
@@ -1233,7 +1233,7 @@ impl BackingView for AIDocumentView {
|
||||
.into_item(),
|
||||
);
|
||||
menu_items.push(
|
||||
MenuItemFields::new("Show in Warp Drive")
|
||||
MenuItemFields::new("Show in Galaxy Drive")
|
||||
.with_on_select_action(AIDocumentAction::ShowInWarpDrive)
|
||||
.with_icon(Icon::WarpDrive)
|
||||
.into_item(),
|
||||
|
||||
@@ -4,7 +4,7 @@ use anyhow::anyhow;
|
||||
use chrono::{DateTime, Utc};
|
||||
use galaxy_cli::agent::Harness;
|
||||
use galaxy_core::report_error;
|
||||
use galaxy_core::ui::theme::WarpTheme;
|
||||
use galaxy_core::ui::theme::GalaxyTheme;
|
||||
use galaxyui::color::ColorU;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
@@ -406,7 +406,7 @@ impl AmbientAgentTaskState {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status_icon_and_color(&self, theme: &WarpTheme) -> (Icon, ColorU) {
|
||||
pub fn status_icon_and_color(&self, theme: &GalaxyTheme) -> (Icon, ColorU) {
|
||||
match self {
|
||||
AmbientAgentTaskState::Queued
|
||||
| AmbientAgentTaskState::Pending
|
||||
|
||||
@@ -140,6 +140,7 @@ impl BedrockClient {
|
||||
user_query: Option<String>,
|
||||
diagnostic_logger: Option<Arc<BedrockDiagnosticLogger>>,
|
||||
messages_sent: Arc<Mutex<Vec<ConversationMessage>>>,
|
||||
is_summarization: bool,
|
||||
) -> Result<ResponseStream, BedrockError> {
|
||||
let effective_model_id = if cross_region_inference {
|
||||
apply_cross_region_prefix(model_id, &self.region)
|
||||
@@ -228,6 +229,7 @@ impl BedrockClient {
|
||||
diagnostic_logger,
|
||||
messages_sent,
|
||||
effective_model_id,
|
||||
is_summarization,
|
||||
)))
|
||||
}
|
||||
|
||||
|
||||
@@ -175,6 +175,20 @@ pub fn extract_new_input_messages(request: &api::Request) -> Vec<ConversationMes
|
||||
),
|
||||
});
|
||||
}
|
||||
api::request::input::Type::SummarizeConversation(summarize) => {
|
||||
let prompt = if summarize.prompt.is_empty() {
|
||||
"Please summarize this conversation so far, preserving key decisions, \
|
||||
code changes, and important context. Be concise but retain all \
|
||||
information needed to continue the work."
|
||||
.to_string()
|
||||
} else {
|
||||
summarize.prompt.clone()
|
||||
};
|
||||
results.push(ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text(prompt),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -378,6 +392,30 @@ fn extract_input_messages(request: &api::Request) -> Vec<api::Message> {
|
||||
});
|
||||
}
|
||||
}
|
||||
api::request::input::Type::SummarizeConversation(summarize) => {
|
||||
let prompt = if summarize.prompt.is_empty() {
|
||||
"Please summarize this conversation so far, preserving key decisions, \
|
||||
code changes, and important context. Be concise but retain all \
|
||||
information needed to continue the work."
|
||||
.to_string()
|
||||
} else {
|
||||
summarize.prompt.clone()
|
||||
};
|
||||
results.push(api::Message {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
task_id: task_id.clone(),
|
||||
request_id: String::new(),
|
||||
timestamp: None,
|
||||
server_message_data: String::new(),
|
||||
citations: vec![],
|
||||
message: Some(api::message::Message::UserQuery(
|
||||
api::message::UserQuery {
|
||||
query: prompt,
|
||||
..Default::default()
|
||||
},
|
||||
)),
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
@@ -1171,8 +1209,9 @@ pub fn extract_messages_from_request(request: &api::Request) -> Vec<Conversation
|
||||
messages
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn convert_proto_message_for_test(msg: &api::Message) -> Option<ConversationMessage> {
|
||||
/// Converts a proto `api::Message` into a `ConversationMessage` for the Bedrock message history.
|
||||
/// Used to rebuild the message history from persisted task messages on session restore.
|
||||
pub fn convert_proto_message(msg: &api::Message) -> Option<ConversationMessage> {
|
||||
let message_content = msg.message.as_ref()?;
|
||||
match message_content {
|
||||
api::message::Message::UserQuery(query) => Some(ConversationMessage {
|
||||
@@ -1184,7 +1223,7 @@ fn convert_proto_message_for_test(msg: &api::Message) -> Option<ConversationMess
|
||||
content: MessageContent::Text(output.text.clone()),
|
||||
}),
|
||||
api::message::Message::ToolCall(tool_call) => {
|
||||
let (name, input) = extract_tool_call_info_for_test(tool_call);
|
||||
let (name, input) = extract_tool_call_info(tool_call);
|
||||
Some(ConversationMessage {
|
||||
role: MessageRole::Assistant,
|
||||
content: MessageContent::ToolUse {
|
||||
@@ -1214,11 +1253,8 @@ fn convert_proto_message_for_test(msg: &api::Message) -> Option<ConversationMess
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(deprecated)]
|
||||
fn extract_tool_call_info_for_test(
|
||||
tool_call: &api::message::ToolCall,
|
||||
) -> (String, serde_json::Value) {
|
||||
fn extract_tool_call_info(tool_call: &api::message::ToolCall) -> (String, serde_json::Value) {
|
||||
if let Some(tool) = &tool_call.tool {
|
||||
match tool {
|
||||
api::message::tool_call::Tool::RunShellCommand(cmd) => (
|
||||
@@ -1254,6 +1290,12 @@ fn extract_tool_call_info_for_test(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn convert_proto_message_for_test(msg: &api::Message) -> Option<ConversationMessage> {
|
||||
convert_proto_message(msg)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "request_translator_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -65,6 +65,7 @@ pub fn bedrock_stream_to_response_events(
|
||||
diagnostic_logger: Option<Arc<BedrockDiagnosticLogger>>,
|
||||
messages_sent: Arc<Mutex<Vec<ConversationMessage>>>,
|
||||
model_id: String,
|
||||
is_summarization: bool,
|
||||
) -> BoxStream<'static, Event> {
|
||||
let request_id = Uuid::new_v4().to_string();
|
||||
let conversation_id = Uuid::new_v4().to_string();
|
||||
@@ -470,6 +471,7 @@ pub fn bedrock_stream_to_response_events(
|
||||
cache_read_input_tokens,
|
||||
cache_write_input_tokens,
|
||||
&model_id,
|
||||
is_summarization,
|
||||
);
|
||||
yield Ok(finished_event);
|
||||
};
|
||||
@@ -555,6 +557,7 @@ pub(super) fn build_stream_finished(
|
||||
cache_read_input_tokens: i32,
|
||||
cache_write_input_tokens: i32,
|
||||
model_id: &str,
|
||||
is_summarization: bool,
|
||||
) -> ResponseEvent {
|
||||
let total_tokens =
|
||||
(input_tokens + output_tokens + cache_read_input_tokens + cache_write_input_tokens) as u32;
|
||||
@@ -598,7 +601,7 @@ pub(super) fn build_stream_finished(
|
||||
#[allow(deprecated)]
|
||||
let conversation_usage_metadata = Some(stream_finished::ConversationUsageMetadata {
|
||||
context_window_usage: context_usage,
|
||||
summarized: false,
|
||||
summarized: is_summarization,
|
||||
credits_spent: 0.0,
|
||||
token_usage: vec![],
|
||||
tool_usage_metadata: None,
|
||||
|
||||
@@ -14,6 +14,7 @@ pub struct TranslatorRequest {
|
||||
pub root_task_id: Option<String>,
|
||||
pub bedrock_message_history: Vec<ConversationMessage>,
|
||||
pub bedrock_messages_sent: Arc<Mutex<Vec<ConversationMessage>>>,
|
||||
pub is_summarization: bool,
|
||||
}
|
||||
|
||||
pub async fn execute(
|
||||
@@ -100,6 +101,7 @@ pub async fn execute(
|
||||
user_query_text,
|
||||
diagnostic_logger,
|
||||
params.bedrock_messages_sent.clone(),
|
||||
params.is_summarization,
|
||||
)
|
||||
.await?;
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::ai::agent::ReadSkillResult;
|
||||
use crate::ai::agent::{AIAgentAction, AIAgentActionId, AIAgentActionType};
|
||||
use crate::ai::blocklist::action_model::AIConversationId;
|
||||
use crate::ai::skills::SkillManager;
|
||||
use crate::warp_managed_paths_watcher::WarpManagedPathsWatcher;
|
||||
use crate::galaxy_managed_paths_watcher::GalaxyManagedPathsWatcher;
|
||||
use ai::skills::{parse_skill, SkillReference};
|
||||
use galaxyui::App;
|
||||
use repo_metadata::{
|
||||
@@ -22,7 +22,7 @@ fn initialize_app(app: &mut App) {
|
||||
app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(SkillManager::new);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ mod inline_agent_view_header;
|
||||
// TODO: Move orchestration_conversation_links module import elsewhere.
|
||||
pub(crate) mod orchestration_conversation_links;
|
||||
pub mod shortcuts;
|
||||
pub(crate) mod subagent_inline_panel;
|
||||
mod zero_state_block;
|
||||
|
||||
pub use agent_input_footer::*;
|
||||
|
||||
@@ -0,0 +1,343 @@
|
||||
//! Inline subagent panel rendered within the parent agent's chat flow.
|
||||
//!
|
||||
//! Shows a collapsible panel with the subagent's status, a mini-transcript of
|
||||
//! recent messages, and controls to expand to full view or cancel.
|
||||
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxyui::elements::{
|
||||
ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Element, Empty, Flex,
|
||||
MainAxisAlignment, MainAxisSize, MouseStateHandle, ParentElement, Radius, Shrinkable, Text,
|
||||
};
|
||||
use galaxyui::{AppContext, SingletonEntity};
|
||||
use pathfinder_color::ColorU;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::agent::conversation::{AIConversationId, ConversationStatus};
|
||||
use crate::ai::blocklist::inline_action::inline_action_header::{
|
||||
ICON_MARGIN, INLINE_ACTION_HEADER_VERTICAL_PADDING, INLINE_ACTION_HORIZONTAL_PADDING,
|
||||
};
|
||||
use crate::ai::blocklist::inline_action::inline_action_icons::icon_size;
|
||||
use crate::ai::blocklist::BlocklistAIHistoryModel;
|
||||
use crate::appearance::Appearance;
|
||||
use crate::ui_components::blended_colors;
|
||||
use crate::ui_components::icons::Icon;
|
||||
|
||||
const MINI_TRANSCRIPT_MAX_LINES: usize = 8;
|
||||
const PANEL_MAX_HEIGHT: f32 = 200.;
|
||||
const PANEL_CORNER_RADIUS: f32 = 8.;
|
||||
|
||||
/// State for a single subagent inline panel instance.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SubagentPanelState {
|
||||
pub conversation_id: AIConversationId,
|
||||
pub is_expanded: bool,
|
||||
pub header_mouse_state: MouseStateHandle,
|
||||
pub expand_button_mouse_state: MouseStateHandle,
|
||||
pub cancel_button_mouse_state: MouseStateHandle,
|
||||
}
|
||||
|
||||
impl SubagentPanelState {
|
||||
pub fn new(conversation_id: AIConversationId) -> Self {
|
||||
Self {
|
||||
conversation_id,
|
||||
is_expanded: false,
|
||||
header_mouse_state: MouseStateHandle::default(),
|
||||
expand_button_mouse_state: MouseStateHandle::default(),
|
||||
cancel_button_mouse_state: MouseStateHandle::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Renders the inline subagent panel for a child conversation.
|
||||
pub fn render_subagent_inline_panel(
|
||||
state: &SubagentPanelState,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let theme = appearance.theme();
|
||||
|
||||
let history_model = BlocklistAIHistoryModel::as_ref(app);
|
||||
let Some(conversation) = history_model.conversation(&state.conversation_id) else {
|
||||
return Empty::new().finish();
|
||||
};
|
||||
|
||||
let status = conversation.status().clone();
|
||||
let agent_name = conversation
|
||||
.agent_name()
|
||||
.unwrap_or("Subagent")
|
||||
.to_string();
|
||||
|
||||
let panel_bg = blended_colors::neutral_2(theme);
|
||||
|
||||
let mut column = Flex::column().with_cross_axis_alignment(CrossAxisAlignment::Stretch);
|
||||
|
||||
// Header — always visible
|
||||
column.add_child(render_panel_header(
|
||||
&agent_name,
|
||||
&status,
|
||||
state,
|
||||
panel_bg,
|
||||
app,
|
||||
));
|
||||
|
||||
// Body (mini-transcript) — only when expanded
|
||||
if state.is_expanded {
|
||||
let transcript_lines = collect_mini_transcript(&state.conversation_id, app);
|
||||
if !transcript_lines.is_empty() {
|
||||
column.add_child(render_mini_transcript(&transcript_lines, panel_bg, app));
|
||||
}
|
||||
}
|
||||
|
||||
// Footer — show summary when complete
|
||||
if status.is_done() {
|
||||
if let Some(summary) = get_completion_summary(&state.conversation_id, app) {
|
||||
column.add_child(render_summary_footer(&summary, panel_bg, app));
|
||||
}
|
||||
}
|
||||
|
||||
Container::new(column.finish())
|
||||
.with_background_color(panel_bg)
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(PANEL_CORNER_RADIUS)))
|
||||
.with_margin_top(4.)
|
||||
.with_margin_bottom(4.)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_panel_header(
|
||||
agent_name: &str,
|
||||
status: &ConversationStatus,
|
||||
state: &SubagentPanelState,
|
||||
_background: ColorU,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let theme = appearance.theme();
|
||||
let font_family = appearance.ui_font_family();
|
||||
let font_size = appearance.monospace_font_size();
|
||||
let surface = theme.surface_2();
|
||||
|
||||
let mut header_row = Flex::row()
|
||||
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||
.with_main_axis_size(MainAxisSize::Max)
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
// Left: status icon + agent name + status text
|
||||
let mut left_side = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
let (icon, icon_color) = status.status_icon_and_color(theme);
|
||||
let status_icon_element = ConstrainedBox::new(
|
||||
galaxyui::elements::Icon::new(icon.into(), icon_color).finish(),
|
||||
)
|
||||
.with_width(icon_size(app))
|
||||
.with_height(icon_size(app))
|
||||
.finish();
|
||||
|
||||
left_side.add_child(
|
||||
Container::new(status_icon_element)
|
||||
.with_margin_right(ICON_MARGIN)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
let name_color: ColorU = theme.main_text_color(surface).into();
|
||||
left_side.add_child(
|
||||
Text::new_inline(agent_name.to_string(), font_family, font_size)
|
||||
.with_color(name_color)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
let status_text = match status {
|
||||
ConversationStatus::InProgress => "Working...",
|
||||
ConversationStatus::Success => "Complete",
|
||||
ConversationStatus::Error => "Error",
|
||||
ConversationStatus::Cancelled => "Cancelled",
|
||||
ConversationStatus::Blocked { .. } => "Blocked",
|
||||
};
|
||||
let status_text_color = blended_colors::text_disabled(theme, surface);
|
||||
left_side.add_child(
|
||||
Container::new(
|
||||
Text::new_inline(status_text.to_string(), font_family, font_size)
|
||||
.with_color(status_text_color)
|
||||
.finish(),
|
||||
)
|
||||
.with_margin_left(8.)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
header_row.add_child(Shrinkable::new(1.0, left_side.finish()).finish());
|
||||
|
||||
// Right: collapse/expand chevron
|
||||
let mut right_side = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
let chevron_icon = if state.is_expanded {
|
||||
Icon::ChevronDown
|
||||
} else {
|
||||
Icon::ChevronRight
|
||||
};
|
||||
let chevron_color = blended_colors::text_disabled(theme, surface);
|
||||
let chevron = ConstrainedBox::new(
|
||||
galaxyui::elements::Icon::new(chevron_icon.into(), chevron_color).finish(),
|
||||
)
|
||||
.with_width(icon_size(app))
|
||||
.with_height(icon_size(app))
|
||||
.finish();
|
||||
right_side.add_child(
|
||||
Container::new(chevron)
|
||||
.with_margin_right(4.)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
header_row.add_child(right_side.finish());
|
||||
|
||||
Container::new(header_row.finish())
|
||||
.with_padding_left(INLINE_ACTION_HORIZONTAL_PADDING)
|
||||
.with_padding_right(INLINE_ACTION_HORIZONTAL_PADDING)
|
||||
.with_padding_top(INLINE_ACTION_HEADER_VERTICAL_PADDING)
|
||||
.with_padding_bottom(INLINE_ACTION_HEADER_VERTICAL_PADDING)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn collect_mini_transcript(
|
||||
conversation_id: &AIConversationId,
|
||||
app: &AppContext,
|
||||
) -> Vec<String> {
|
||||
let history_model = BlocklistAIHistoryModel::as_ref(app);
|
||||
let Some(conversation) = history_model.conversation(conversation_id) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let mut lines = Vec::new();
|
||||
let messages = conversation.all_linearized_messages();
|
||||
for msg in messages.iter().rev().take(MINI_TRANSCRIPT_MAX_LINES * 2) {
|
||||
if let Some(text) = extract_message_text(msg) {
|
||||
let truncated = if text.len() > 120 {
|
||||
format!("{}...", &text[..117])
|
||||
} else {
|
||||
text
|
||||
};
|
||||
lines.push(truncated);
|
||||
if lines.len() >= MINI_TRANSCRIPT_MAX_LINES {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.reverse();
|
||||
lines
|
||||
}
|
||||
|
||||
fn extract_message_text(msg: &api::Message) -> Option<String> {
|
||||
let message_content = msg.message.as_ref()?;
|
||||
match message_content {
|
||||
api::message::Message::AgentOutput(output) => {
|
||||
if output.text.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(output.text.clone())
|
||||
}
|
||||
}
|
||||
api::message::Message::UserQuery(query) => {
|
||||
if query.query.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(query.query.clone())
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn render_mini_transcript(
|
||||
lines: &[String],
|
||||
background: ColorU,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let theme = appearance.theme();
|
||||
let text_color = blended_colors::text_disabled(theme, background);
|
||||
let font_family = appearance.ui_font_family();
|
||||
let font_size = appearance.monospace_font_size() - 1.;
|
||||
|
||||
let mut column = Flex::column();
|
||||
for line in lines {
|
||||
let prefixed = format!("> {line}");
|
||||
column.add_child(
|
||||
Text::new_inline(prefixed, font_family, font_size)
|
||||
.with_color(text_color)
|
||||
.finish(),
|
||||
);
|
||||
}
|
||||
|
||||
ConstrainedBox::new(
|
||||
Container::new(column.finish())
|
||||
.with_padding_left(INLINE_ACTION_HORIZONTAL_PADDING)
|
||||
.with_padding_right(INLINE_ACTION_HORIZONTAL_PADDING)
|
||||
.with_padding_top(4.)
|
||||
.with_padding_bottom(4.)
|
||||
.finish(),
|
||||
)
|
||||
.with_max_height(PANEL_MAX_HEIGHT)
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn get_completion_summary(
|
||||
conversation_id: &AIConversationId,
|
||||
app: &AppContext,
|
||||
) -> Option<String> {
|
||||
let history_model = BlocklistAIHistoryModel::as_ref(app);
|
||||
let conversation = history_model.conversation(conversation_id)?;
|
||||
|
||||
if !conversation.status().is_done() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let messages = conversation.all_linearized_messages();
|
||||
for msg in messages.iter().rev() {
|
||||
if let Some(text) = extract_message_text(msg) {
|
||||
if !text.is_empty() {
|
||||
let truncated = if text.len() > 300 {
|
||||
format!("{}...", &text[..297])
|
||||
} else {
|
||||
text
|
||||
};
|
||||
return Some(truncated);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn render_summary_footer(
|
||||
summary: &str,
|
||||
_background: ColorU,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let theme = appearance.theme();
|
||||
let surface = theme.surface_2();
|
||||
let text_color: ColorU = theme.main_text_color(surface).into();
|
||||
let label_color = blended_colors::text_disabled(theme, surface);
|
||||
let font_family = appearance.ui_font_family();
|
||||
let font_size = appearance.monospace_font_size();
|
||||
|
||||
let mut row = Flex::row().with_cross_axis_alignment(CrossAxisAlignment::Start);
|
||||
row.add_child(
|
||||
Text::new_inline("Summary: ".to_string(), font_family, font_size)
|
||||
.with_color(label_color)
|
||||
.finish(),
|
||||
);
|
||||
row.add_child(
|
||||
Shrinkable::new(
|
||||
1.0,
|
||||
Text::new_inline(summary.to_string(), font_family, font_size)
|
||||
.with_color(text_color)
|
||||
.finish(),
|
||||
)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
Container::new(row.finish())
|
||||
.with_padding_left(INLINE_ACTION_HORIZONTAL_PADDING)
|
||||
.with_padding_right(INLINE_ACTION_HORIZONTAL_PADDING)
|
||||
.with_padding_top(6.)
|
||||
.with_padding_bottom(6.)
|
||||
.finish()
|
||||
}
|
||||
@@ -90,6 +90,7 @@ use crate::ai::blocklist::inline_action::aws_bedrock_credentials_error::{
|
||||
use crate::ai::blocklist::inline_action::search_codebase::{
|
||||
SearchCodebaseView, SearchCodebaseViewEvent,
|
||||
};
|
||||
use crate::ai::blocklist::inline_action::summarization::SummarizationView;
|
||||
use crate::ai::blocklist::inline_action::web_fetch::WebFetchView;
|
||||
use crate::ai::blocklist::inline_action::web_search::WebSearchView;
|
||||
use crate::ai::facts::{AIFact, AIMemory, CloudAIFactModel};
|
||||
@@ -830,6 +831,9 @@ pub struct AIBlock {
|
||||
/// Map from web fetch message IDs to their view handles.
|
||||
web_fetch_views: HashMap<MessageId, ViewHandle<WebFetchView>>,
|
||||
|
||||
/// Map from summarization message IDs to their view handles.
|
||||
summarization_views: HashMap<MessageId, ViewHandle<SummarizationView>>,
|
||||
|
||||
/// Map from todo list IDs to their states.
|
||||
todo_list_states: HashMap<MessageId, TodoListElementState>,
|
||||
|
||||
@@ -1340,6 +1344,7 @@ impl AIBlock {
|
||||
search_codebase_view: Default::default(),
|
||||
web_search_views: Default::default(),
|
||||
web_fetch_views: Default::default(),
|
||||
summarization_views: Default::default(),
|
||||
requested_commands_to_auto_collapse: Default::default(),
|
||||
review_changes_button,
|
||||
open_all_comments_button,
|
||||
@@ -1801,6 +1806,9 @@ impl AIBlock {
|
||||
self.handle_web_fetch_messages(&output.messages, ctx);
|
||||
}
|
||||
|
||||
self.handle_summarization_messages(&output.messages, ctx);
|
||||
self.maybe_create_summarization_view_from_input(ctx);
|
||||
|
||||
for action in output.actions() {
|
||||
let new_action_ids: HashSet<AIAgentActionId> =
|
||||
output.actions().map(|action| action.id.clone()).collect();
|
||||
@@ -3489,6 +3497,84 @@ impl AIBlock {
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_summarization_messages(
|
||||
&mut self,
|
||||
messages: &[AIAgentOutputMessage],
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
use crate::ai::agent::SummarizationType;
|
||||
|
||||
for message in messages {
|
||||
let AIAgentOutputMessageType::Summarization {
|
||||
finished_duration,
|
||||
summarization_type,
|
||||
..
|
||||
} = &message.message
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if !matches!(summarization_type, SummarizationType::ConversationSummary) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(view) = self.summarization_views.get(&message.id) {
|
||||
if finished_duration.is_some() {
|
||||
view.update(ctx, |view, ctx| {
|
||||
view.mark_finished();
|
||||
ctx.notify();
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let is_finished = finished_duration.is_some();
|
||||
let view = ctx.add_view(|ctx| {
|
||||
let mut v = SummarizationView::new(ctx);
|
||||
if is_finished {
|
||||
v.mark_finished();
|
||||
}
|
||||
v
|
||||
});
|
||||
self.summarization_views.insert(message.id.clone(), view);
|
||||
ctx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a SummarizationView when the exchange input is a SummarizeConversation.
|
||||
/// This handles the Bedrock path where no Summarization output message is emitted.
|
||||
fn maybe_create_summarization_view_from_input(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
let is_summarize_input = self
|
||||
.model
|
||||
.inputs_to_render(ctx)
|
||||
.iter()
|
||||
.any(|i| matches!(i, AIAgentInput::SummarizeConversation { .. }));
|
||||
|
||||
if !is_summarize_input {
|
||||
return;
|
||||
}
|
||||
|
||||
let key = MessageId::new("__summarization_inline_view__".to_string());
|
||||
if self.summarization_views.contains_key(&key) {
|
||||
// Already created — check if we should mark it finished
|
||||
let is_complete = !self.model.status(ctx).is_streaming();
|
||||
if is_complete {
|
||||
if let Some(view) = self.summarization_views.get(&key) {
|
||||
view.update(ctx, |view, ctx| {
|
||||
if !view.is_finished {
|
||||
view.mark_finished();
|
||||
ctx.notify();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let view = ctx.add_view(|ctx| SummarizationView::new(ctx));
|
||||
self.summarization_views.insert(key, view);
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
/// Note this is called when the search codebase tool call definition finishes streaming, not when the search actually completes.
|
||||
fn handle_search_codebase_complete(
|
||||
&mut self,
|
||||
|
||||
@@ -86,7 +86,7 @@ use galaxy_core::ui::color::contrast::{
|
||||
foreground_color_with_minimum_contrast, MinimumAllowedContrast,
|
||||
};
|
||||
use galaxy_core::ui::color::Rgb;
|
||||
use galaxy_core::ui::theme::{Fill, WarpTheme};
|
||||
use galaxy_core::ui::theme::{Fill, GalaxyTheme};
|
||||
use galaxyui::elements::{Highlight, HighlightedRange, Text};
|
||||
use galaxyui::fonts::Properties;
|
||||
use galaxyui::platform::Cursor;
|
||||
@@ -433,7 +433,7 @@ pub(crate) fn add_highlights_to_rich_text(
|
||||
find_context: Option<FindContext<'_>>,
|
||||
location_index: usize,
|
||||
line_count: usize,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
is_selecting: bool,
|
||||
is_action: bool,
|
||||
app: &AppContext,
|
||||
@@ -1080,6 +1080,7 @@ impl View for AIBlock {
|
||||
search_codebase_view: &self.search_codebase_view,
|
||||
web_search_views: &self.web_search_views,
|
||||
web_fetch_views: &self.web_fetch_views,
|
||||
summarization_views: &self.summarization_views,
|
||||
review_changes_button: &self.review_changes_button,
|
||||
open_all_comments_button: &self.open_all_comments_button,
|
||||
dismiss_suggestion_button: &self.dismiss_suggestion_button,
|
||||
|
||||
@@ -88,6 +88,7 @@ use crate::{
|
||||
},
|
||||
requested_command::RequestedCommand,
|
||||
search_codebase::SearchCodebaseView,
|
||||
summarization::SummarizationView,
|
||||
suggested_unit_tests::SuggestedUnitTestsView,
|
||||
web_fetch::WebFetchView,
|
||||
web_search::WebSearchView,
|
||||
@@ -177,6 +178,7 @@ pub(crate) struct Props<'a> {
|
||||
pub(super) search_codebase_view: &'a HashMap<AIAgentActionId, ViewHandle<SearchCodebaseView>>,
|
||||
pub(super) web_search_views: &'a HashMap<MessageId, ViewHandle<WebSearchView>>,
|
||||
pub(super) web_fetch_views: &'a HashMap<MessageId, ViewHandle<WebFetchView>>,
|
||||
pub(super) summarization_views: &'a HashMap<MessageId, ViewHandle<SummarizationView>>,
|
||||
pub(super) review_changes_button: &'a ViewHandle<ActionButton>,
|
||||
pub(super) open_all_comments_button: &'a ViewHandle<ActionButton>,
|
||||
pub(super) dismiss_suggestion_button: &'a ViewHandle<ActionButton>,
|
||||
@@ -210,6 +212,23 @@ pub(super) fn render(props: Props, app: &AppContext) -> Box<dyn Element> {
|
||||
let conversation_status = props.model.conversation(app).map(|c| c.status());
|
||||
let is_conversation_in_progress = conversation_status.is_some_and(|s| s.is_in_progress());
|
||||
|
||||
// If this is a summarization request, render the inline SummarizationView at the top
|
||||
// regardless of output status. This handles the Bedrock path where no Summarization
|
||||
// output message type is emitted.
|
||||
let is_summarize_input = props
|
||||
.model
|
||||
.inputs_to_render(app)
|
||||
.iter()
|
||||
.any(|i| matches!(i, AIAgentInput::SummarizeConversation { .. }));
|
||||
if is_summarize_input {
|
||||
let key = crate::ai::agent::MessageId::new(
|
||||
"__summarization_inline_view__".to_string(),
|
||||
);
|
||||
if let Some(summarization_view) = props.summarization_views.get(&key) {
|
||||
output_items.add_child(ChildView::new(summarization_view).finish());
|
||||
}
|
||||
}
|
||||
|
||||
let status = props.model.status(app);
|
||||
match status {
|
||||
// Ignore errors if the response is not yet complete-- it could be a deserialization
|
||||
@@ -807,21 +826,28 @@ pub(super) fn render(props: Props, app: &AppContext) -> Box<dyn Element> {
|
||||
} if matches!(
|
||||
summarization_type,
|
||||
SummarizationType::ConversationSummary
|
||||
) && !are_all_text_sections_empty(&text.sections) =>
|
||||
) =>
|
||||
{
|
||||
let header_text = "Conversation summarized".to_string();
|
||||
if let Some(element) = render_collapsible_block(
|
||||
output_message,
|
||||
header_text,
|
||||
&text.sections,
|
||||
finished_duration.is_some(),
|
||||
props,
|
||||
&mut has_rendered_first_text_section,
|
||||
&mut text_section_index,
|
||||
&mut code_section_index,
|
||||
app,
|
||||
) {
|
||||
output_items.add_child(element);
|
||||
if let Some(summarization_view) =
|
||||
props.summarization_views.get(&output_message.id)
|
||||
{
|
||||
output_items
|
||||
.add_child(ChildView::new(summarization_view).finish());
|
||||
} else if !are_all_text_sections_empty(&text.sections) {
|
||||
let header_text = "Conversation summarized".to_string();
|
||||
if let Some(element) = render_collapsible_block(
|
||||
output_message,
|
||||
header_text,
|
||||
&text.sections,
|
||||
finished_duration.is_some(),
|
||||
props,
|
||||
&mut has_rendered_first_text_section,
|
||||
&mut text_section_index,
|
||||
&mut code_section_index,
|
||||
app,
|
||||
) {
|
||||
output_items.add_child(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
AIAgentOutputMessageType::WebSearch(web_search_status) => {
|
||||
@@ -3210,19 +3236,18 @@ fn render_usage_button(props: Props, app: &AppContext) -> Box<dyn Element> {
|
||||
};
|
||||
|
||||
let context_usage = conversation.context_window_usage();
|
||||
let total_input = conversation.total_input_tokens();
|
||||
let current_context = conversation.current_context_tokens();
|
||||
let cache_read = conversation.total_cache_read_tokens();
|
||||
let cache_write = conversation.total_cache_write_tokens();
|
||||
let cache_miss = conversation.cache_miss_tokens();
|
||||
let cost_cents = conversation.total_cost_cents();
|
||||
|
||||
let max_context: u32 = if context_usage > 0.0 {
|
||||
(total_input as f32 / context_usage).round() as u32
|
||||
(current_context as f32 / context_usage).round() as u32
|
||||
} else {
|
||||
200_000
|
||||
};
|
||||
let context_pct = context_usage * 100.0;
|
||||
let cache_total = cache_read + cache_write + cache_miss;
|
||||
let cache_total = cache_read + cache_write;
|
||||
let cache_hit_pct = if cache_total > 0 {
|
||||
(cache_read as f64 / cache_total as f64) * 100.0
|
||||
} else {
|
||||
@@ -3230,14 +3255,13 @@ fn render_usage_button(props: Props, app: &AppContext) -> Box<dyn Element> {
|
||||
};
|
||||
|
||||
let usage_text = format!(
|
||||
"Context: {:.1}% ({} / {}) | Cache: {:.1}% (R: {}, W: {}, M: {}) | Cost: ${:.2}",
|
||||
"Context: {:.1}% ({} / {}) | Cache: {:.1}% (R: {}, W: {}) | Cost: ${:.2}",
|
||||
context_pct,
|
||||
format_token_count(total_input),
|
||||
format_token_count(current_context),
|
||||
format_token_count(max_context),
|
||||
cache_hit_pct,
|
||||
format_token_count(cache_read),
|
||||
format_token_count(cache_write),
|
||||
format_token_count(cache_miss),
|
||||
cost_cents / 100.0,
|
||||
);
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ fn render_linked_code_block_internal(
|
||||
let open_button = render_button(
|
||||
appearance,
|
||||
Icon::LinkExternal,
|
||||
"Open in Warp",
|
||||
"Open in Galaxy",
|
||||
mouse_handles.open_button,
|
||||
code_clone.clone(),
|
||||
on_open,
|
||||
|
||||
@@ -1995,6 +1995,9 @@ impl BlocklistAIController {
|
||||
request_params.parent_agent_id = parent_agent_id;
|
||||
request_params.agent_name = agent_name;
|
||||
request_params.bedrock_message_history = bedrock_history;
|
||||
request_params.is_summarization = request_input
|
||||
.all_inputs()
|
||||
.any(|input| matches!(input, AIAgentInput::SummarizeConversation { .. }));
|
||||
|
||||
let server_conversation_token_for_identifiers =
|
||||
conversation_data.server_conversation_token.clone();
|
||||
@@ -2020,9 +2023,13 @@ impl BlocklistAIController {
|
||||
let input_contains_user_query = request_input
|
||||
.all_inputs()
|
||||
.any(|input| input.is_user_query());
|
||||
let input_is_summarization = request_input
|
||||
.all_inputs()
|
||||
.any(|input| matches!(input, AIAgentInput::SummarizeConversation { .. }));
|
||||
ctx.subscribe_to_model(&response_stream, move |me, event, ctx| {
|
||||
me.handle_response_stream_event(
|
||||
input_contains_user_query,
|
||||
input_is_summarization,
|
||||
event,
|
||||
&response_stream_clone,
|
||||
ctx,
|
||||
@@ -2212,6 +2219,7 @@ impl BlocklistAIController {
|
||||
fn handle_response_stream_event(
|
||||
&mut self,
|
||||
did_input_contain_user_query: bool,
|
||||
is_summarization_request: bool,
|
||||
event: &ResponseStreamEvent,
|
||||
response_stream: &ModelHandle<ResponseStream>,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
@@ -2319,11 +2327,88 @@ impl BlocklistAIController {
|
||||
let history_model = BlocklistAIHistoryModel::handle(ctx);
|
||||
history_model.update(ctx, |history_model, _| {
|
||||
if let Some(conversation) = history_model.conversation_mut(&conversation_id) {
|
||||
*conversation.bedrock_message_history_mut() = new_history;
|
||||
log::info!(
|
||||
"[bedrock] Updated conversation bedrock history: {} messages",
|
||||
conversation.bedrock_message_history().len()
|
||||
);
|
||||
// If this was a summarization request, compact the
|
||||
// history to just the summary instead of keeping
|
||||
// the full message list. This is what actually
|
||||
// frees up context window space.
|
||||
let is_summarization = is_summarization_request;
|
||||
|
||||
if is_summarization {
|
||||
// Extract the assistant's summary from the last
|
||||
// message in the history (the response).
|
||||
let summary_text = new_history
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|msg| {
|
||||
use crate::ai::bedrock::convert::{
|
||||
MessageContent, MessageRole,
|
||||
};
|
||||
if msg.role == MessageRole::Assistant {
|
||||
if let MessageContent::Text(text) =
|
||||
&msg.content
|
||||
{
|
||||
Some(text.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(summary) = summary_text {
|
||||
use crate::ai::bedrock::convert::{
|
||||
ConversationMessage, MessageContent,
|
||||
MessageRole,
|
||||
};
|
||||
let assistant_reply = "Understood. I have the context from our previous conversation. How can I help you next?";
|
||||
let user_msg = format!(
|
||||
"Here is a summary of our conversation so far:\n\n{summary}"
|
||||
);
|
||||
let compacted = vec![
|
||||
ConversationMessage {
|
||||
role: MessageRole::User,
|
||||
content: MessageContent::Text(user_msg.clone()),
|
||||
},
|
||||
ConversationMessage {
|
||||
role: MessageRole::Assistant,
|
||||
content: MessageContent::Text(
|
||||
assistant_reply.to_string()
|
||||
),
|
||||
},
|
||||
];
|
||||
log::info!(
|
||||
"[bedrock] Compacted conversation history from {} messages to {} (summary)",
|
||||
new_history.len(),
|
||||
compacted.len()
|
||||
);
|
||||
*conversation.bedrock_message_history_mut() =
|
||||
compacted;
|
||||
|
||||
// Estimate new context size from the compacted content.
|
||||
// ~4 chars per token is a reasonable approximation.
|
||||
let estimated_tokens = ((user_msg.len() + assistant_reply.len()) / 4) as u32;
|
||||
let max_context = crate::ai::bedrock::response_translator::context_window_for_model("claude-opus-4-6-20250514[1m]");
|
||||
let new_usage = estimated_tokens as f32 / max_context as f32;
|
||||
conversation.set_context_window_usage(new_usage);
|
||||
conversation.set_current_context_tokens(estimated_tokens);
|
||||
log::info!(
|
||||
"[bedrock] Post-compact context estimate: ~{} tokens ({:.1}% of context window)",
|
||||
estimated_tokens,
|
||||
new_usage * 100.0
|
||||
);
|
||||
} else {
|
||||
*conversation.bedrock_message_history_mut() =
|
||||
new_history;
|
||||
}
|
||||
} else {
|
||||
*conversation.bedrock_message_history_mut() =
|
||||
new_history;
|
||||
log::info!(
|
||||
"[bedrock] Updated conversation bedrock history: {} messages",
|
||||
conversation.bedrock_message_history().len()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -2800,6 +2885,42 @@ impl BlocklistAIController {
|
||||
});
|
||||
ctx.emit(BlocklistAIControllerEvent::FreeTierLimitCheckTriggered);
|
||||
}
|
||||
|
||||
// Auto-compact: trigger summarization when context window usage >= 85%.
|
||||
let should_auto_compact = {
|
||||
let history_model = BlocklistAIHistoryModel::as_ref(ctx);
|
||||
history_model
|
||||
.conversation(&conversation_id)
|
||||
.is_some_and(|conversation| {
|
||||
let is_summarization_request = conversation
|
||||
.latest_exchange()
|
||||
.is_some_and(|exchange| {
|
||||
exchange
|
||||
.input
|
||||
.iter()
|
||||
.any(|i| matches!(i, AIAgentInput::SummarizeConversation { .. }))
|
||||
});
|
||||
conversation.context_window_usage() >= 0.85
|
||||
&& !conversation.has_pending_auto_compact()
|
||||
&& !is_summarization_request
|
||||
})
|
||||
};
|
||||
|
||||
if should_auto_compact {
|
||||
log::info!(
|
||||
"[auto-compact] Context window usage >= 85% for conversation {:?}, triggering summarization",
|
||||
conversation_id
|
||||
);
|
||||
BlocklistAIHistoryModel::handle(ctx).update(ctx, |history_model, _| {
|
||||
if let Some(conversation) = history_model.conversation_mut(&conversation_id) {
|
||||
conversation.set_has_pending_auto_compact(true);
|
||||
}
|
||||
});
|
||||
self.send_slash_command_request(
|
||||
SlashCommandRequest::Summarize { prompt: None },
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use ai::agent::{
|
||||
action::{AskUserQuestionItem, AskUserQuestionOption, AskUserQuestionType},
|
||||
action_result::{AskUserQuestionAnswerItem, AskUserQuestionResult},
|
||||
};
|
||||
use galaxy_core::ui::theme::{color::internal_colors, WarpTheme};
|
||||
use galaxy_core::ui::theme::{color::internal_colors, GalaxyTheme};
|
||||
use galaxyui::{
|
||||
elements::{
|
||||
new_scrollable::SingleAxisConfig, Border, ChildView, Clipped, ClippedScrollStateHandle,
|
||||
@@ -1333,7 +1333,7 @@ impl AskUserQuestionView {
|
||||
fn render_question_text(
|
||||
question_text: &str,
|
||||
appearance: &Appearance,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
) -> Box<dyn Element> {
|
||||
let text_color = theme.foreground().into();
|
||||
Container::new(render_text_with_markdown_support(
|
||||
@@ -1357,7 +1357,7 @@ impl AskUserQuestionView {
|
||||
&self,
|
||||
question_text: &str,
|
||||
appearance: &Appearance,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
) -> Box<dyn Element> {
|
||||
let body = Flex::column()
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Stretch)
|
||||
@@ -1385,7 +1385,7 @@ impl AskUserQuestionView {
|
||||
fn render_nav_footer(
|
||||
&self,
|
||||
appearance: &Appearance,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let counter = format!(
|
||||
|
||||
@@ -11,6 +11,7 @@ pub(crate) mod requested_command_attribution;
|
||||
pub(crate) mod requested_script;
|
||||
pub(super) mod search_codebase;
|
||||
pub(crate) mod search_results_common;
|
||||
pub(super) mod summarization;
|
||||
pub(crate) mod suggested_unit_tests;
|
||||
pub(super) mod web_fetch;
|
||||
pub(super) mod web_search;
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxyui::elements::shimmering_text::{ShimmerConfig, ShimmeringTextElement, ShimmeringTextStateHandle};
|
||||
use galaxyui::elements::{
|
||||
ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Element, Flex,
|
||||
MainAxisAlignment, ParentElement, Radius, Shrinkable, Text,
|
||||
};
|
||||
use galaxyui::r#async::{SpawnedFutureHandle, Timer};
|
||||
use galaxyui::{AppContext, Entity, SingletonEntity, View, ViewContext};
|
||||
use instant::Instant;
|
||||
use std::time::Duration;
|
||||
|
||||
use super::inline_action_header::{
|
||||
INLINE_ACTION_HEADER_VERTICAL_PADDING, INLINE_ACTION_HORIZONTAL_PADDING,
|
||||
};
|
||||
use super::inline_action_icons::icon_size;
|
||||
use crate::ai::blocklist::block::view_impl::WithContentItemSpacing;
|
||||
use crate::ui_components::icons::Icon;
|
||||
|
||||
pub enum SummarizationViewEvent {}
|
||||
|
||||
pub struct SummarizationView {
|
||||
pub is_finished: bool,
|
||||
shimmering_text_handle: ShimmeringTextStateHandle,
|
||||
start_time: Instant,
|
||||
timer_handle: Option<SpawnedFutureHandle>,
|
||||
}
|
||||
|
||||
impl SummarizationView {
|
||||
pub fn new(ctx: &mut ViewContext<Self>) -> Self {
|
||||
let mut view = Self {
|
||||
is_finished: false,
|
||||
shimmering_text_handle: ShimmeringTextStateHandle::default(),
|
||||
start_time: Instant::now(),
|
||||
timer_handle: None,
|
||||
};
|
||||
view.start_timer(ctx);
|
||||
view
|
||||
}
|
||||
|
||||
pub fn mark_finished(&mut self) {
|
||||
self.is_finished = true;
|
||||
if let Some(handle) = self.timer_handle.take() {
|
||||
handle.abort();
|
||||
}
|
||||
}
|
||||
|
||||
fn start_timer(&mut self, ctx: &mut ViewContext<Self>) {
|
||||
if self.timer_handle.is_some() {
|
||||
return;
|
||||
}
|
||||
let handle = ctx.spawn(
|
||||
async move {
|
||||
Timer::after(Duration::from_secs(1)).await;
|
||||
},
|
||||
|me, _unit, ctx| {
|
||||
me.timer_handle = None;
|
||||
if !me.is_finished {
|
||||
ctx.notify();
|
||||
me.start_timer(ctx);
|
||||
}
|
||||
},
|
||||
);
|
||||
self.timer_handle = Some(handle);
|
||||
}
|
||||
|
||||
fn render_in_progress(&self, app: &AppContext) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let theme = appearance.theme();
|
||||
let header_background = theme.surface_2();
|
||||
|
||||
let mut header_row = Flex::row()
|
||||
.with_main_axis_alignment(MainAxisAlignment::Start)
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
// Clock loader icon (magenta, matches InProgress convention)
|
||||
let icon_element = galaxyui::elements::Icon::new(
|
||||
Icon::ClockLoader.into(),
|
||||
theme.ansi_fg_magenta(),
|
||||
)
|
||||
.finish();
|
||||
let icon_box = ConstrainedBox::new(icon_element)
|
||||
.with_width(icon_size(app))
|
||||
.with_height(icon_size(app))
|
||||
.finish();
|
||||
header_row.add_child(
|
||||
Container::new(icon_box)
|
||||
.with_margin_right(8.)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
// Shimmering "Summarizing conversation..." text
|
||||
let base_color = theme.disabled_text_color(header_background).into_solid();
|
||||
let shimmer_color = theme.main_text_color(header_background).into_solid();
|
||||
let shimmer_element = ShimmeringTextElement::new(
|
||||
"Summarizing conversation...".to_string(),
|
||||
appearance.ui_font_family(),
|
||||
appearance.monospace_font_size(),
|
||||
base_color,
|
||||
shimmer_color,
|
||||
ShimmerConfig::default(),
|
||||
self.shimmering_text_handle.clone(),
|
||||
)
|
||||
.finish();
|
||||
header_row.add_child(Shrinkable::new(1.0, shimmer_element).finish());
|
||||
|
||||
// Elapsed time suffix
|
||||
let elapsed = self.start_time.elapsed();
|
||||
let elapsed_text = format_elapsed(elapsed);
|
||||
let suffix = Text::new_inline(
|
||||
format!(" \u{2022} {elapsed_text}"),
|
||||
appearance.ui_font_family(),
|
||||
appearance.monospace_font_size(),
|
||||
)
|
||||
.with_color(theme.disabled_text_color(header_background).into())
|
||||
.finish();
|
||||
header_row.add_child(suffix);
|
||||
|
||||
Container::new(header_row.finish())
|
||||
.with_horizontal_padding(INLINE_ACTION_HORIZONTAL_PADDING)
|
||||
.with_vertical_padding(INLINE_ACTION_HEADER_VERTICAL_PADDING)
|
||||
.with_background(header_background)
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_finished(&self, app: &AppContext) -> Box<dyn Element> {
|
||||
let appearance = Appearance::as_ref(app);
|
||||
let theme = appearance.theme();
|
||||
let header_background = theme.surface_2();
|
||||
|
||||
let mut header_row = Flex::row()
|
||||
.with_main_axis_alignment(MainAxisAlignment::Start)
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
// Checkmark-style icon for completed
|
||||
let icon_element = galaxyui::elements::Icon::new(
|
||||
Icon::Check.into(),
|
||||
theme.ansi_fg_green(),
|
||||
)
|
||||
.finish();
|
||||
let icon_box = ConstrainedBox::new(icon_element)
|
||||
.with_width(icon_size(app))
|
||||
.with_height(icon_size(app))
|
||||
.finish();
|
||||
header_row.add_child(
|
||||
Container::new(icon_box)
|
||||
.with_margin_right(8.)
|
||||
.finish(),
|
||||
);
|
||||
|
||||
let elapsed = self.start_time.elapsed();
|
||||
let elapsed_text = format_elapsed(elapsed);
|
||||
let title = Text::new_inline(
|
||||
format!("Conversation summarized \u{2022} {elapsed_text}"),
|
||||
appearance.ui_font_family(),
|
||||
appearance.monospace_font_size(),
|
||||
)
|
||||
.with_color(theme.main_text_color(header_background).into())
|
||||
.finish();
|
||||
header_row.add_child(Shrinkable::new(1.0, title).finish());
|
||||
|
||||
Container::new(header_row.finish())
|
||||
.with_horizontal_padding(INLINE_ACTION_HORIZONTAL_PADDING)
|
||||
.with_vertical_padding(INLINE_ACTION_HEADER_VERTICAL_PADDING)
|
||||
.with_background(header_background)
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for SummarizationView {
|
||||
type Event = SummarizationViewEvent;
|
||||
}
|
||||
|
||||
impl View for SummarizationView {
|
||||
fn ui_name() -> &'static str {
|
||||
"SummarizationView"
|
||||
}
|
||||
|
||||
fn render(&self, app: &AppContext) -> Box<dyn Element> {
|
||||
let element = if self.is_finished {
|
||||
self.render_finished(app)
|
||||
} else {
|
||||
self.render_in_progress(app)
|
||||
};
|
||||
element.with_agent_output_item_spacing(app).finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_elapsed(duration: Duration) -> String {
|
||||
let secs = duration.as_secs();
|
||||
if secs < 60 {
|
||||
format!("{secs}s")
|
||||
} else {
|
||||
format!("{}m {}s", secs / 60, secs % 60)
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ use warp_multi_agent_api as api;
|
||||
|
||||
const MAX_RETRY_ATTEMPTS: i32 = 3;
|
||||
const MAX_PENDING_LIFECYCLE_EVENTS_PER_TARGET: usize = 200;
|
||||
pub const MAX_SUBAGENT_RETRIES: u8 = 3;
|
||||
const MAX_SUBAGENT_QUESTION_DEPTH: u8 = 3;
|
||||
|
||||
/// Stage associated with a lifecycle error detail.
|
||||
/// This keeps persisted/runtime metadata consistent across API payloads and DB rows.
|
||||
@@ -64,6 +66,23 @@ pub enum PendingEventDetail {
|
||||
Lifecycle {
|
||||
event: api::AgentEvent,
|
||||
},
|
||||
/// A subagent is asking its parent a question (routed from AskUserQuestion).
|
||||
SubagentQuestion {
|
||||
source_conversation_id: AIConversationId,
|
||||
question_text: String,
|
||||
options: Vec<String>,
|
||||
depth: u8,
|
||||
},
|
||||
/// The parent's answer to a subagent's question.
|
||||
SubagentAnswer {
|
||||
target_conversation_id: AIConversationId,
|
||||
answer_text: String,
|
||||
},
|
||||
/// A subagent reporting its completion summary to the parent.
|
||||
SubagentCompletionSummary {
|
||||
source_conversation_id: AIConversationId,
|
||||
summary_text: String,
|
||||
},
|
||||
}
|
||||
|
||||
/// A queued event consumed by the controller.
|
||||
@@ -930,29 +949,49 @@ impl OrchestrationEventService {
|
||||
|
||||
let mut messages = Vec::new();
|
||||
let mut lifecycle_events = Vec::new();
|
||||
for event in &deliverable {
|
||||
let mut server_bound_events = Vec::new();
|
||||
for event in deliverable {
|
||||
match &event.detail {
|
||||
PendingEventDetail::Message {
|
||||
message_id,
|
||||
addresses,
|
||||
subject,
|
||||
message_body,
|
||||
} => messages.push(ReceivedMessageInput {
|
||||
message_id: message_id.clone(),
|
||||
sender_agent_id: event.source_agent_id.clone(),
|
||||
addresses: addresses.clone(),
|
||||
subject: subject.clone(),
|
||||
message_body: message_body.clone(),
|
||||
}),
|
||||
PendingEventDetail::Lifecycle { event } => lifecycle_events.push(event.clone()),
|
||||
} => {
|
||||
messages.push(ReceivedMessageInput {
|
||||
message_id: message_id.clone(),
|
||||
sender_agent_id: event.source_agent_id.clone(),
|
||||
addresses: addresses.clone(),
|
||||
subject: subject.clone(),
|
||||
message_body: message_body.clone(),
|
||||
});
|
||||
server_bound_events.push(event);
|
||||
}
|
||||
PendingEventDetail::Lifecycle { event: _ } => {
|
||||
lifecycle_events.push(
|
||||
if let PendingEventDetail::Lifecycle { event: e } = &event.detail {
|
||||
e.clone()
|
||||
} else {
|
||||
unreachable!()
|
||||
},
|
||||
);
|
||||
server_bound_events.push(event);
|
||||
}
|
||||
// Local-only subagent events are consumed directly by the controller,
|
||||
// not converted to AIAgentInput or awaited for server echo.
|
||||
PendingEventDetail::SubagentQuestion { .. }
|
||||
| PendingEventDetail::SubagentAnswer { .. }
|
||||
| PendingEventDetail::SubagentCompletionSummary { .. } => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Move to awaiting echo for delivery confirmation.
|
||||
self.awaiting_server_echo_events
|
||||
.entry(conversation_id)
|
||||
.or_default()
|
||||
.extend(deliverable);
|
||||
// Only server-bound events need echo confirmation.
|
||||
if !server_bound_events.is_empty() {
|
||||
self.awaiting_server_echo_events
|
||||
.entry(conversation_id)
|
||||
.or_default()
|
||||
.extend(server_bound_events);
|
||||
}
|
||||
|
||||
let mut inputs = Vec::new();
|
||||
if !messages.is_empty() {
|
||||
@@ -966,6 +1005,34 @@ impl OrchestrationEventService {
|
||||
inputs
|
||||
}
|
||||
|
||||
/// Drain only the local subagent events (Question/Answer/Summary) for a conversation.
|
||||
/// These are not sent to the server and are consumed directly by the controller.
|
||||
pub fn drain_subagent_events(
|
||||
&mut self,
|
||||
conversation_id: &AIConversationId,
|
||||
) -> Vec<PendingEvent> {
|
||||
let Some(pending) = self.pending_events.get_mut(conversation_id) else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let mut subagent_events = Vec::new();
|
||||
pending.retain(|event| match &event.detail {
|
||||
PendingEventDetail::SubagentQuestion { .. }
|
||||
| PendingEventDetail::SubagentAnswer { .. }
|
||||
| PendingEventDetail::SubagentCompletionSummary { .. } => {
|
||||
subagent_events.push(event.clone());
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
|
||||
if pending.is_empty() {
|
||||
self.pending_events.remove(conversation_id);
|
||||
}
|
||||
|
||||
subagent_events
|
||||
}
|
||||
|
||||
/// Moves all awaiting events back to pending for retry after a failed
|
||||
/// send attempt. Increments attempt counts and drops events that have
|
||||
/// exhausted their retry limit.
|
||||
@@ -1109,6 +1176,101 @@ impl OrchestrationEventService {
|
||||
self.awaiting_server_echo_events.remove(&conversation_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Route a subagent's AskUserQuestion to the parent conversation for silent auto-answer.
|
||||
pub fn route_subagent_question_to_parent(
|
||||
&mut self,
|
||||
child_conversation_id: AIConversationId,
|
||||
parent_conversation_id: AIConversationId,
|
||||
question_text: String,
|
||||
options: Vec<String>,
|
||||
depth: u8,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
if depth >= MAX_SUBAGENT_QUESTION_DEPTH {
|
||||
log::warn!(
|
||||
"Subagent question depth limit reached for conversation {:?}",
|
||||
child_conversation_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let event = PendingEvent {
|
||||
event_id: Uuid::new_v4().to_string(),
|
||||
source_agent_id: child_conversation_id.to_string(),
|
||||
attempt_count: 0,
|
||||
detail: PendingEventDetail::SubagentQuestion {
|
||||
source_conversation_id: child_conversation_id,
|
||||
question_text,
|
||||
options,
|
||||
depth,
|
||||
},
|
||||
};
|
||||
|
||||
self.pending_events
|
||||
.entry(parent_conversation_id)
|
||||
.or_default()
|
||||
.push(event);
|
||||
|
||||
ctx.emit(OrchestrationEventServiceEvent::EventsReady {
|
||||
conversation_id: parent_conversation_id,
|
||||
});
|
||||
}
|
||||
|
||||
/// Route the parent's answer back to the child subagent.
|
||||
pub fn route_answer_to_subagent(
|
||||
&mut self,
|
||||
child_conversation_id: AIConversationId,
|
||||
answer_text: String,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let event = PendingEvent {
|
||||
event_id: Uuid::new_v4().to_string(),
|
||||
source_agent_id: "parent".to_string(),
|
||||
attempt_count: 0,
|
||||
detail: PendingEventDetail::SubagentAnswer {
|
||||
target_conversation_id: child_conversation_id,
|
||||
answer_text,
|
||||
},
|
||||
};
|
||||
|
||||
self.pending_events
|
||||
.entry(child_conversation_id)
|
||||
.or_default()
|
||||
.push(event);
|
||||
|
||||
ctx.emit(OrchestrationEventServiceEvent::EventsReady {
|
||||
conversation_id: child_conversation_id,
|
||||
});
|
||||
}
|
||||
|
||||
/// Route a subagent's completion summary to the parent conversation.
|
||||
pub fn route_subagent_completion_summary(
|
||||
&mut self,
|
||||
child_conversation_id: AIConversationId,
|
||||
parent_conversation_id: AIConversationId,
|
||||
summary_text: String,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let event = PendingEvent {
|
||||
event_id: Uuid::new_v4().to_string(),
|
||||
source_agent_id: child_conversation_id.to_string(),
|
||||
attempt_count: 0,
|
||||
detail: PendingEventDetail::SubagentCompletionSummary {
|
||||
source_conversation_id: child_conversation_id,
|
||||
summary_text,
|
||||
},
|
||||
};
|
||||
|
||||
self.pending_events
|
||||
.entry(parent_conversation_id)
|
||||
.or_default()
|
||||
.push(event);
|
||||
|
||||
ctx.emit(OrchestrationEventServiceEvent::EventsReady {
|
||||
conversation_id: parent_conversation_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// `None` means \"subscribe to all lifecycle types\" (input omitted).
|
||||
@@ -1135,6 +1297,10 @@ fn did_event_round_trip_through_server(
|
||||
PendingEventDetail::Lifecycle { event } => {
|
||||
echoed_lifecycle_event_ids.contains(event.event_id.as_str())
|
||||
}
|
||||
// Local-only events never round-trip through the server.
|
||||
PendingEventDetail::SubagentQuestion { .. }
|
||||
| PendingEventDetail::SubagentAnswer { .. }
|
||||
| PendingEventDetail::SubagentCompletionSummary { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,11 +33,14 @@ pub struct ConversationUsageInfo {
|
||||
pub lines_added: i32,
|
||||
pub lines_removed: i32,
|
||||
pub commands_executed: i32,
|
||||
pub total_input_tokens: u32,
|
||||
pub total_output_tokens: u32,
|
||||
pub total_cache_read_tokens: u32,
|
||||
pub total_cache_write_tokens: u32,
|
||||
/// Live context window token count (from most recent Bedrock response).
|
||||
pub current_context_tokens: u32,
|
||||
/// Cumulative cost across all requests.
|
||||
pub estimated_cost_cents: f32,
|
||||
/// Cumulative cache read tokens (session total).
|
||||
pub total_cache_read_tokens: u32,
|
||||
/// Cumulative cache write tokens (session total).
|
||||
pub total_cache_write_tokens: u32,
|
||||
}
|
||||
|
||||
/// Timing information for the last set of agent responses
|
||||
@@ -246,32 +249,21 @@ impl ConversationUsageView {
|
||||
);
|
||||
}
|
||||
|
||||
// Token usage section
|
||||
let total_tokens = self.usage_info.total_input_tokens
|
||||
+ self.usage_info.total_output_tokens
|
||||
+ self.usage_info.total_cache_read_tokens
|
||||
// Context tokens (live state — current context window size)
|
||||
if self.usage_info.current_context_tokens > 0 {
|
||||
labels.push(render_label_text("Context tokens", appearance));
|
||||
values.push(render_value_text(
|
||||
format_token_count(self.usage_info.current_context_tokens),
|
||||
appearance,
|
||||
));
|
||||
}
|
||||
|
||||
// Cache usage (cumulative session totals)
|
||||
let total_cache = self.usage_info.total_cache_read_tokens
|
||||
+ self.usage_info.total_cache_write_tokens;
|
||||
if total_tokens > 0 {
|
||||
labels.push(render_label_text("Total tokens", appearance));
|
||||
values.push(render_value_text(
|
||||
format_token_count(total_tokens),
|
||||
appearance,
|
||||
));
|
||||
|
||||
labels.push(render_label_text(" Input", appearance));
|
||||
values.push(render_value_text(
|
||||
format_token_count(self.usage_info.total_input_tokens),
|
||||
appearance,
|
||||
));
|
||||
|
||||
labels.push(render_label_text(" Output", appearance));
|
||||
values.push(render_value_text(
|
||||
format_token_count(self.usage_info.total_output_tokens),
|
||||
appearance,
|
||||
));
|
||||
|
||||
if total_cache > 0 {
|
||||
if self.usage_info.total_cache_read_tokens > 0 {
|
||||
labels.push(render_label_text(" Cache read", appearance));
|
||||
labels.push(render_label_text("Cache read", appearance));
|
||||
values.push(render_value_text(
|
||||
format_token_count(self.usage_info.total_cache_read_tokens),
|
||||
appearance,
|
||||
@@ -279,12 +271,25 @@ impl ConversationUsageView {
|
||||
}
|
||||
|
||||
if self.usage_info.total_cache_write_tokens > 0 {
|
||||
labels.push(render_label_text(" Cache write", appearance));
|
||||
labels.push(render_label_text("Cache write", appearance));
|
||||
values.push(render_value_text(
|
||||
format_token_count(self.usage_info.total_cache_write_tokens),
|
||||
appearance,
|
||||
));
|
||||
}
|
||||
|
||||
// Cache hit rate
|
||||
let cache_miss = self.usage_info.current_context_tokens
|
||||
.saturating_sub(self.usage_info.total_cache_read_tokens);
|
||||
let total_input = self.usage_info.total_cache_read_tokens + cache_miss;
|
||||
if total_input > 0 {
|
||||
let hit_rate = (self.usage_info.total_cache_read_tokens as f32 / total_input as f32) * 100.0;
|
||||
labels.push(render_label_text("Cache hit rate", appearance));
|
||||
values.push(render_value_text(
|
||||
format!("{:.0}%", hit_rate),
|
||||
appearance,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
labels.push(render_label_text("Context window used", appearance));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use galaxy_core::ui::theme::{Fill, WarpTheme};
|
||||
use galaxy_core::ui::theme::{Fill, GalaxyTheme};
|
||||
use galaxy_core::ui::Icon;
|
||||
use galaxyui::Element;
|
||||
|
||||
@@ -33,7 +33,7 @@ pub fn icon_for_context_window_usage(context_window_usage: f32) -> Icon {
|
||||
|
||||
pub fn render_context_window_usage_icon(
|
||||
context_window_usage: f32,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
color_override: Option<Fill>,
|
||||
) -> Box<dyn Element> {
|
||||
let icon = icon_for_context_window_usage(context_window_usage);
|
||||
|
||||
@@ -19,7 +19,7 @@ use pathfinder_color::ColorU;
|
||||
use pathfinder_geometry::vector::vec2f;
|
||||
|
||||
use crate::{
|
||||
themes::theme::{AnsiColorIdentifier, Fill, WarpTheme},
|
||||
themes::theme::{AnsiColorIdentifier, Fill, GalaxyTheme},
|
||||
ui_components::icons::Icon,
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ pub const CLAUDE_ORANGE: ColorU = ColorU {
|
||||
|
||||
/// Returns the color to be used for various AI signifiers
|
||||
/// input with AI mode).
|
||||
pub fn ai_brand_color(theme: &WarpTheme) -> ColorU {
|
||||
pub fn ai_brand_color(theme: &GalaxyTheme) -> ColorU {
|
||||
AnsiColorIdentifier::Magenta
|
||||
.to_ansi_color(&theme.terminal_colors().normal)
|
||||
.into()
|
||||
@@ -60,7 +60,7 @@ pub fn ai_brand_color(theme: &WarpTheme) -> ColorU {
|
||||
|
||||
/// Returns the color to be used for error UI throughout Agent Mode (like the "request limit
|
||||
/// exceeded" chip).
|
||||
pub fn error_color(theme: &WarpTheme) -> ColorU {
|
||||
pub fn error_color(theme: &GalaxyTheme) -> ColorU {
|
||||
AnsiColorIdentifier::Red
|
||||
.to_ansi_color(&theme.terminal_colors().normal)
|
||||
.into()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::color::coloru_with_opacity;
|
||||
use galaxy_core::ui::theme::{Fill, WarpTheme};
|
||||
use galaxy_core::ui::theme::{Fill, GalaxyTheme};
|
||||
use galaxyui::color::ColorU;
|
||||
use galaxyui::elements::{ConstrainedBox, Container, CornerRadius, Radius};
|
||||
use galaxyui::Element;
|
||||
@@ -13,17 +13,17 @@ use crate::ui_components::icons::Icon;
|
||||
pub const STATUS_ELEMENT_PADDING: f32 = 2.;
|
||||
|
||||
pub trait StatusElementStyle {
|
||||
fn status_icon_and_color(&self, theme: &WarpTheme) -> (Icon, ColorU);
|
||||
fn status_icon_and_color(&self, theme: &GalaxyTheme) -> (Icon, ColorU);
|
||||
}
|
||||
|
||||
impl StatusElementStyle for ConversationStatus {
|
||||
fn status_icon_and_color(&self, theme: &WarpTheme) -> (Icon, ColorU) {
|
||||
fn status_icon_and_color(&self, theme: &GalaxyTheme) -> (Icon, ColorU) {
|
||||
ConversationStatus::status_icon_and_color(self, theme)
|
||||
}
|
||||
}
|
||||
|
||||
impl StatusElementStyle for AgentRunDisplayStatus {
|
||||
fn status_icon_and_color(&self, theme: &WarpTheme) -> (Icon, ColorU) {
|
||||
fn status_icon_and_color(&self, theme: &GalaxyTheme) -> (Icon, ColorU) {
|
||||
AgentRunDisplayStatus::status_icon_and_color(self, theme)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -664,7 +664,7 @@ pub fn render_plan_auto_sync_toggle(
|
||||
.finish();
|
||||
|
||||
let desc_elem = Text::new(
|
||||
"The plans this agent creates will be automatically added and synced to Warp Drive."
|
||||
"The plans this agent creates will be automatically added and synced to Galaxy Drive."
|
||||
.to_string(),
|
||||
appearance.ui_font_family(),
|
||||
11.,
|
||||
|
||||
@@ -5,49 +5,58 @@ pub struct PredefinedRule {
|
||||
|
||||
pub const SYSTEM_DEFINED_RULE_PREFIX: &str = "System Defined Rule";
|
||||
|
||||
pub fn is_predefined_rule(name: &str) -> bool {
|
||||
PREDEFINED_RULES.iter().any(|r| r.name == name)
|
||||
|| name.starts_with(SYSTEM_DEFINED_RULE_PREFIX)
|
||||
}
|
||||
|
||||
pub fn predefined_rule_index(name: &str) -> Option<usize> {
|
||||
PREDEFINED_RULES.iter().position(|r| r.name == name)
|
||||
}
|
||||
|
||||
pub const PREDEFINED_RULES: &[PredefinedRule] = &[
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #1",
|
||||
name: "Correctness Over Speed",
|
||||
content: "Prioritize correctness, completeness, and reliability over speed.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #2",
|
||||
name: "Never Guess",
|
||||
content: "Never guess. If uncertain, explicitly say so and verify before finalizing.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #3",
|
||||
name: "Evidence-Based Claims",
|
||||
content: "Ground non-trivial claims in evidence (repo files, command output, tests, official documentation).",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #4",
|
||||
name: "Verify When Uncertain",
|
||||
content: "If confidence is not high, or if a claim depends on external/current behavior, perform web verification before answering; prioritize official docs and cross-check with at least one additional reliable source.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #5",
|
||||
name: "Separate Facts From Assumptions",
|
||||
content: "Clearly separate facts, assumptions, and hypotheses.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #6",
|
||||
name: "Ask When Ambiguous",
|
||||
content: "Ask clarifying questions when ambiguity could change the solution or implementation.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #7",
|
||||
name: "Validate Code Changes",
|
||||
content: "For code changes, run relevant validations when available (tests, lint, typecheck, build) and report what was run, what passed/failed, and what was not run.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #8",
|
||||
name: "Disclose Unvalidated Risks",
|
||||
content: "If validation cannot be run, state that explicitly and describe residual risk and recommended manual checks.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #9",
|
||||
name: "Admit Unknowns",
|
||||
content: "Prefer \"I don't know yet\" over plausible speculation.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #10",
|
||||
name: "Challenge Bad Ideas",
|
||||
content: "If the user's idea is wrong, incomplete, risky, or non-optimal, say so directly and respectfully; explain why it may fail and provide a better alternative that still achieves the user's goal.",
|
||||
},
|
||||
PredefinedRule {
|
||||
name: "System Defined Rule #11",
|
||||
name: "Surface Disagreement",
|
||||
content: "Do not hide uncertainty, and do not avoid technical disagreement when correctness is at stake.",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
use crate::cloud_object::{
|
||||
CloudObject, CloudObjectSyncStatus, GenericStringObjectFormat, JsonObjectType,
|
||||
};
|
||||
use crate::drive::CloudObjectTypeAndId;
|
||||
use crate::cloud_object::{CloudObject, CloudObjectSyncStatus};
|
||||
use crate::network::NetworkStatus;
|
||||
use crate::pane_group::focus_state::PaneFocusHandle;
|
||||
use crate::pane_group::{pane::view, BackingView, PaneConfiguration, PaneEvent};
|
||||
@@ -337,22 +334,12 @@ pub fn is_online(app: &AppContext) -> bool {
|
||||
NetworkStatus::as_ref(app).is_online()
|
||||
}
|
||||
|
||||
pub fn is_delete_allowed(ai_fact: CloudAIFact, app: &AppContext) -> bool {
|
||||
let cloud_object_type_and_id = CloudObjectTypeAndId::GenericStringObject {
|
||||
object_type: GenericStringObjectFormat::Json(JsonObjectType::AIFact),
|
||||
id: ai_fact.sync_id(),
|
||||
};
|
||||
is_online(app)
|
||||
&& cloud_object_type_and_id.has_server_id()
|
||||
&& !ai_fact.metadata().has_pending_online_only_change()
|
||||
pub fn is_delete_allowed(_ai_fact: CloudAIFact, _app: &AppContext) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_edit_allowed(ai_fact: CloudAIFact, app: &AppContext) -> bool {
|
||||
let cloud_object_type_and_id = CloudObjectTypeAndId::GenericStringObject {
|
||||
object_type: GenericStringObjectFormat::Json(JsonObjectType::AIFact),
|
||||
id: ai_fact.sync_id(),
|
||||
};
|
||||
is_online(app) || !cloud_object_type_and_id.has_server_id()
|
||||
pub fn is_edit_allowed(_ai_fact: CloudAIFact, _app: &AppContext) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub fn is_syncing(ai_fact: CloudAIFact, app: &AppContext) -> bool {
|
||||
|
||||
@@ -46,8 +46,12 @@ use markdown_parser::{
|
||||
use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::{is_edit_allowed, is_syncing, style, AIFact, CloudAIFact, CloudAIFactModel};
|
||||
use crate::ai::facts::predefined_rules::{PREDEFINED_RULES, SYSTEM_DEFINED_RULE_PREFIX};
|
||||
use super::{
|
||||
is_delete_allowed, is_edit_allowed, is_syncing, style, AIFact, CloudAIFact, CloudAIFactModel,
|
||||
};
|
||||
use crate::ai::facts::predefined_rules::{
|
||||
is_predefined_rule, predefined_rule_index, PREDEFINED_RULES,
|
||||
};
|
||||
use crate::ai::facts::AIMemory;
|
||||
|
||||
pub const HEADER_TEXT: &str = "Rules";
|
||||
@@ -56,7 +60,7 @@ const DESCRIPTION_TEXT: &str = "Rules enhance the agent by providing structured
|
||||
const SEARCH_PLACEHOLDER_TEXT: &str = "Search rules";
|
||||
const ZERO_STATE_TEXT: &str = "Once you add a rule, it will be shown here.";
|
||||
const ZERO_STATE_TEXT_PROJECT: &str =
|
||||
"Once you generate a WARP.md rules file for a project, it will appear here.";
|
||||
"Once you generate a GALAXY.md rules file for a project, it will appear here.";
|
||||
|
||||
const DISABLED_BANNER_TEXT: &str =
|
||||
"Your rules are disabled and won't be used as context in sessions. You can ";
|
||||
@@ -84,6 +88,7 @@ pub enum RuleViewAction {
|
||||
AddPredefinedRules,
|
||||
InitializeProject,
|
||||
Edit(SyncId),
|
||||
Delete(SyncId),
|
||||
OpenSettings,
|
||||
SelectScope(RuleScope),
|
||||
OpenFile(PathBuf),
|
||||
@@ -94,6 +99,7 @@ pub struct MouseStateHandles {
|
||||
pub hover: MouseStateHandle,
|
||||
pub sync_status_hover: MouseStateHandle,
|
||||
pub sync_status_icon: MouseStateHandle,
|
||||
pub delete_hover: MouseStateHandle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -358,13 +364,37 @@ impl RuleView {
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
self.global_rules = ai_rules
|
||||
let mut rows: Vec<CloudRuleRow> = ai_rules
|
||||
.into_iter()
|
||||
.map(|ai_fact| CloudRuleRow {
|
||||
fact: ai_fact,
|
||||
mouse_states: Default::default(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
rows.sort_by(|a, b| {
|
||||
let name_a = match &a.fact.model().string_model {
|
||||
AIFact::Memory(AIMemory { name, .. }) => name.clone().unwrap_or_default(),
|
||||
};
|
||||
let name_b = match &b.fact.model().string_model {
|
||||
AIFact::Memory(AIMemory { name, .. }) => name.clone().unwrap_or_default(),
|
||||
};
|
||||
let is_predefined_a = is_predefined_rule(&name_a);
|
||||
let is_predefined_b = is_predefined_rule(&name_b);
|
||||
|
||||
match (is_predefined_a, is_predefined_b) {
|
||||
(true, true) => {
|
||||
let idx_a = predefined_rule_index(&name_a).unwrap_or(usize::MAX);
|
||||
let idx_b = predefined_rule_index(&name_b).unwrap_or(usize::MAX);
|
||||
idx_a.cmp(&idx_b)
|
||||
}
|
||||
(true, false) => std::cmp::Ordering::Less,
|
||||
(false, true) => std::cmp::Ordering::Greater,
|
||||
(false, false) => std::cmp::Ordering::Equal,
|
||||
}
|
||||
});
|
||||
|
||||
self.global_rules = rows;
|
||||
ctx.notify();
|
||||
}
|
||||
|
||||
@@ -466,7 +496,7 @@ impl RuleView {
|
||||
.filter_map(|row| {
|
||||
let AIFact::Memory(AIMemory { ref name, .. }) = row.fact.model().string_model;
|
||||
let name = name.as_deref().unwrap_or_default();
|
||||
if name.starts_with(SYSTEM_DEFINED_RULE_PREFIX) {
|
||||
if is_predefined_rule(name) {
|
||||
Some((
|
||||
name.to_string(),
|
||||
(row.fact.sync_id(), row.fact.metadata().revision.clone()),
|
||||
@@ -870,7 +900,8 @@ impl RuleView {
|
||||
|
||||
let mut row = Flex::row()
|
||||
.with_main_axis_size(MainAxisSize::Max)
|
||||
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween);
|
||||
.with_main_axis_alignment(MainAxisAlignment::SpaceBetween)
|
||||
.with_cross_axis_alignment(CrossAxisAlignment::Center);
|
||||
|
||||
if let Some(sync_status_icon) =
|
||||
self.render_sync_status_icon(ai_row.clone(), appearance, app)
|
||||
@@ -880,6 +911,45 @@ impl RuleView {
|
||||
|
||||
row.add_child(Expanded::new(1., fact_text).finish());
|
||||
|
||||
if is_delete_allowed(ai_row.fact.clone(), app) {
|
||||
let delete_sync_id = ai_row.fact.sync_id();
|
||||
let delete_button = Hoverable::new(
|
||||
ai_row.mouse_states.delete_hover.clone(),
|
||||
|state| {
|
||||
let mut container = Container::new(
|
||||
ConstrainedBox::new(
|
||||
Icon::Trash
|
||||
.to_galaxyui_icon(
|
||||
appearance
|
||||
.theme()
|
||||
.sub_text_color(appearance.theme().background()),
|
||||
)
|
||||
.finish(),
|
||||
)
|
||||
.with_width(16.)
|
||||
.with_height(16.)
|
||||
.finish(),
|
||||
)
|
||||
.with_uniform_padding(4.)
|
||||
.with_corner_radius(CornerRadius::with_all(
|
||||
galaxyui::elements::Radius::Pixels(4.),
|
||||
));
|
||||
if state.is_hovered() {
|
||||
container =
|
||||
container.with_background(appearance.theme().surface_2());
|
||||
}
|
||||
container.finish()
|
||||
},
|
||||
)
|
||||
.with_cursor(Cursor::PointingHand)
|
||||
.on_click(move |ctx, _, _| {
|
||||
ctx.dispatch_typed_action(RuleViewAction::Delete(delete_sync_id));
|
||||
})
|
||||
.finish();
|
||||
|
||||
row.add_child(delete_button);
|
||||
}
|
||||
|
||||
let mut hoverable = Hoverable::new(ai_row.mouse_states.hover.clone(), |state| {
|
||||
let mut bg_color = internal_colors::neutral_1(appearance.theme());
|
||||
if state.is_hovered() {
|
||||
@@ -904,6 +974,7 @@ impl RuleView {
|
||||
if is_edit_allowed(ai_row.fact.clone(), app) {
|
||||
hoverable = hoverable
|
||||
.with_cursor(Cursor::PointingHand)
|
||||
.with_defer_events_to_children()
|
||||
.on_click(move |ctx, _, _| {
|
||||
ctx.dispatch_typed_action(RuleViewAction::Edit(ai_row.fact.sync_id()));
|
||||
});
|
||||
@@ -1050,6 +1121,9 @@ impl TypedActionView for RuleView {
|
||||
RuleViewAction::Edit(sync_id) => {
|
||||
ctx.emit(RuleViewEvent::Edit(*sync_id));
|
||||
}
|
||||
RuleViewAction::Delete(sync_id) => {
|
||||
self.delete_ai_rule(*sync_id, ctx);
|
||||
}
|
||||
RuleViewAction::OpenSettings => {
|
||||
ctx.emit(RuleViewEvent::OpenSettings);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::{
|
||||
ParsedTemplatableMCPServerResult,
|
||||
},
|
||||
settings::{ai::AISettings, AISettingsChangedEvent},
|
||||
warp_managed_paths_watcher::warp_data_dir,
|
||||
galaxy_managed_paths_watcher::galaxy_data_dir,
|
||||
};
|
||||
|
||||
/// Singleton model to manage file-based MCP servers.
|
||||
@@ -235,7 +235,7 @@ impl FileBasedMCPManager {
|
||||
/// config location.
|
||||
///
|
||||
/// "Global" means the installation was detected outside of a user repository:
|
||||
/// - For `MCPProvider::Warp`: `warp_data_dir()` (i.e. `~/.warp-core/.mcp.json`).
|
||||
/// - For `MCPProvider::Warp`: `galaxy_data_dir()` (i.e. `~/.warp-core/.mcp.json`).
|
||||
/// - For any other provider: the user's home directory (e.g. `~/.claude.json`).
|
||||
///
|
||||
/// Project-scoped installations (those detected inside a repo) are not considered
|
||||
@@ -243,7 +243,7 @@ impl FileBasedMCPManager {
|
||||
/// case this returns `true` due to the global reference).
|
||||
fn is_global_server(&self, hash: u64) -> bool {
|
||||
let home_dir = dirs::home_dir();
|
||||
let warp_root = warp_data_dir();
|
||||
let warp_root = galaxy_data_dir();
|
||||
self.file_based_servers_by_root
|
||||
.iter()
|
||||
.any(|(root_path, provider_map)| {
|
||||
@@ -264,7 +264,7 @@ impl FileBasedMCPManager {
|
||||
/// Returns `true` if the server identified by `hash` is referenced from the global
|
||||
/// Warp config (`~/.warp/.mcp.json`). Global Warp servers always auto-spawn.
|
||||
fn is_global_warp_server(&self, hash: u64) -> bool {
|
||||
let warp_root = warp_data_dir();
|
||||
let warp_root = galaxy_data_dir();
|
||||
self.file_based_servers_by_root
|
||||
.get(&warp_root)
|
||||
.and_then(|provider_map| provider_map.get(&MCPProvider::Warp))
|
||||
@@ -432,7 +432,7 @@ impl FileBasedMCPManager {
|
||||
// Global Warp installs live under `~/.warp-core/`, which is internal Warp state
|
||||
// rather than a meaningful working directory. Map them to the home dir so
|
||||
// all global installs (Warp and third-party) share a consistent cwd.
|
||||
if discovery_root == warp_data_dir() {
|
||||
if discovery_root == galaxy_data_dir() {
|
||||
return dirs::home_dir().or(Some(discovery_root));
|
||||
}
|
||||
Some(discovery_root)
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::ai::mcp::FileMCPWatcher;
|
||||
use crate::ai::mcp::ParsedTemplatableMCPServerResult;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::settings::{AISettings, FocusedTerminalInfo};
|
||||
use crate::warp_managed_paths_watcher::{warp_data_dir, WarpManagedPathsWatcher};
|
||||
use crate::galaxy_managed_paths_watcher::{galaxy_data_dir, GalaxyManagedPathsWatcher};
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use galaxyui::{App, Entity, ModelHandle, SingletonEntity as _};
|
||||
@@ -22,7 +22,7 @@ fn setup_app(app: &mut App) -> galaxyui::ModelHandle<FileBasedMCPManager> {
|
||||
app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(FileMCPWatcher::new);
|
||||
app.add_singleton_model(AISettings::new_with_defaults);
|
||||
app.add_singleton_model(|_| AuthStateProvider::new_for_test());
|
||||
@@ -260,7 +260,7 @@ fn test_update_file_based_servers_removes_unreferenced_servers() {
|
||||
#[test]
|
||||
fn test_global_warp_server_always_spawns() {
|
||||
let _flag_guard = FeatureFlag::FileBasedMcp.override_enabled(true);
|
||||
let warp_root = warp_data_dir();
|
||||
let warp_root = galaxy_data_dir();
|
||||
let parsed = parse_mcp_json(r#"{"global-warp": {"command": "npx", "args": ["warp"]}}"#);
|
||||
|
||||
App::test((), |mut app| async move {
|
||||
|
||||
@@ -19,8 +19,8 @@ use crate::ai::mcp::{
|
||||
home_config_file_path, parsing::normalize_codex_toml_to_json, MCPProvider,
|
||||
ParsedTemplatableMCPServerResult,
|
||||
};
|
||||
use crate::warp_managed_paths_watcher::{
|
||||
warp_managed_mcp_config_path, WarpManagedPathsWatcher, WarpManagedPathsWatcherEvent,
|
||||
use crate::galaxy_managed_paths_watcher::{
|
||||
galaxy_managed_mcp_config_path, GalaxyManagedPathsWatcher, GalaxyManagedPathsWatcherEvent,
|
||||
};
|
||||
use crate::HomeDirectoryWatcher;
|
||||
use strum::IntoEnumIterator;
|
||||
@@ -173,12 +173,12 @@ impl FileMCPWatcher {
|
||||
ctx.subscribe_to_model(&HomeDirectoryWatcher::handle(ctx), |me, event, ctx| {
|
||||
me.handle_home_directory_watcher_event(event, ctx);
|
||||
});
|
||||
ctx.subscribe_to_model(&WarpManagedPathsWatcher::handle(ctx), |me, event, ctx| {
|
||||
ctx.subscribe_to_model(&GalaxyManagedPathsWatcher::handle(ctx), |me, event, ctx| {
|
||||
me.handle_warp_managed_paths_event(event, ctx);
|
||||
});
|
||||
|
||||
let mut home_provider_watchers = HashMap::new();
|
||||
if let Some(mcp_config_path) = warp_managed_mcp_config_path() {
|
||||
if let Some(mcp_config_path) = galaxy_managed_mcp_config_path() {
|
||||
Self::spawn_config_parse(
|
||||
mcp_config_path.config_path,
|
||||
mcp_config_path.root_path,
|
||||
@@ -418,11 +418,11 @@ impl FileMCPWatcher {
|
||||
|
||||
fn handle_warp_managed_paths_event(
|
||||
&mut self,
|
||||
event: &WarpManagedPathsWatcherEvent,
|
||||
event: &GalaxyManagedPathsWatcherEvent,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let WarpManagedPathsWatcherEvent::FilesChanged(update) = event;
|
||||
let Some(mcp_config_path) = warp_managed_mcp_config_path() else {
|
||||
let GalaxyManagedPathsWatcherEvent::FilesChanged(update) = event;
|
||||
let Some(mcp_config_path) = galaxy_managed_mcp_config_path() else {
|
||||
return;
|
||||
};
|
||||
let config_path = mcp_config_path.config_path;
|
||||
|
||||
@@ -48,7 +48,7 @@ cfg_if::cfg_if! {
|
||||
|
||||
pub(crate) fn home_config_file_path(provider: MCPProvider) -> Option<PathBuf> {
|
||||
match provider {
|
||||
MCPProvider::Warp => galaxy_core::paths::warp_home_mcp_config_file_path(),
|
||||
MCPProvider::Warp => galaxy_core::paths::galaxy_home_mcp_config_file_path(),
|
||||
_ => dirs::home_dir().map(|home_dir| home_dir.join(provider.home_config_path())),
|
||||
}
|
||||
}
|
||||
@@ -146,11 +146,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn mcp_provider_from_file_path_recognizes_warp_home_path() {
|
||||
if let Some(warp_home_mcp_config_file_path) =
|
||||
galaxy_core::paths::warp_home_mcp_config_file_path()
|
||||
if let Some(galaxy_home_mcp_config_file_path) =
|
||||
galaxy_core::paths::galaxy_home_mcp_config_file_path()
|
||||
{
|
||||
assert_eq!(
|
||||
mcp_provider_from_file_path(&warp_home_mcp_config_file_path),
|
||||
mcp_provider_from_file_path(&galaxy_home_mcp_config_file_path),
|
||||
Some(MCPProvider::Warp)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ use super::{
|
||||
use watcher::{BulkFilesystemWatcherEvent, HomeDirectoryWatcher, HomeDirectoryWatcherEvent};
|
||||
|
||||
use crate::server::datetime_ext::DateTimeExt;
|
||||
use crate::warp_managed_paths_watcher::{
|
||||
filter_repository_update_by_prefix, warp_managed_skill_dirs, WarpManagedPathsWatcher,
|
||||
WarpManagedPathsWatcherEvent,
|
||||
use crate::galaxy_managed_paths_watcher::{
|
||||
filter_repository_update_by_prefix, galaxy_managed_skill_dirs, GalaxyManagedPathsWatcher,
|
||||
GalaxyManagedPathsWatcherEvent,
|
||||
};
|
||||
use ai::skills::{
|
||||
home_skills_path, parse_skill, ParsedSkill, SkillProvider, SKILL_PROVIDER_DEFINITIONS,
|
||||
@@ -124,7 +124,7 @@ impl SkillWatcher {
|
||||
}
|
||||
},
|
||||
);
|
||||
ctx.subscribe_to_model(&WarpManagedPathsWatcher::handle(ctx), |me, event, ctx| {
|
||||
ctx.subscribe_to_model(&GalaxyManagedPathsWatcher::handle(ctx), |me, event, ctx| {
|
||||
me.handle_warp_managed_paths_event(event, ctx);
|
||||
});
|
||||
}
|
||||
@@ -138,7 +138,7 @@ impl SkillWatcher {
|
||||
// We use a separate HomeDirectoryWatcher to detect when those are created and start watching them after they are created.
|
||||
let mut home_provider_watchers = HashMap::new();
|
||||
if let Some(home_path) = home_dir {
|
||||
Self::spawn_read_skills_from_directories(warp_managed_skill_dirs(), ctx);
|
||||
Self::spawn_read_skills_from_directories(galaxy_managed_skill_dirs(), ctx);
|
||||
let skills_parent_paths: HashSet<PathBuf> = SKILL_PROVIDER_DEFINITIONS
|
||||
.iter()
|
||||
.filter(|provider| provider.provider != SkillProvider::Warp)
|
||||
@@ -806,11 +806,11 @@ impl SkillWatcher {
|
||||
|
||||
fn handle_warp_managed_paths_event(
|
||||
&mut self,
|
||||
event: &WarpManagedPathsWatcherEvent,
|
||||
event: &GalaxyManagedPathsWatcherEvent,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) {
|
||||
let WarpManagedPathsWatcherEvent::FilesChanged(update) = event;
|
||||
for skill_dir in warp_managed_skill_dirs() {
|
||||
let GalaxyManagedPathsWatcherEvent::FilesChanged(update) = event;
|
||||
for skill_dir in galaxy_managed_skill_dirs() {
|
||||
if let Some(filtered_update) = filter_repository_update_by_prefix(update, &skill_dir) {
|
||||
self.handle_repository_update(&filtered_update, ctx);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ use galaxyui::AppContext;
|
||||
use regex::Regex;
|
||||
use repo_metadata::{local_model::GetContentsArgs, RepoContent, RepoMetadataModel};
|
||||
|
||||
use crate::warp_managed_paths_watcher::warp_managed_skill_dirs;
|
||||
use crate::galaxy_managed_paths_watcher::galaxy_managed_skill_dirs;
|
||||
|
||||
/// Finds all skill directories in a repository by querying the RepoMetadataModel tree.
|
||||
///
|
||||
@@ -100,7 +100,7 @@ pub fn extract_skill_parent_directory(path: &Path) -> Result<PathBuf, Error> {
|
||||
&& path
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.is_some_and(|parent| warp_managed_skill_dirs().iter().any(|dir| parent == dir));
|
||||
.is_some_and(|parent| galaxy_managed_skill_dirs().iter().any(|dir| parent == dir));
|
||||
if is_warp_home_skill {
|
||||
return dirs::home_dir()
|
||||
.ok_or_else(|| anyhow::anyhow!("Home directory not available for {}", path.display()));
|
||||
@@ -136,7 +136,7 @@ pub fn is_home_skill_directory(path: &Path) -> bool {
|
||||
pub fn is_home_provider_path(path: &Path) -> bool {
|
||||
SKILL_PROVIDER_DEFINITIONS.iter().any(|provider| {
|
||||
if provider.provider == SkillProvider::Warp {
|
||||
return warp_managed_skill_dirs().iter().any(|dir| path == dir);
|
||||
return galaxy_managed_skill_dirs().iter().any(|dir| path == dir);
|
||||
}
|
||||
home_skills_path(provider.provider)
|
||||
.as_ref()
|
||||
|
||||
@@ -327,7 +327,7 @@ fn is_home_provider_path_true_for_known_providers() {
|
||||
let path = home_dir.join(".agents").join("skills");
|
||||
assert!(is_home_provider_path(&path));
|
||||
|
||||
if let Some(path) = galaxy_core::paths::warp_home_skills_dir() {
|
||||
if let Some(path) = galaxy_core::paths::galaxy_home_skills_dir() {
|
||||
assert!(is_home_provider_path(&path));
|
||||
}
|
||||
|
||||
@@ -350,12 +350,12 @@ fn extract_skill_parent_directory_returns_home_dir_for_warp_home_skill() {
|
||||
eprintln!("Skipping test: home directory not available");
|
||||
return;
|
||||
};
|
||||
let Some(warp_home_skills_dir) = galaxy_core::paths::warp_home_skills_dir() else {
|
||||
let Some(galaxy_home_skills_dir) = galaxy_core::paths::galaxy_home_skills_dir() else {
|
||||
eprintln!("Skipping test: Warp home skills directory not available");
|
||||
return;
|
||||
};
|
||||
|
||||
let skill_path = warp_home_skills_dir.join("test-skill").join("SKILL.md");
|
||||
let skill_path = galaxy_home_skills_dir.join("test-skill").join("SKILL.md");
|
||||
let result = extract_skill_parent_directory(&skill_path);
|
||||
assert_eq!(result.ok(), Some(home_dir));
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ use galaxyui::AppContext;
|
||||
use galaxyui::SingletonEntity as _;
|
||||
|
||||
use super::SkillManager;
|
||||
use crate::warp_managed_paths_watcher::warp_managed_skill_dirs;
|
||||
use crate::galaxy_managed_paths_watcher::galaxy_managed_skill_dirs;
|
||||
|
||||
const SKILL_FILE_NAME: &str = "SKILL.md";
|
||||
|
||||
@@ -64,7 +64,7 @@ fn home_skill_dirs_for_resolution() -> Vec<PathBuf> {
|
||||
let mut skill_dirs = Vec::new();
|
||||
for provider in SKILL_PROVIDER_DEFINITIONS.iter() {
|
||||
if provider.provider == SkillProvider::Warp {
|
||||
for dir in warp_managed_skill_dirs() {
|
||||
for dir in galaxy_managed_skill_dirs() {
|
||||
push_unique_path(&mut skill_dirs, dir);
|
||||
}
|
||||
} else if let Some(dir) = home_skills_path(provider.provider) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::warp_managed_paths_watcher::WarpManagedPathsWatcher;
|
||||
use crate::galaxy_managed_paths_watcher::GalaxyManagedPathsWatcher;
|
||||
use ai::skills::{ParsedSkill, SkillProvider, SkillScope};
|
||||
use galaxy_core::channel::ChannelState;
|
||||
use galaxyui::App;
|
||||
@@ -74,7 +74,7 @@ fn get_skills_for_working_directory_scopes_subdirectory_skills() {
|
||||
let repo_handle = app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
let skill_manager_handle = app.add_singleton_model(SkillManager::new);
|
||||
|
||||
// Register the repo root so get_root_for_path returns Some.
|
||||
@@ -196,7 +196,7 @@ fn get_skills_for_working_directory_name_collision_returns_both() {
|
||||
let repo_handle = app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
let skill_manager_handle = app.add_singleton_model(SkillManager::new);
|
||||
|
||||
// Register the repo root so get_root_for_path returns Some.
|
||||
@@ -292,7 +292,7 @@ fn cloud_environment_skills_always_included() {
|
||||
let repo_handle = app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
let skill_manager_handle = app.add_singleton_model(SkillManager::new);
|
||||
|
||||
let canonical_repo_a =
|
||||
@@ -493,7 +493,7 @@ fn best_supported_provider_fast_path_returns_deduped_provider() {
|
||||
app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
let handle = app.add_singleton_model(SkillManager::new);
|
||||
|
||||
let claude_skill = make_skill("deploy", ".claude");
|
||||
@@ -518,7 +518,7 @@ fn best_supported_provider_remaps_to_supported_provider() {
|
||||
app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
let handle = app.add_singleton_model(SkillManager::new);
|
||||
|
||||
let agents_skill = make_skill("deploy", ".agents");
|
||||
@@ -548,7 +548,7 @@ fn best_supported_provider_falls_back_when_no_match() {
|
||||
app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(RepoMetadataModel::new);
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
let handle = app.add_singleton_model(SkillManager::new);
|
||||
|
||||
let agents_skill = make_skill("deploy", ".agents");
|
||||
|
||||
@@ -21,7 +21,7 @@ use std::hash::{Hash, Hasher};
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::warp_managed_paths_watcher::warp_managed_skill_dirs;
|
||||
use crate::galaxy_managed_paths_watcher::galaxy_managed_skill_dirs;
|
||||
|
||||
lazy_static! {
|
||||
static ref CONTENT_HASHER: SipHasher = SipHasher::new_with_keys(0, 0);
|
||||
@@ -167,7 +167,7 @@ pub fn icon_override_for_skill_name(name: &str) -> Option<Icon> {
|
||||
pub fn skill_path_from_file_path(file_path: &Path) -> Option<PathBuf> {
|
||||
for definition in SKILL_PROVIDER_DEFINITIONS.iter() {
|
||||
let home_skill_dirs = if definition.provider == SkillProvider::Warp {
|
||||
warp_managed_skill_dirs()
|
||||
galaxy_managed_skill_dirs()
|
||||
} else {
|
||||
home_skills_path(definition.provider).into_iter().collect()
|
||||
};
|
||||
|
||||
@@ -14,18 +14,18 @@ fn test_skill_path_from_file_path_skill_md() {
|
||||
|
||||
#[test]
|
||||
fn test_skill_path_from_file_path_warp_home_skill() {
|
||||
let Some(warp_home_skills_dir) = galaxy_core::paths::warp_home_skills_dir() else {
|
||||
let Some(galaxy_home_skills_dir) = galaxy_core::paths::galaxy_home_skills_dir() else {
|
||||
eprintln!("Skipping test: Warp home skills directory not available");
|
||||
return;
|
||||
};
|
||||
let warp_home_skill = warp_home_skills_dir
|
||||
let warp_home_skill = galaxy_home_skills_dir
|
||||
.join("my-skill")
|
||||
.join("assets")
|
||||
.join("image.png");
|
||||
let result = skill_path_from_file_path(&warp_home_skill);
|
||||
assert_eq!(
|
||||
result,
|
||||
Some(warp_home_skills_dir.join("my-skill").join("SKILL.md"))
|
||||
Some(galaxy_home_skills_dir.join("my-skill").join("SKILL.md"))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::terminal::alt_screen_reporting::AltScreenReporting;
|
||||
use crate::terminal::session_settings::SessionSettings;
|
||||
use crate::terminal::settings::{SpacingMode, TerminalSettings};
|
||||
use crate::undo_close::UndoCloseStack;
|
||||
use crate::user_config::WarpConfig;
|
||||
use crate::user_config::GalaxyConfig;
|
||||
use crate::util::bindings::{self, trigger_to_keystroke, CustomAction};
|
||||
use crate::util::links;
|
||||
use crate::workspace::sync_inputs::SyncedInputState;
|
||||
@@ -138,7 +138,7 @@ fn updateable_custom_item_without_checkmark(action: CustomAction, ctx: &AppConte
|
||||
|
||||
fn make_new_app_menu(ctx: &AppContext) -> Menu {
|
||||
let mut menu_items = vec![updateable_custom_item_without_checkmark(
|
||||
CustomAction::ShowAboutWarp,
|
||||
CustomAction::ShowAboutGalaxy,
|
||||
ctx,
|
||||
)];
|
||||
|
||||
@@ -375,7 +375,7 @@ fn make_new_edit_menu(ctx: &AppContext) -> Menu {
|
||||
|
||||
fn make_new_view_menu(ctx: &AppContext) -> Menu {
|
||||
let mut items = vec![
|
||||
updateable_custom_item_without_checkmark(CustomAction::ToggleWarpDrive, ctx),
|
||||
updateable_custom_item_without_checkmark(CustomAction::ToggleGalaxyDrive, ctx),
|
||||
MenuItem::Separator,
|
||||
updateable_custom_item_without_checkmark(CustomAction::CommandPalette, ctx),
|
||||
updateable_custom_item_without_checkmark(CustomAction::NavigationPalette, ctx),
|
||||
@@ -605,7 +605,7 @@ fn make_new_drive_menu(ctx: &AppContext) -> Menu {
|
||||
));
|
||||
items.extend([
|
||||
MenuItem::Separator,
|
||||
updateable_custom_item_without_checkmark(CustomAction::ToggleWarpDrive, ctx),
|
||||
updateable_custom_item_without_checkmark(CustomAction::ToggleGalaxyDrive, ctx),
|
||||
updateable_custom_item_without_checkmark(CustomAction::SearchDrive, ctx),
|
||||
updateable_custom_item_without_checkmark(CustomAction::OpenTeamSettings, ctx),
|
||||
updateable_custom_item_without_checkmark(CustomAction::OpenAIFactCollection, ctx),
|
||||
@@ -934,7 +934,7 @@ fn make_new_help_menu() -> Menu {
|
||||
fn make_launch_config_menu_items(ctx: &mut AppContext) -> Vec<MenuItem> {
|
||||
let mut launch_config_menu_items = vec![];
|
||||
|
||||
let launch_configs = WarpConfig::handle(ctx).as_ref(ctx).launch_configs();
|
||||
let launch_configs = GalaxyConfig::handle(ctx).as_ref(ctx).launch_configs();
|
||||
for config in launch_configs {
|
||||
launch_config_menu_items.push(MenuItem::Custom(CustomMenuItem::new(
|
||||
&config.name,
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::ai::ambient_agents::AmbientAgentTaskId;
|
||||
use crate::ai::blocklist::InputConfig;
|
||||
use crate::ai::blocklist::SerializedBlockListItem;
|
||||
use crate::code::editor_management::CodeSource;
|
||||
use crate::drive::OpenWarpDriveObjectSettings;
|
||||
use crate::drive::OpenGalaxyDriveObjectSettings;
|
||||
use crate::root_view::quake_mode_window_id;
|
||||
use crate::server::ids::SyncId;
|
||||
use crate::settings_view::{environments_page::EnvironmentsPage, SettingsSection};
|
||||
@@ -216,7 +216,7 @@ pub enum NotebookPaneSnapshot {
|
||||
/// server ID.
|
||||
notebook_id: Option<SyncId>,
|
||||
// Settings for the notebook pane when it's opened (such as a folder to focus upon opening)
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
settings: OpenGalaxyDriveObjectSettings,
|
||||
},
|
||||
LocalFileNotebook {
|
||||
/// The path to the local file that was open in this pane. This may be `None` if
|
||||
@@ -255,7 +255,7 @@ pub enum WorkflowPaneSnapshot {
|
||||
CloudWorkflow {
|
||||
workflow_id: Option<SyncId>,
|
||||
// Settings for the workflow pane when it's opened (such as a folder to focus upon opening)
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
settings: OpenGalaxyDriveObjectSettings,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
active_theme_kind, FontSettings, FontSettingsChangedEvent, MonospaceFontSize, Settings,
|
||||
ThemeSettings,
|
||||
},
|
||||
themes::theme::{ThemeKind, WarpTheme},
|
||||
themes::theme::{ThemeKind, GalaxyTheme},
|
||||
ASSETS,
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ pub use galaxy_core::ui::appearance::{Appearance, AppearanceEvent};
|
||||
pub struct AppearanceManager {
|
||||
// The transient theme is a theme that is set by the user but not saved
|
||||
// as a setting. It is used when the user is actively choosing a theme.
|
||||
transient_theme: Option<WarpTheme>,
|
||||
transient_theme: Option<GalaxyTheme>,
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
app_icon_at_startup: AppIcon,
|
||||
@@ -438,10 +438,10 @@ fn build_appearance(ctx: &mut AppContext) -> Appearance {
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
fn emit_theme_background_event(theme: &WarpTheme) {
|
||||
fn emit_theme_background_event(theme: &GalaxyTheme) {
|
||||
let bg = theme.background().into_solid();
|
||||
let color = format!("#{:02x}{:02x}{:02x}", bg.r, bg.g, bg.b);
|
||||
crate::platform::wasm::emit_event(crate::platform::wasm::WarpEvent::ThemeBackgroundChanged {
|
||||
crate::platform::wasm::emit_event(crate::platform::wasm::GalaxyEvent::ThemeBackgroundChanged {
|
||||
color,
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -268,7 +268,7 @@ pub fn log_out(app: &mut AppContext) {
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
crate::platform::wasm::emit_event(crate::platform::wasm::WarpEvent::LoggedOut);
|
||||
crate::platform::wasm::emit_event(crate::platform::wasm::GalaxyEvent::LoggedOut);
|
||||
}
|
||||
|
||||
// Remove the cloud persisted settings from user defaults.
|
||||
|
||||
@@ -26,7 +26,7 @@ use galaxyui::r#async::Timer;
|
||||
use galaxyui::windowing::state::ApplicationStage;
|
||||
use galaxyui::windowing::{self, WindowManager};
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
AppContext,
|
||||
};
|
||||
use galaxyui::{Entity, ModelContext, SingletonEntity, ViewContext};
|
||||
@@ -679,12 +679,12 @@ pub fn accessibility_content(
|
||||
(RequestType::ManualCheck, Ok(UpdateReady::Yes { .. })) => Some(AccessibilityContent::new(
|
||||
"Update available.",
|
||||
"Use the command palette to install and relaunch Galaxy",
|
||||
WarpA11yRole::HelpRole,
|
||||
GalaxyA11yRole::HelpRole,
|
||||
)),
|
||||
// Any non-successful autoupdate check
|
||||
(RequestType::ManualCheck, _) => Some(AccessibilityContent::new_without_help(
|
||||
"No updates available",
|
||||
WarpA11yRole::HelpRole,
|
||||
GalaxyA11yRole::HelpRole,
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
drive::{
|
||||
folders::{CloudFolderModel, FolderId},
|
||||
items::WarpDriveItem,
|
||||
CloudObjectTypeAndId, OpenWarpDriveObjectArgs, OpenWarpDriveObjectSettings,
|
||||
CloudObjectTypeAndId, OpenGalaxyDriveObjectArgs, OpenGalaxyDriveObjectSettings,
|
||||
},
|
||||
env_vars::CloudEnvVarCollectionModel,
|
||||
notebooks::{CloudNotebookModel, NotebookId},
|
||||
@@ -935,7 +935,7 @@ where
|
||||
/// can be opened natively in Warp with no web interaction.
|
||||
pub fn extract_server_id_and_object_type_from_warp_drive_link(
|
||||
url: &Url,
|
||||
) -> Option<OpenWarpDriveObjectArgs> {
|
||||
) -> Option<OpenGalaxyDriveObjectArgs> {
|
||||
let server_id = url
|
||||
.path_segments()
|
||||
.and_then(|mut segments| segments.next_back())
|
||||
@@ -958,13 +958,13 @@ pub fn extract_server_id_and_object_type_from_warp_drive_link(
|
||||
|
||||
let invitee_email: Option<String> = query_string.get("invitee_email").map(|s| s.to_string());
|
||||
|
||||
Some(OpenWarpDriveObjectArgs {
|
||||
Some(OpenGalaxyDriveObjectArgs {
|
||||
object_type,
|
||||
server_id: match server_id {
|
||||
Some(server_id) => server_id.try_into().ok()?,
|
||||
_ => return None,
|
||||
},
|
||||
settings: OpenWarpDriveObjectSettings {
|
||||
settings: OpenGalaxyDriveObjectSettings {
|
||||
focused_folder_id,
|
||||
invitee_email,
|
||||
},
|
||||
|
||||
@@ -20,7 +20,7 @@ use galaxyui::elements::{ChildAnchor, OffsetPositioning, Radius, SavePosition, S
|
||||
use galaxyui::keymap::EditableBinding;
|
||||
use galaxyui::ui_components::components::UiComponent;
|
||||
pub use galaxyui::{
|
||||
accessibility::{AccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
elements::{ParentElement as _, Stack},
|
||||
geometry::vector::vec2f,
|
||||
AppContext,
|
||||
@@ -378,10 +378,10 @@ impl CodeEditorFind {
|
||||
self.searcher.as_ref(ctx).match_count()
|
||||
),
|
||||
"Use enter and shift-enter to navigate between matches. Escape to quit.",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
)
|
||||
} else {
|
||||
AccessibilityContent::new_without_help("No results.", WarpA11yRole::UserAction)
|
||||
AccessibilityContent::new_without_help("No results.", GalaxyA11yRole::UserAction)
|
||||
};
|
||||
ctx.emit_a11y_content(content);
|
||||
}
|
||||
@@ -396,12 +396,12 @@ impl CodeEditorFind {
|
||||
"Successfully replaced match. Selected match is {match_index} of {remaining_matches}"
|
||||
),
|
||||
"Continue pressing Enter to replace more matches, or use up/down arrows to navigate.",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
)
|
||||
} else {
|
||||
AccessibilityContent::new_without_help(
|
||||
"Successfully replaced the last match.",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
)
|
||||
};
|
||||
ctx.emit_a11y_content(content);
|
||||
@@ -946,7 +946,7 @@ impl View for CodeEditorFind {
|
||||
Some(AccessibilityContent::new(
|
||||
description,
|
||||
help_text,
|
||||
WarpA11yRole::TextareaRole,
|
||||
GalaxyA11yRole::TextareaRole,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ pub enum CodeSource {
|
||||
},
|
||||
/// Opened from an active AI agent conversation.
|
||||
AIAction { id: AIAgentActionId },
|
||||
/// Opened from project rules (WARP.md) file.
|
||||
/// Opened from project rules (GALAXY.md) file.
|
||||
ProjectRules { path: PathBuf },
|
||||
/// Opened from file tree.
|
||||
FileTree { path: PathBuf },
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use galaxy_core::ui::{
|
||||
appearance::Appearance, icons::Icon as WarpIcon, theme::color::internal_colors,
|
||||
appearance::Appearance, icons::Icon as GalaxyIcon, theme::color::internal_colors,
|
||||
};
|
||||
use galaxy_files::FileModel;
|
||||
use galaxyui::{
|
||||
@@ -520,7 +520,7 @@ fn render_header(
|
||||
let icon_color = theme.sub_text_color(theme.background());
|
||||
let close_button = Hoverable::new(back_mouse_state, move |state| {
|
||||
let close_icon = ConstrainedBox::new(
|
||||
galaxyui::elements::Icon::new(WarpIcon::X.into(), icon_color).finish(),
|
||||
galaxyui::elements::Icon::new(GalaxyIcon::X.into(), icon_color).finish(),
|
||||
)
|
||||
.with_width(16.)
|
||||
.with_height(16.)
|
||||
|
||||
@@ -10,7 +10,7 @@ use lsp::{
|
||||
|
||||
use crate::code::lsp_telemetry::{LspControlActionType, LspEnablementSource, LspTelemetryEvent};
|
||||
use galaxy_core::ui::theme::color::internal_colors;
|
||||
use galaxy_core::ui::theme::{Fill as ThemeFill, WarpTheme};
|
||||
use galaxy_core::ui::theme::{Fill as ThemeFill, GalaxyTheme};
|
||||
use galaxy_core::ui::{appearance::Appearance, Icon};
|
||||
use galaxyui::elements::{
|
||||
ChildAnchor, ChildView, Dismiss, Empty, Hoverable, MainAxisSize, MouseStateHandle,
|
||||
@@ -167,7 +167,7 @@ enum LSPServerRenderStatus {
|
||||
}
|
||||
|
||||
impl LSPServerRenderStatus {
|
||||
fn to_icon_color(&self, theme: &WarpTheme) -> ColorU {
|
||||
fn to_icon_color(&self, theme: &GalaxyTheme) -> ColorU {
|
||||
match self {
|
||||
LSPServerRenderStatus::Available => AnsiColorIdentifier::Green
|
||||
.to_ansi_color(&theme.terminal_colors().normal)
|
||||
@@ -285,7 +285,7 @@ impl CodeFooterView {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_tab_config_info_icon(theme: &WarpTheme) -> Box<dyn Element> {
|
||||
fn render_tab_config_info_icon(theme: &GalaxyTheme) -> Box<dyn Element> {
|
||||
Container::new(
|
||||
ConstrainedBox::new(
|
||||
Icon::Info
|
||||
@@ -1376,7 +1376,7 @@ impl CodeFooterView {
|
||||
|
||||
/// Computes the aggregate indicator color across all tracked servers.
|
||||
/// Priority: Failed > Busy > Stopped > Available.
|
||||
fn aggregate_indicator_color(&self, theme: &WarpTheme, app: &AppContext) -> ColorU {
|
||||
fn aggregate_indicator_color(&self, theme: &GalaxyTheme, app: &AppContext) -> ColorU {
|
||||
if self.lsp_servers.is_empty() {
|
||||
return LSPServerRenderStatus::Stopped.to_icon_color(theme);
|
||||
}
|
||||
@@ -1449,7 +1449,7 @@ impl CodeFooterView {
|
||||
}
|
||||
|
||||
fn render_status_text(
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
appearance: &Appearance,
|
||||
message: String,
|
||||
) -> Box<dyn Element> {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use galaxy_core::ui::{
|
||||
appearance::Appearance,
|
||||
theme::{color::internal_colors, WarpTheme},
|
||||
theme::{color::internal_colors, GalaxyTheme},
|
||||
};
|
||||
use galaxy_editor::{
|
||||
content::buffer::InitialBufferState,
|
||||
@@ -575,7 +575,7 @@ impl LocalCodeEditorView {
|
||||
}
|
||||
|
||||
/// Render a separator line between hover card sections.
|
||||
fn render_separator(theme: &WarpTheme) -> Box<dyn Element> {
|
||||
fn render_separator(theme: &GalaxyTheme) -> Box<dyn Element> {
|
||||
Container::new(
|
||||
ConstrainedBox::new(
|
||||
Rect::new()
|
||||
|
||||
@@ -172,7 +172,7 @@ use crate::{
|
||||
editor::InteractionState,
|
||||
pane_group::pane::{view, BackingView, PaneEvent},
|
||||
send_telemetry_from_ctx,
|
||||
themes::theme::WarpTheme,
|
||||
themes::theme::GalaxyTheme,
|
||||
};
|
||||
|
||||
use vec1::Vec1;
|
||||
@@ -1383,7 +1383,7 @@ impl CodeReviewView {
|
||||
let init_project_button = ctx.add_typed_action_view(|_ctx| {
|
||||
ActionButton::new("Initialize codebase", NakedTheme)
|
||||
.with_size(ButtonSize::Small)
|
||||
.with_tooltip("Enables codebase indexing and WARP.md")
|
||||
.with_tooltip("Enables codebase indexing and GALAXY.md")
|
||||
.with_tooltip_alignment(TooltipAlignment::Center)
|
||||
.on_click(|ctx| {
|
||||
ctx.dispatch_typed_action(CodeReviewAction::InitProjectForCurrentDirectory)
|
||||
@@ -5528,7 +5528,7 @@ impl CodeReviewView {
|
||||
|
||||
fn styled_file_content_container(
|
||||
content: Box<dyn Element>,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
) -> Box<dyn Element> {
|
||||
Container::new(
|
||||
Flex::row()
|
||||
|
||||
@@ -43,7 +43,7 @@ pub(crate) struct HeaderClickHandler {
|
||||
/// (rounded corners, neutral background, outline border).
|
||||
fn comment_card_container(
|
||||
content: Box<dyn Element>,
|
||||
theme: &galaxy_core::ui::theme::WarpTheme,
|
||||
theme: &galaxy_core::ui::theme::GalaxyTheme,
|
||||
) -> Box<dyn Element> {
|
||||
Container::new(content)
|
||||
.with_corner_radius(CornerRadius::with_all(Radius::Pixels(8.)))
|
||||
|
||||
@@ -9,7 +9,7 @@ use galaxy_core::channel::ChannelState;
|
||||
use galaxyui::windowing;
|
||||
|
||||
pub(crate) fn run() -> anyhow::Result<()> {
|
||||
println!("Warp version: {:?}", ChannelState::app_version());
|
||||
println!("Galaxy version: {:?}", ChannelState::app_version());
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::{
|
||||
appearance::Appearance,
|
||||
cloud_object::{model::persistence::CloudModel, CloudObject, Owner},
|
||||
server::{ids::SyncId, sync_queue::SyncQueue},
|
||||
themes::theme::WarpTheme,
|
||||
themes::theme::GalaxyTheme,
|
||||
workspaces::user_workspaces::UserWorkspaces,
|
||||
};
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
@@ -184,7 +184,7 @@ impl ImportModal {
|
||||
fn render_breadcrumbs(
|
||||
&self,
|
||||
appearance: &Appearance,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let (breadcrumb_text, highlight_indices) = self.breadcrumb(app);
|
||||
@@ -222,7 +222,7 @@ impl ImportModal {
|
||||
fn render_header(
|
||||
&self,
|
||||
appearance: &Appearance,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let top_row = Flex::row()
|
||||
@@ -258,7 +258,7 @@ impl ImportModal {
|
||||
.finish()
|
||||
}
|
||||
|
||||
fn render_body(&self, theme: &WarpTheme) -> Box<dyn Element> {
|
||||
fn render_body(&self, theme: &GalaxyTheme) -> Box<dyn Element> {
|
||||
Container::new(
|
||||
ConstrainedBox::new(
|
||||
ClippedScrollable::vertical(
|
||||
|
||||
@@ -46,7 +46,7 @@ impl WarpDriveItem for WarpDriveFolder {
|
||||
fn icon(&self, appearance: &Appearance, color: Option<Fill>) -> Option<Box<dyn Element>> {
|
||||
let icon_fill =
|
||||
color.unwrap_or(warp_drive_icon_color(appearance, DriveObjectType::Folder).into());
|
||||
let icon = if FeatureFlag::WarpPacks.is_enabled() && self.folder.model().is_warp_pack {
|
||||
let icon = if FeatureFlag::GalaxyPacks.is_enabled() && self.folder.model().is_warp_pack {
|
||||
Icon::PackageCheck
|
||||
} else {
|
||||
Icon::from(DriveObjectType::Folder)
|
||||
|
||||
@@ -88,7 +88,7 @@ impl fmt::Display for DriveObjectType {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Default)]
|
||||
pub struct OpenWarpDriveObjectSettings {
|
||||
pub struct OpenGalaxyDriveObjectSettings {
|
||||
/// The folder that should be focused in the Warp Drive when the object is opened.
|
||||
pub focused_folder_id: Option<ServerId>,
|
||||
/// The email of the user to invite to the object, if the object is being opened via the request access flow.
|
||||
@@ -96,10 +96,10 @@ pub struct OpenWarpDriveObjectSettings {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct OpenWarpDriveObjectArgs {
|
||||
pub struct OpenGalaxyDriveObjectArgs {
|
||||
pub object_type: ObjectType,
|
||||
pub server_id: ServerId,
|
||||
pub settings: OpenWarpDriveObjectSettings,
|
||||
pub settings: OpenGalaxyDriveObjectSettings,
|
||||
}
|
||||
|
||||
/// Enum to use to pass down type and id between actions to avoid multiplying actions whenever we
|
||||
|
||||
@@ -122,7 +122,7 @@ use galaxyui::text::TextBuffer;
|
||||
use galaxyui::text_layout::TextStyle;
|
||||
use galaxyui::windowing::WindowManager;
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, GalaxyA11yRole},
|
||||
fonts::Cache as FontCache,
|
||||
keymap::{EditableBinding, FixedBinding},
|
||||
AppContext, Element, Entity, ModelAsRef, ModelHandle, View, ViewContext, WindowId,
|
||||
@@ -8368,7 +8368,7 @@ impl TypedActionView for EditorView {
|
||||
) -> ActionAccessibilityContent {
|
||||
match action {
|
||||
EditorAction::UserInsert(text) => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help(text.to_string(), WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help(text.to_string(), GalaxyA11yRole::UserAction),
|
||||
),
|
||||
EditorAction::SelectLeft
|
||||
| EditorAction::SelectToLineEnd
|
||||
@@ -8403,7 +8403,7 @@ impl TypedActionView for EditorView {
|
||||
EditorAction::Paste => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
format!("Pasting: {}", self.clipboard_content(ctx)),
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
_ => ActionAccessibilityContent::from_debug(),
|
||||
|
||||
@@ -27,7 +27,7 @@ use std::{
|
||||
};
|
||||
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
text_layout::TextStyle,
|
||||
AppContext, Entity, ModelAsRef, ModelContext, ModelHandle,
|
||||
};
|
||||
@@ -505,7 +505,7 @@ impl EditorModel {
|
||||
let delta = &text[start.as_usize()..end.as_usize()];
|
||||
match (was_selecting, is_selecting) {
|
||||
(false, false) => {
|
||||
AccessibilityContent::new_without_help(delta, WarpA11yRole::UserAction)
|
||||
AccessibilityContent::new_without_help(delta, GalaxyA11yRole::UserAction)
|
||||
}
|
||||
(_, true) => {
|
||||
// Note that Range is start <= x < end, and in our case, when deciding what was the action
|
||||
@@ -529,10 +529,10 @@ impl EditorModel {
|
||||
} else {
|
||||
"unselected"
|
||||
};
|
||||
AccessibilityContent::new(delta, format!(", {action}"), WarpA11yRole::UserAction)
|
||||
AccessibilityContent::new(delta, format!(", {action}"), GalaxyA11yRole::UserAction)
|
||||
}
|
||||
(true, false) => {
|
||||
AccessibilityContent::new_without_help("Unselected", WarpA11yRole::UserAction)
|
||||
AccessibilityContent::new_without_help("Unselected", GalaxyA11yRole::UserAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2230,7 +2230,7 @@ impl EditorModel {
|
||||
ctx.emit_a11y_content(AccessibilityContent::new(
|
||||
self.selected_text(ctx),
|
||||
", deleted",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
));
|
||||
self.change_selections(new_selections, ctx);
|
||||
self.insert("", None, ctx);
|
||||
@@ -2254,7 +2254,7 @@ impl EditorModel {
|
||||
ctx.emit_a11y_content(AccessibilityContent::new(
|
||||
self.selected_text(ctx),
|
||||
", deleted",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
));
|
||||
self.change_selections(new_selections, ctx);
|
||||
self.insert("", None, ctx);
|
||||
|
||||
@@ -16,21 +16,21 @@ use watcher::{BulkFilesystemWatcher, BulkFilesystemWatcherEvent};
|
||||
|
||||
/// Duration between filesystem watch events for the Warp managed paths watcher, in milliseconds.
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
const WARP_MANAGED_PATHS_WATCHER_DEBOUNCE_MILLI_SECS: u64 = 500;
|
||||
const GALAXY_MANAGED_PATHS_WATCHER_DEBOUNCE_MILLI_SECS: u64 = 500;
|
||||
|
||||
pub(crate) fn warp_data_dir() -> PathBuf {
|
||||
pub(crate) fn galaxy_data_dir() -> PathBuf {
|
||||
galaxy_core::paths::data_dir()
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub(crate) fn ensure_warp_watch_roots_exist() {}
|
||||
pub(crate) fn ensure_galaxy_watch_roots_exist() {}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub(crate) fn ensure_warp_watch_roots_exist() {
|
||||
let data_dir = warp_data_dir();
|
||||
pub(crate) fn ensure_galaxy_watch_roots_exist() {
|
||||
let data_dir = galaxy_data_dir();
|
||||
if let Err(err) = fs::create_dir_all(&data_dir) {
|
||||
log::warn!(
|
||||
"Failed to create Warp data directory {}: {err}",
|
||||
"Failed to create Galaxy data directory {}: {err}",
|
||||
data_dir.display()
|
||||
);
|
||||
}
|
||||
@@ -39,7 +39,7 @@ pub(crate) fn ensure_warp_watch_roots_exist() {
|
||||
if config_local_dir != data_dir {
|
||||
if let Err(err) = fs::create_dir_all(&config_local_dir) {
|
||||
log::warn!(
|
||||
"Failed to create Warp config directory {}: {err}",
|
||||
"Failed to create Galaxy config directory {}: {err}",
|
||||
config_local_dir.display()
|
||||
);
|
||||
}
|
||||
@@ -47,35 +47,35 @@ pub(crate) fn ensure_warp_watch_roots_exist() {
|
||||
}
|
||||
|
||||
#[cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
pub(crate) fn warp_home_config_dir() -> Option<PathBuf> {
|
||||
galaxy_core::paths::warp_home_config_dir()
|
||||
pub(crate) fn galaxy_home_config_dir() -> Option<PathBuf> {
|
||||
galaxy_core::paths::galaxy_home_config_dir()
|
||||
}
|
||||
|
||||
pub(crate) fn warp_home_skills_dir() -> Option<PathBuf> {
|
||||
galaxy_core::paths::warp_home_skills_dir()
|
||||
pub(crate) fn galaxy_home_skills_dir() -> Option<PathBuf> {
|
||||
galaxy_core::paths::galaxy_home_skills_dir()
|
||||
}
|
||||
|
||||
#[cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
pub(crate) fn warp_home_mcp_config_file_path() -> Option<PathBuf> {
|
||||
galaxy_core::paths::warp_home_mcp_config_file_path()
|
||||
pub(crate) fn galaxy_home_mcp_config_file_path() -> Option<PathBuf> {
|
||||
galaxy_core::paths::galaxy_home_mcp_config_file_path()
|
||||
}
|
||||
|
||||
#[cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct WarpMcpConfigPath {
|
||||
pub(crate) struct GalaxyMcpConfigPath {
|
||||
pub(crate) root_path: PathBuf,
|
||||
pub(crate) config_path: PathBuf,
|
||||
}
|
||||
|
||||
pub(crate) fn warp_managed_skill_dirs() -> Vec<PathBuf> {
|
||||
warp_home_skills_dir().into_iter().collect()
|
||||
pub(crate) fn galaxy_managed_skill_dirs() -> Vec<PathBuf> {
|
||||
galaxy_home_skills_dir().into_iter().collect()
|
||||
}
|
||||
|
||||
#[cfg_attr(target_family = "wasm", allow(dead_code))]
|
||||
pub(crate) fn warp_managed_mcp_config_path() -> Option<WarpMcpConfigPath> {
|
||||
Some(WarpMcpConfigPath {
|
||||
pub(crate) fn galaxy_managed_mcp_config_path() -> Option<GalaxyMcpConfigPath> {
|
||||
Some(GalaxyMcpConfigPath {
|
||||
root_path: home_dir()?,
|
||||
config_path: warp_home_mcp_config_file_path()?,
|
||||
config_path: galaxy_home_mcp_config_file_path()?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -200,23 +200,23 @@ fn filesystem_event_to_repository_update(event: &BulkFilesystemWatcherEvent) ->
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum WarpManagedPathsWatcherEvent {}
|
||||
pub(crate) enum GalaxyManagedPathsWatcherEvent {}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub(crate) enum WarpManagedPathsWatcherEvent {
|
||||
pub(crate) enum GalaxyManagedPathsWatcherEvent {
|
||||
FilesChanged(RepositoryUpdate),
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
pub(crate) struct WarpManagedPathsWatcher {
|
||||
pub(crate) struct GalaxyManagedPathsWatcher {
|
||||
_watcher: ModelHandle<BulkFilesystemWatcher>,
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
pub(crate) struct WarpManagedPathsWatcher;
|
||||
pub(crate) struct GalaxyManagedPathsWatcher;
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
impl WarpManagedPathsWatcher {
|
||||
impl GalaxyManagedPathsWatcher {
|
||||
pub(crate) fn new(ctx: &mut ModelContext<Self>) -> Self {
|
||||
Self::new_internal(ctx, true)
|
||||
}
|
||||
@@ -230,7 +230,7 @@ impl WarpManagedPathsWatcher {
|
||||
let watcher = if should_register_watcher {
|
||||
ctx.add_model(|ctx| {
|
||||
BulkFilesystemWatcher::new(
|
||||
Duration::from_millis(WARP_MANAGED_PATHS_WATCHER_DEBOUNCE_MILLI_SECS),
|
||||
Duration::from_millis(GALAXY_MANAGED_PATHS_WATCHER_DEBOUNCE_MILLI_SECS),
|
||||
ctx,
|
||||
)
|
||||
})
|
||||
@@ -240,7 +240,7 @@ impl WarpManagedPathsWatcher {
|
||||
ctx.subscribe_to_model(&watcher, Self::handle_fs_event);
|
||||
|
||||
if should_register_watcher {
|
||||
let data_dir = warp_data_dir();
|
||||
let data_dir = galaxy_data_dir();
|
||||
let config_local_dir = galaxy_core::paths::config_local_dir();
|
||||
let should_register_config_local_dir = config_local_dir != data_dir;
|
||||
let worktrees_dir = data_dir.join("worktrees");
|
||||
@@ -250,7 +250,7 @@ impl WarpManagedPathsWatcher {
|
||||
data_dir.clone(),
|
||||
WatchFilter::with_filter(Arc::new(move |path| !path.starts_with(&worktrees_dir))),
|
||||
RecursiveMode::Recursive,
|
||||
"Warp data directory",
|
||||
"Galaxy data directory",
|
||||
);
|
||||
if should_register_config_local_dir {
|
||||
Self::register_path(
|
||||
@@ -259,42 +259,42 @@ impl WarpManagedPathsWatcher {
|
||||
config_local_dir.clone(),
|
||||
WatchFilter::accept_all(),
|
||||
RecursiveMode::Recursive,
|
||||
"Warp config directory",
|
||||
"Galaxy config directory",
|
||||
);
|
||||
}
|
||||
if let Some(warp_home_skills_dir) = warp_home_skills_dir() {
|
||||
if warp_home_skills_dir.exists()
|
||||
&& !warp_home_skills_dir.starts_with(&data_dir)
|
||||
if let Some(galaxy_home_skills_dir) = galaxy_home_skills_dir() {
|
||||
if galaxy_home_skills_dir.exists()
|
||||
&& !galaxy_home_skills_dir.starts_with(&data_dir)
|
||||
&& (!should_register_config_local_dir
|
||||
|| !warp_home_skills_dir.starts_with(&config_local_dir))
|
||||
|| !galaxy_home_skills_dir.starts_with(&config_local_dir))
|
||||
{
|
||||
Self::register_path(
|
||||
ctx,
|
||||
&watcher,
|
||||
warp_home_skills_dir,
|
||||
galaxy_home_skills_dir,
|
||||
WatchFilter::accept_all(),
|
||||
RecursiveMode::Recursive,
|
||||
"Warp home skills directory",
|
||||
"Galaxy home skills directory",
|
||||
);
|
||||
}
|
||||
}
|
||||
if let (Some(warp_home_config_dir), Some(warp_home_mcp_config_path)) =
|
||||
(warp_home_config_dir(), warp_home_mcp_config_file_path())
|
||||
if let (Some(galaxy_home_config_dir), Some(warp_home_mcp_config_path)) =
|
||||
(galaxy_home_config_dir(), galaxy_home_mcp_config_file_path())
|
||||
{
|
||||
if warp_home_config_dir.exists()
|
||||
&& !warp_home_config_dir.starts_with(&data_dir)
|
||||
if galaxy_home_config_dir.exists()
|
||||
&& !galaxy_home_config_dir.starts_with(&data_dir)
|
||||
&& (!should_register_config_local_dir
|
||||
|| !warp_home_config_dir.starts_with(&config_local_dir))
|
||||
|| !galaxy_home_config_dir.starts_with(&config_local_dir))
|
||||
{
|
||||
Self::register_path(
|
||||
ctx,
|
||||
&watcher,
|
||||
warp_home_config_dir,
|
||||
galaxy_home_config_dir,
|
||||
WatchFilter::with_filter(Arc::new(move |path| {
|
||||
path == warp_home_mcp_config_path
|
||||
})),
|
||||
RecursiveMode::NonRecursive,
|
||||
"Warp home MCP config directory",
|
||||
"Galaxy home MCP config directory",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -333,13 +333,13 @@ impl WarpManagedPathsWatcher {
|
||||
) {
|
||||
let update = filesystem_event_to_repository_update(event);
|
||||
if !update.is_empty() {
|
||||
ctx.emit(WarpManagedPathsWatcherEvent::FilesChanged(update));
|
||||
ctx.emit(GalaxyManagedPathsWatcherEvent::FilesChanged(update));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "wasm")]
|
||||
impl WarpManagedPathsWatcher {
|
||||
impl GalaxyManagedPathsWatcher {
|
||||
pub(crate) fn new(_ctx: &mut ModelContext<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
@@ -350,11 +350,11 @@ impl WarpManagedPathsWatcher {
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for WarpManagedPathsWatcher {
|
||||
type Event = WarpManagedPathsWatcherEvent;
|
||||
impl Entity for GalaxyManagedPathsWatcher {
|
||||
type Event = GalaxyManagedPathsWatcherEvent;
|
||||
}
|
||||
|
||||
impl SingletonEntity for WarpManagedPathsWatcher {}
|
||||
impl SingletonEntity for GalaxyManagedPathsWatcher {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@@ -365,25 +365,25 @@ mod tests {
|
||||
use repo_metadata::{RepositoryUpdate, TargetFile};
|
||||
|
||||
use super::{
|
||||
filter_repository_update_by_prefix, warp_home_mcp_config_file_path, warp_home_skills_dir,
|
||||
warp_managed_mcp_config_path, warp_managed_skill_dirs,
|
||||
filter_repository_update_by_prefix, galaxy_home_mcp_config_file_path, galaxy_home_skills_dir,
|
||||
galaxy_managed_mcp_config_path, galaxy_managed_skill_dirs,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn warp_managed_skill_dirs_contains_only_warp_home_path() {
|
||||
let dirs = warp_managed_skill_dirs();
|
||||
match warp_home_skills_dir() {
|
||||
Some(warp_home_skills_dir) => assert_eq!(dirs, vec![warp_home_skills_dir]),
|
||||
fn galaxy_managed_skill_dirs_contains_only_warp_home_path() {
|
||||
let dirs = galaxy_managed_skill_dirs();
|
||||
match galaxy_home_skills_dir() {
|
||||
Some(galaxy_home_skills_dir) => assert_eq!(dirs, vec![galaxy_home_skills_dir]),
|
||||
None => assert!(dirs.is_empty()),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn warp_managed_mcp_config_path_contains_only_warp_home_path() {
|
||||
fn galaxy_managed_mcp_config_path_contains_only_warp_home_path() {
|
||||
match (
|
||||
home_dir(),
|
||||
warp_home_mcp_config_file_path(),
|
||||
warp_managed_mcp_config_path(),
|
||||
galaxy_home_mcp_config_file_path(),
|
||||
galaxy_managed_mcp_config_path(),
|
||||
) {
|
||||
(Some(home_dir), Some(warp_home_mcp_config_path), Some(path)) => {
|
||||
assert_eq!(path.root_path, home_dir);
|
||||
@@ -15,7 +15,7 @@ use galaxyui::elements::{
|
||||
};
|
||||
use galaxyui::ui_components::components::{Coords, UiComponent, UiComponentStyles};
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
elements::{
|
||||
Align, AnchorPair, Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
|
||||
DropShadow, Element, Empty, EventHandler, Flex, Highlight, Icon, OffsetPositioning,
|
||||
@@ -591,13 +591,13 @@ impl InputSuggestions {
|
||||
ctx.emit_a11y_content(AccessibilityContent::new(
|
||||
format!("Suggestion: {text}.\n"),
|
||||
desc,
|
||||
WarpA11yRole::MenuItemRole,
|
||||
GalaxyA11yRole::MenuItemRole,
|
||||
));
|
||||
}
|
||||
(Some(text), None) => {
|
||||
ctx.emit_a11y_content(AccessibilityContent::new_without_help(
|
||||
format!("Suggestion: {text}.\n"),
|
||||
WarpA11yRole::MenuItemRole,
|
||||
GalaxyA11yRole::MenuItemRole,
|
||||
));
|
||||
}
|
||||
_ => {}
|
||||
@@ -621,7 +621,7 @@ impl InputSuggestions {
|
||||
if let Some(text) = self.get_selected_item_text() {
|
||||
ctx.emit_a11y_content(AccessibilityContent::new_without_help(
|
||||
format!("Selected: {text}"),
|
||||
WarpA11yRole::MenuItemRole,
|
||||
GalaxyA11yRole::MenuItemRole,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -646,7 +646,7 @@ impl InputSuggestions {
|
||||
) {
|
||||
ctx.emit_a11y_content(AccessibilityContent::new_without_help(
|
||||
"Closed suggestions.",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
));
|
||||
ctx.emit(Event::CloseSuggestion {
|
||||
should_restore_buffer_before_history_up,
|
||||
@@ -1089,7 +1089,7 @@ impl View for InputSuggestions {
|
||||
// TODO use bindings from user settings
|
||||
"Navigate with tab and shift-tab, and confirm with enter. Execute selected command \
|
||||
with command + enter. Esc leaves the suggestions menu.",
|
||||
WarpA11yRole::MenuRole,
|
||||
GalaxyA11yRole::MenuRole,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ use string_offset::CharOffset;
|
||||
|
||||
use crate::{
|
||||
cloud_object::{model::persistence::CloudModel, CloudObjectEventEntrypoint, Space},
|
||||
drive::OpenWarpDriveObjectSettings,
|
||||
drive::OpenGalaxyDriveObjectSettings,
|
||||
integration_testing::view_getters::{notebook_view, workspace_view},
|
||||
notebooks::manager::NotebookSource,
|
||||
server::{
|
||||
@@ -83,7 +83,7 @@ pub fn open_notebook(window_key: impl Into<String>, notebook_key: impl Into<Stri
|
||||
WindowManager::as_ref(ctx).show_window_and_focus_app(*window_id);
|
||||
workspace.open_notebook(
|
||||
&NotebookSource::Existing(*notebook_id),
|
||||
&OpenWarpDriveObjectSettings::default(),
|
||||
&OpenGalaxyDriveObjectSettings::default(),
|
||||
ctx,
|
||||
true,
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ use galaxyui::{
|
||||
|
||||
use crate::{
|
||||
cloud_object::{model::persistence::CloudModel, CloudObjectEventEntrypoint, Space},
|
||||
drive::OpenWarpDriveObjectSettings,
|
||||
drive::OpenGalaxyDriveObjectSettings,
|
||||
integration_testing::view_getters::workspace_view,
|
||||
server::{
|
||||
cloud_objects::update_manager::UpdateManager,
|
||||
@@ -70,7 +70,7 @@ pub fn open_workflow(window_key: impl Into<String>, workflow_key: impl Into<Stri
|
||||
WindowManager::as_ref(ctx).show_window_and_focus_app(*window_id);
|
||||
workspace.open_workflow_in_pane(
|
||||
&WorkflowOpenSource::Existing(*workflow_id),
|
||||
&OpenWarpDriveObjectSettings::default(),
|
||||
&OpenGalaxyDriveObjectSettings::default(),
|
||||
WorkflowViewMode::View,
|
||||
ctx,
|
||||
);
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::{
|
||||
AppState, BranchSnapshot, LeafContents, LeafSnapshot, NotebookPaneSnapshot, PaneFlex,
|
||||
PaneNodeSnapshot, SplitDirection, TabSnapshot, TerminalPaneSnapshot, WindowSnapshot,
|
||||
},
|
||||
drive::OpenWarpDriveObjectSettings,
|
||||
drive::OpenGalaxyDriveObjectSettings,
|
||||
tab::SelectedTabColor,
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ fn test_config_from_snapshot_flattens_single_pane() {
|
||||
custom_vertical_tabs_title: None,
|
||||
contents: LeafContents::Notebook(NotebookPaneSnapshot::CloudNotebook {
|
||||
notebook_id: None,
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
@@ -151,7 +151,7 @@ fn test_config_from_snapshot_filters_panes() {
|
||||
custom_vertical_tabs_title: None,
|
||||
contents: LeafContents::Notebook(NotebookPaneSnapshot::CloudNotebook {
|
||||
notebook_id: None,
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
@@ -215,7 +215,7 @@ fn test_config_from_snapshot_filters_tabs() {
|
||||
custom_vertical_tabs_title: None,
|
||||
contents: LeafContents::Notebook(NotebookPaneSnapshot::CloudNotebook {
|
||||
notebook_id: None,
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
}),
|
||||
}),
|
||||
)],
|
||||
@@ -285,7 +285,7 @@ fn test_config_with_active_tab_index_and_filtered_tabs() {
|
||||
custom_vertical_tabs_title: None,
|
||||
contents: LeafContents::Notebook(NotebookPaneSnapshot::CloudNotebook {
|
||||
notebook_id: None,
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
}),
|
||||
}),
|
||||
)],
|
||||
@@ -376,7 +376,7 @@ fn test_config_with_active_tab_being_filtered() {
|
||||
custom_vertical_tabs_title: None,
|
||||
contents: LeafContents::Notebook(NotebookPaneSnapshot::CloudNotebook {
|
||||
notebook_id: None,
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
}),
|
||||
}),
|
||||
)],
|
||||
|
||||
@@ -8,13 +8,13 @@ use crate::send_telemetry_from_ctx;
|
||||
use crate::server::telemetry::TelemetryEvent;
|
||||
use crate::user_config::launch_configs_dir;
|
||||
#[cfg(feature = "local_fs")]
|
||||
use crate::user_config::{util::file_name_to_human_readable_name, WarpConfig};
|
||||
use crate::user_config::{util::file_name_to_human_readable_name, GalaxyConfig};
|
||||
use crate::util::bindings::keybinding_name_to_display_string;
|
||||
#[cfg(feature = "local_fs")]
|
||||
use crate::util::openable_file_type::FileTarget;
|
||||
use galaxy_core::paths::home_relative_path;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
use galaxyui::accessibility::{AccessibilityContent, WarpA11yRole};
|
||||
use galaxyui::accessibility::{AccessibilityContent, GalaxyA11yRole};
|
||||
use galaxyui::elements::{
|
||||
Align, Border, ChildAnchor, ChildView, Clipped, ConstrainedBox, Container, CornerRadius,
|
||||
Element, Empty, Flex, FormattedTextElement, MouseStateHandle, OffsetPositioning, ParentAnchor,
|
||||
@@ -153,7 +153,7 @@ pub enum LaunchConfigModalEvent {
|
||||
/// It's called when the new config was just saved. Note that when we save the configuration,
|
||||
/// it take a moment for the file system to register the change, and us to receive it (as there
|
||||
/// is a delay in our watcher). But because we actually have the LaunchConfig in our hands
|
||||
/// already, we may as well save it "manually" to the WarpConfig, while waiting for the update
|
||||
/// already, we may as well save it "manually" to the GalaxyConfig, while waiting for the update
|
||||
/// from the file system. This event passes a saved config to the handler to let us do that.
|
||||
SuccessfullySavedConfig(LaunchConfig),
|
||||
#[cfg(feature = "local_fs")]
|
||||
@@ -263,7 +263,7 @@ impl LaunchConfigSaveModal {
|
||||
let launch_config_name = file_name_to_human_readable_name(&file_name_candidate);
|
||||
if let Some(app_state) = &self.current_app_state {
|
||||
let launch_config = LaunchConfig::from_snapshot(launch_config_name, app_state);
|
||||
match WarpConfig::save_new_launch_config(file_name_candidate, launch_config.clone()) {
|
||||
match GalaxyConfig::save_new_launch_config(file_name_candidate, launch_config.clone()) {
|
||||
Ok(file_name) => {
|
||||
self.saved_successfully(file_name, ctx);
|
||||
ctx.emit(LaunchConfigModalEvent::SuccessfullySavedConfig(
|
||||
@@ -661,7 +661,7 @@ impl View for LaunchConfigSaveModal {
|
||||
"Type the name of the file to which you want to save your
|
||||
current configuration of windows, tabs, and panes. Use enter to save the
|
||||
launch configuration, esc to quit the save configuration modal.",
|
||||
WarpA11yRole::PopoverRole,
|
||||
GalaxyA11yRole::PopoverRole,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
+10
-10
@@ -93,7 +93,7 @@ mod view_components;
|
||||
mod vim_registers;
|
||||
mod voice;
|
||||
mod voltron;
|
||||
mod warp_managed_paths_watcher;
|
||||
mod galaxy_managed_paths_watcher;
|
||||
#[cfg(target_family = "wasm")]
|
||||
mod wasm_nux_dialog;
|
||||
mod window_settings;
|
||||
@@ -241,9 +241,9 @@ use crate::terminal::resizable_data::ResizableData;
|
||||
use crate::terminal::view::inline_banner::ByoLlmAuthBannerSessionState;
|
||||
use crate::terminal::{AudibleBell, History};
|
||||
use crate::undo_close::UndoCloseStack;
|
||||
use crate::user_config::WarpConfig;
|
||||
use crate::user_config::GalaxyConfig;
|
||||
use crate::vim_registers::VimRegisters;
|
||||
use crate::warp_managed_paths_watcher::{ensure_warp_watch_roots_exist, WarpManagedPathsWatcher};
|
||||
use crate::galaxy_managed_paths_watcher::{ensure_galaxy_watch_roots_exist, GalaxyManagedPathsWatcher};
|
||||
use crate::workflows::aliases::WorkflowAliases;
|
||||
use crate::workflows::local_workflows::LocalWorkflows;
|
||||
use crate::workspace::{ActiveSession, OneTimeModalModel, ToastStack};
|
||||
@@ -347,7 +347,7 @@ pub enum LaunchMode {
|
||||
/// Run the regular GUI application.
|
||||
App {
|
||||
args: galaxy_cli::AppArgs,
|
||||
/// API key for server authentication, if provided via `--api-key` or `WARP_API_KEY`.
|
||||
/// API key for server authentication, if provided via `--api-key` or `GALAXY_API_KEY`.
|
||||
/// Only used on dogfood channels.
|
||||
api_key: Option<String>,
|
||||
},
|
||||
@@ -996,14 +996,14 @@ fn initialize_app(
|
||||
|
||||
// One-time migration: give Preview its own config directory by
|
||||
// symlinking contents from the shared ~/.warp location. Must run
|
||||
// before ensure_warp_watch_roots_exist() creates the new directory.
|
||||
// before ensure_galaxy_watch_roots_exist() creates the new directory.
|
||||
#[cfg(target_os = "macos")]
|
||||
preview_config_migration::migrate_preview_config_dir_if_needed();
|
||||
|
||||
ensure_warp_watch_roots_exist();
|
||||
ctx.add_singleton_model(WarpManagedPathsWatcher::new);
|
||||
ensure_galaxy_watch_roots_exist();
|
||||
ctx.add_singleton_model(GalaxyManagedPathsWatcher::new);
|
||||
|
||||
ctx.add_singleton_model(WarpConfig::new);
|
||||
ctx.add_singleton_model(GalaxyConfig::new);
|
||||
ctx.add_singleton_model(|_ctx| SettingsManager::default());
|
||||
|
||||
let user_defaults_on_startup = settings::init(startup_toml_parse_error, ctx);
|
||||
@@ -2443,7 +2443,7 @@ pub fn enabled_features() -> HashSet<FeatureFlag> {
|
||||
#[cfg(all(not(windows), feature = "kitty_images"))]
|
||||
FeatureFlag::KittyImages,
|
||||
#[cfg(feature = "warp_packs")]
|
||||
FeatureFlag::WarpPacks,
|
||||
FeatureFlag::GalaxyPacks,
|
||||
#[cfg(feature = "global_ai_analytics_banner")]
|
||||
FeatureFlag::GlobalAIAnalyticsBanner,
|
||||
#[cfg(feature = "global_ai_analytics_collection")]
|
||||
@@ -2657,7 +2657,7 @@ pub fn enabled_features() -> HashSet<FeatureFlag> {
|
||||
#[cfg(feature = "agent_view_block_context")]
|
||||
FeatureFlag::AgentViewBlockContext,
|
||||
#[cfg(feature = "galaxy_managed_secrets")]
|
||||
FeatureFlag::WarpManagedSecrets,
|
||||
FeatureFlag::GalaxyManagedSecrets,
|
||||
#[cfg(feature = "v4a_file_diffs")]
|
||||
FeatureFlag::V4AFileDiffs,
|
||||
#[cfg(feature = "interactive_conversation_management_view")]
|
||||
|
||||
+6
-6
@@ -15,7 +15,7 @@ use galaxyui::elements::{
|
||||
};
|
||||
use galaxyui::WindowId;
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, GalaxyA11yRole},
|
||||
elements::{
|
||||
Align, Border, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment, Dismiss,
|
||||
DispatchEventResult, Element, EventHandler, Flex, Hoverable, Icon, MainAxisAlignment,
|
||||
@@ -2342,28 +2342,28 @@ impl<A: Action + Clone> SubMenu<A> {
|
||||
Custom(AccessibilityContent::new(
|
||||
menu_item,
|
||||
instructions,
|
||||
WarpA11yRole::TextRole,
|
||||
GalaxyA11yRole::TextRole,
|
||||
))
|
||||
}
|
||||
OpenSubmenu => Custom(AccessibilityContent::new(
|
||||
String::from("Submenu Expanded"),
|
||||
"Press the right key to open the selected submenu",
|
||||
WarpA11yRole::TextRole,
|
||||
GalaxyA11yRole::TextRole,
|
||||
)),
|
||||
CloseSubmenu(_) => Custom(AccessibilityContent::new(
|
||||
String::from("Submenu Closed"),
|
||||
"Removing focus from a submenu will close the submenu",
|
||||
WarpA11yRole::TextRole,
|
||||
GalaxyA11yRole::TextRole,
|
||||
)),
|
||||
Close(_) => Custom(AccessibilityContent::new(
|
||||
String::from("Menu Closed"),
|
||||
"Press the escape key to close the menu",
|
||||
WarpA11yRole::TextRole,
|
||||
GalaxyA11yRole::TextRole,
|
||||
)),
|
||||
Enter => Custom(AccessibilityContent::new(
|
||||
String::from("Action Selected"),
|
||||
"Press the enter key to execute the selected menu item action",
|
||||
WarpA11yRole::TextRole,
|
||||
GalaxyA11yRole::TextRole,
|
||||
)),
|
||||
HoverSubmenuLeafNode { .. }
|
||||
| UnhoverSubmenuParent(_)
|
||||
|
||||
@@ -6,7 +6,7 @@ use galaxy_editor::{
|
||||
search::{SearchEvent, Searcher},
|
||||
};
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, GalaxyA11yRole},
|
||||
elements::{
|
||||
Border, ChildAnchor, Clipped, ConstrainedBox, Container, CornerRadius, CrossAxisAlignment,
|
||||
Empty, Flex, MouseStateHandle, OffsetPositioning, ParentElement, PositionedElementAnchor,
|
||||
@@ -565,7 +565,7 @@ impl TypedActionView for FindBar {
|
||||
};
|
||||
Some(AccessibilityContent::new_without_help(
|
||||
text,
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
.into()
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use base64::{prelude::BASE64_STANDARD, Engine as _};
|
||||
use std::{any::Any, borrow::Cow, collections::HashMap, ops::Range, time::Duration};
|
||||
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, GalaxyA11yRole},
|
||||
clipboard::ClipboardContent,
|
||||
AppContext, Entity, ModelAsRef, ModelContext, ModelHandle, SingletonEntity, WindowId,
|
||||
};
|
||||
@@ -1289,7 +1289,7 @@ impl NotebooksEditorModel {
|
||||
if let Some(command) = child_model.executable_command(ctx) {
|
||||
ctx.emit_a11y_content(AccessibilityContent::new_without_help(
|
||||
format!("Selected workflow: {command}"),
|
||||
WarpA11yRole::TextareaRole,
|
||||
GalaxyA11yRole::TextareaRole,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1650,7 +1650,7 @@ impl NotebooksEditorModel {
|
||||
let text = format!("{style:?} {action}");
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
text,
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use galaxy_editor::{
|
||||
render::model::RenderState,
|
||||
};
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, GalaxyA11yRole},
|
||||
elements::{
|
||||
AnchorPair, Border, ConstrainedBox, Container, CornerRadius, DropShadow, Flex,
|
||||
MainAxisSize, MouseStateHandle, OffsetPositioning, OffsetType, ParentElement, Point,
|
||||
@@ -447,12 +447,12 @@ impl TypedActionView for Omnibar {
|
||||
OmnibarAction::ConvertBlock(style) => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
format!("Convert to {}", BlockType::from(style).label()),
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
OmnibarAction::OpenLinkEditor => ActionAccessibilityContent::from_debug(),
|
||||
OmnibarAction::UnstyleLink => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Remove link", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Remove link", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ use string_offset::CharOffset;
|
||||
|
||||
use galaxy_util::{path::LineAndColumnArg, user_input::UserInput};
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, ActionAccessibilityContent, GalaxyA11yRole},
|
||||
assets::asset_cache::{AssetCache, AssetHandle, AssetState},
|
||||
clipboard::ClipboardContent,
|
||||
elements::{
|
||||
@@ -2440,7 +2440,7 @@ impl RichTextEditorView {
|
||||
if show_open_in_warp {
|
||||
let path_for_warp = path.clone();
|
||||
links.push(TooltipLink {
|
||||
text: "Open in Warp".to_string(),
|
||||
text: "Open in Galaxy".to_string(),
|
||||
on_click: Box::new(move |ctx: &mut EventContext| {
|
||||
ctx.dispatch_typed_action(EditorViewAction::OpenFile {
|
||||
path: path_for_warp.clone(),
|
||||
@@ -3015,13 +3015,13 @@ impl TypedActionView for RichTextEditorView {
|
||||
EditorViewAction::UserTyped(text) => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
text.clone().into_inner(),
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::Paste | EditorViewAction::MiddleClickPaste => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
format!("Pasting: {}", ctx.clipboard().read().plain_text),
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::Enter
|
||||
@@ -3034,21 +3034,21 @@ impl TypedActionView for RichTextEditorView {
|
||||
| EditorViewAction::Unindent
|
||||
| EditorViewAction::Tab => ActionAccessibilityContent::from_debug(),
|
||||
EditorViewAction::ShiftTab => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Shift-tab", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Shift-tab", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
EditorViewAction::EditLink | EditorViewAction::CreateOrEditLink => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Edit Link",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::CopyLink => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Copy Link", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Copy Link", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
EditorViewAction::OpenTooltipLink(link) => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
format!("Open link: {}", **link),
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::SecondaryLinkAction(link) => {
|
||||
@@ -3058,72 +3058,72 @@ impl TypedActionView for RichTextEditorView {
|
||||
);
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
content,
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::DeleteLineLeft => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Delete line left",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::DeleteLineRight => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Delete line right",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::DeleteWordLeft => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Delete word left",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::DeleteWordRight => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Delete word right",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
|
||||
EditorViewAction::CutLineLeft => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Cut line left", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Cut line left", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
EditorViewAction::CutLineRight => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Cut line right", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Cut line right", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
EditorViewAction::CutWordLeft => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Cut word left", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Cut word left", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
EditorViewAction::CutWordRight => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Cut word right", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Cut word right", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
|
||||
EditorViewAction::ShowCharacterPalette => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Show character palette",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::ShowFindBar => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Show find bar", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Show find bar", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
EditorViewAction::OpenBlockInsertionMenu => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Open block-insertion menu",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::OpenEmbeddedObjectSearch => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Open embedded object search menu",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::InsertBlock(block_type) => {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
format!("Insert {} block", BlockType::from(block_type).label()),
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::Bold => self
|
||||
@@ -3150,23 +3150,23 @@ impl TypedActionView for RichTextEditorView {
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new(
|
||||
"De-select command",
|
||||
"Switch from selecting commands to selecting text",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::CodeBlockTypeSelectedAtOffset {
|
||||
code_block_type, ..
|
||||
} => ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
format!("Change code block language to {code_block_type}"),
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
)),
|
||||
EditorViewAction::CopyTextToClipboard { .. } => ActionAccessibilityContent::Custom(
|
||||
AccessibilityContent::new_without_help("Copy code block", WarpA11yRole::UserAction),
|
||||
AccessibilityContent::new_without_help("Copy code block", GalaxyA11yRole::UserAction),
|
||||
),
|
||||
EditorViewAction::ToggleTaskList(_) => {
|
||||
// TODO(ben): Is it useful to include the text and/or on/off state here?
|
||||
ActionAccessibilityContent::Custom(AccessibilityContent::new_without_help(
|
||||
"Toggle task list",
|
||||
WarpA11yRole::UserAction,
|
||||
GalaxyA11yRole::UserAction,
|
||||
))
|
||||
}
|
||||
EditorViewAction::Delete
|
||||
|
||||
@@ -8,7 +8,7 @@ use galaxy_util::path::user_friendly_path;
|
||||
#[cfg(feature = "local_fs")]
|
||||
use galaxyui::clipboard::ClipboardContent;
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
elements::{
|
||||
Align, Container, CrossAxisAlignment, DispatchEventResult, Empty, EventHandler, Flex,
|
||||
MainAxisAlignment, MainAxisSize, MouseStateHandle, ParentElement, SavePosition, Shrinkable,
|
||||
@@ -810,7 +810,7 @@ impl View for FileNotebookView {
|
||||
fn accessibility_contents(&self, _ctx: &AppContext) -> Option<AccessibilityContent> {
|
||||
Some(AccessibilityContent::new_without_help(
|
||||
format!("{} notebook", self.title()),
|
||||
WarpA11yRole::TextRole,
|
||||
GalaxyA11yRole::TextRole,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ use crate::util::file::external_editor::EditorSettings;
|
||||
#[cfg(feature = "local_fs")]
|
||||
use crate::util::openable_file_type::{is_supported_image_file, resolve_file_target, FileTarget};
|
||||
use crate::{
|
||||
drive::OpenWarpDriveObjectArgs,
|
||||
drive::OpenGalaxyDriveObjectArgs,
|
||||
terminal::model::session::Session,
|
||||
uri::parse_url_paths::{get_item_data_from_warp_link, WarpWebLink},
|
||||
workspace::ActiveSession,
|
||||
@@ -266,7 +266,7 @@ impl NotebookLinks {
|
||||
match link {
|
||||
LinkTarget::Url(url) => {
|
||||
if let Some(WarpWebLink::DriveObject(args)) = get_item_data_from_warp_link(&url) {
|
||||
return ctx.emit(LinkEvent::OpenWarpDriveLink {
|
||||
return ctx.emit(LinkEvent::OpenGalaxyDriveLink {
|
||||
open_warp_drive_args: *args,
|
||||
});
|
||||
}
|
||||
@@ -409,8 +409,8 @@ pub enum LinkEvent {
|
||||
path: PathBuf,
|
||||
session: Arc<Session>,
|
||||
},
|
||||
OpenWarpDriveLink {
|
||||
open_warp_drive_args: OpenWarpDriveObjectArgs,
|
||||
OpenGalaxyDriveLink {
|
||||
open_warp_drive_args: OpenGalaxyDriveObjectArgs,
|
||||
},
|
||||
/// This event tells the parent pane group to open a new terminal session in the given
|
||||
/// directory.
|
||||
|
||||
@@ -13,7 +13,7 @@ use crate::{
|
||||
model::persistence::{CloudModel, CloudModelEvent},
|
||||
Owner,
|
||||
},
|
||||
drive::OpenWarpDriveObjectSettings,
|
||||
drive::OpenGalaxyDriveObjectSettings,
|
||||
pane_group::{NotebookPane, PaneContent},
|
||||
safe_debug, safe_warn,
|
||||
server::{
|
||||
@@ -182,7 +182,7 @@ impl NotebookManager {
|
||||
pub fn create_pane(
|
||||
&mut self,
|
||||
source: &NotebookSource,
|
||||
settings: &OpenWarpDriveObjectSettings,
|
||||
settings: &OpenGalaxyDriveObjectSettings,
|
||||
window_id: WindowId,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) -> NotebookPane {
|
||||
|
||||
@@ -16,7 +16,7 @@ use galaxy_editor::{
|
||||
model::{CoreEditorModel, RichTextEditorModel},
|
||||
};
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
clipboard::ClipboardContent,
|
||||
elements::{
|
||||
Align, Clipped, ConstrainedBox, Container, CrossAxisAlignment, DispatchEventResult, Empty,
|
||||
@@ -52,7 +52,7 @@ use crate::{
|
||||
drive::{
|
||||
drive_helpers::has_feature_gated_anonymous_user_reached_notebook_limit,
|
||||
export::ExportManager, items::WarpDriveItemId, sharing::ShareableObject,
|
||||
CloudObjectTypeAndId, OpenWarpDriveObjectSettings,
|
||||
CloudObjectTypeAndId, OpenGalaxyDriveObjectSettings,
|
||||
},
|
||||
editor::{
|
||||
EditOrigin, EditorView, Event as EditorEvent, InteractionState,
|
||||
@@ -1522,7 +1522,7 @@ impl NotebookView {
|
||||
pub fn wait_for_initial_load_then_load(
|
||||
&mut self,
|
||||
notebook_id: SyncId,
|
||||
settings: &OpenWarpDriveObjectSettings,
|
||||
settings: &OpenGalaxyDriveObjectSettings,
|
||||
window_id: WindowId,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
@@ -1561,7 +1561,7 @@ impl NotebookView {
|
||||
fn fetch_and_load_notebook(
|
||||
&mut self,
|
||||
notebook_id: ServerId,
|
||||
settings: &OpenWarpDriveObjectSettings,
|
||||
settings: &OpenGalaxyDriveObjectSettings,
|
||||
window_id: WindowId,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) {
|
||||
@@ -1605,7 +1605,7 @@ impl NotebookView {
|
||||
pub fn load(
|
||||
&mut self,
|
||||
notebook: CloudNotebook,
|
||||
settings: &OpenWarpDriveObjectSettings,
|
||||
settings: &OpenGalaxyDriveObjectSettings,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) -> SpawnedFutureHandle {
|
||||
self.set_title(¬ebook.model().title, ctx);
|
||||
@@ -1868,7 +1868,7 @@ impl NotebookView {
|
||||
if let Some(notebook) = CloudModel::as_ref(ctx).get_notebook(&id) {
|
||||
self.load(
|
||||
notebook.clone(),
|
||||
&OpenWarpDriveObjectSettings::default(),
|
||||
&OpenGalaxyDriveObjectSettings::default(),
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
@@ -2185,7 +2185,7 @@ impl View for NotebookView {
|
||||
fn accessibility_contents(&self, ctx: &AppContext) -> Option<AccessibilityContent> {
|
||||
Some(AccessibilityContent::new_without_help(
|
||||
format!("{} notebook", self.title(ctx)),
|
||||
WarpA11yRole::TextRole,
|
||||
GalaxyA11yRole::TextRole,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ use crate::{
|
||||
},
|
||||
Owner, Revision, ServerCloudObject, ServerMetadata, ServerNotebook, ServerPermissions,
|
||||
},
|
||||
drive::OpenWarpDriveObjectSettings,
|
||||
drive::OpenGalaxyDriveObjectSettings,
|
||||
editor::{DisplayPoint, EditorAction, InteractionState, SelectAction},
|
||||
network::NetworkStatus,
|
||||
notebooks::{
|
||||
@@ -140,7 +140,7 @@ fn open_notebook(
|
||||
notebook: CloudNotebook,
|
||||
) -> BoxFuture<'static, ()> {
|
||||
let load_future = handle.update(app, |view, ctx| {
|
||||
view.load(notebook, &OpenWarpDriveObjectSettings::default(), ctx)
|
||||
view.load(notebook, &OpenGalaxyDriveObjectSettings::default(), ctx)
|
||||
});
|
||||
app.update(|ctx| ctx.await_spawned_future(load_future.future_id()))
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ use crate::banner::{Banner, BannerEvent, BannerState, BannerTextContent, Dismiss
|
||||
use crate::channel::{Channel, ChannelState};
|
||||
use crate::code::view::CodeView;
|
||||
use crate::drive::items::WarpDriveItemId;
|
||||
use crate::drive::{CloudObjectTypeAndId, OpenWarpDriveObjectArgs};
|
||||
use crate::drive::{CloudObjectTypeAndId, OpenGalaxyDriveObjectArgs};
|
||||
use crate::features::FeatureFlag;
|
||||
use crate::launch_configs::launch_config::{self, PaneMode, PaneTemplateType};
|
||||
use crate::persistence::ModelEvent;
|
||||
@@ -522,8 +522,8 @@ pub enum Event {
|
||||
/// The session that the path was opened from.
|
||||
session: Arc<Session>,
|
||||
},
|
||||
OpenWarpDriveLink {
|
||||
open_warp_drive_args: OpenWarpDriveObjectArgs,
|
||||
OpenGalaxyDriveLink {
|
||||
open_warp_drive_args: OpenGalaxyDriveObjectArgs,
|
||||
},
|
||||
#[cfg(feature = "local_fs")]
|
||||
OpenCodeInWarp {
|
||||
@@ -598,7 +598,7 @@ pub enum Event {
|
||||
},
|
||||
/// Clears the hovered tab index so it no longer appears as highlighted drop target
|
||||
ClearHoveredTabIndex,
|
||||
OpenWarpDriveObjectInPane(ObjectUid),
|
||||
OpenGalaxyDriveObjectInPane(ObjectUid),
|
||||
OpenSuggestedAgentModeWorkflowModal {
|
||||
workflow_and_id: SuggestedAgentModeWorkflowAndId,
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@ use crate::{
|
||||
},
|
||||
test_util::settings::initialize_settings_for_tests,
|
||||
undo_close::UndoCloseStack,
|
||||
warp_managed_paths_watcher::WarpManagedPathsWatcher,
|
||||
galaxy_managed_paths_watcher::GalaxyManagedPathsWatcher,
|
||||
workflows::local_workflows::LocalWorkflows,
|
||||
workspace::{
|
||||
sync_inputs::SyncedInputState, ActiveSession, OneTimeModalModel, WorkspaceRegistry,
|
||||
@@ -102,7 +102,7 @@ fn initialize_app(app: &mut App) {
|
||||
app.add_singleton_model(|_| DetectedRepositories::default());
|
||||
app.add_singleton_model(HomeDirectoryWatcher::new_for_test);
|
||||
app.add_singleton_model(DirectoryWatcher::new);
|
||||
app.add_singleton_model(WarpManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(GalaxyManagedPathsWatcher::new_for_testing);
|
||||
app.add_singleton_model(FileMCPWatcher::new);
|
||||
app.add_singleton_model(|_| FileBasedMCPManager::default());
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ use galaxyui::{AppContext, ModelHandle, SingletonEntity, ViewContext, ViewHandle
|
||||
use crate::{
|
||||
app_state::{LeafContents, NotebookPaneSnapshot},
|
||||
cloud_object::Space,
|
||||
drive::{items::WarpDriveItemId, CloudObjectTypeAndId, OpenWarpDriveObjectSettings},
|
||||
drive::{items::WarpDriveItemId, CloudObjectTypeAndId, OpenGalaxyDriveObjectSettings},
|
||||
notebooks::{
|
||||
link::{LinkEvent, NotebookLinks},
|
||||
manager::{NotebookManager, NotebookSource},
|
||||
@@ -47,7 +47,7 @@ impl NotebookPane {
|
||||
/// Restore a notebook pane given its cloud notebook ID.
|
||||
pub fn restore(
|
||||
notebook_id: Option<SyncId>,
|
||||
settings: &OpenWarpDriveObjectSettings,
|
||||
settings: &OpenGalaxyDriveObjectSettings,
|
||||
ctx: &mut ViewContext<PaneGroup>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let window_id = ctx.window_id();
|
||||
@@ -81,7 +81,7 @@ impl PaneContent for NotebookPane {
|
||||
let notebook_id = self.notebook_view(app).as_ref(app).notebook_id(app);
|
||||
LeafContents::Notebook(NotebookPaneSnapshot::CloudNotebook {
|
||||
notebook_id,
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -183,9 +183,9 @@ pub(super) fn subscribe_to_link_model(
|
||||
session: session.clone(),
|
||||
})
|
||||
}
|
||||
LinkEvent::OpenWarpDriveLink {
|
||||
LinkEvent::OpenGalaxyDriveLink {
|
||||
open_warp_drive_args,
|
||||
} => ctx.emit(crate::pane_group::Event::OpenWarpDriveLink {
|
||||
} => ctx.emit(crate::pane_group::Event::OpenGalaxyDriveLink {
|
||||
open_warp_drive_args: open_warp_drive_args.clone(),
|
||||
}),
|
||||
LinkEvent::StartLocalSession { path } => {
|
||||
|
||||
@@ -902,8 +902,8 @@ fn handle_terminal_view_event(
|
||||
Event::RoleRequestCancelled(role_request_id) => {
|
||||
group.remove_shared_session_role_request(role_request_id.clone(), ctx);
|
||||
}
|
||||
Event::OpenWarpDriveObjectInPane(uid) => {
|
||||
ctx.emit(pane_group::Event::OpenWarpDriveObjectInPane(uid.clone()));
|
||||
Event::OpenGalaxyDriveObjectInPane(uid) => {
|
||||
ctx.emit(pane_group::Event::OpenGalaxyDriveObjectInPane(uid.clone()));
|
||||
}
|
||||
Event::OpenSuggestedAgentModeWorkflowModal { workflow_and_id } => {
|
||||
ctx.emit(pane_group::Event::OpenSuggestedAgentModeWorkflowModal {
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::{
|
||||
};
|
||||
use crate::{
|
||||
app_state::{LeafContents, WorkflowPaneSnapshot},
|
||||
drive::{items::WarpDriveItemId, OpenWarpDriveObjectSettings},
|
||||
drive::{items::WarpDriveItemId, OpenGalaxyDriveObjectSettings},
|
||||
server::ids::SyncId,
|
||||
workflows::{
|
||||
manager::{WorkflowManager, WorkflowOpenSource},
|
||||
@@ -39,7 +39,7 @@ impl WorkflowPane {
|
||||
|
||||
pub fn restore(
|
||||
workflow_id: Option<SyncId>,
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
settings: OpenGalaxyDriveObjectSettings,
|
||||
ctx: &mut ViewContext<PaneGroup>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let window_id = ctx.window_id();
|
||||
@@ -132,7 +132,7 @@ impl PaneContent for WorkflowPane {
|
||||
let workflow_id = self.get_view(app).as_ref(app).workflow_id();
|
||||
LeafContents::Workflow(WorkflowPaneSnapshot::CloudWorkflow {
|
||||
workflow_id: Some(workflow_id),
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::{fmt, iter, mem};
|
||||
|
||||
use super::{ActivationReason, PaneGroup, PaneId};
|
||||
use crate::pane_group::{get_minimum_pane_size, DraggedBorder, PaneGroupAction};
|
||||
use crate::themes::theme::WarpTheme;
|
||||
use crate::themes::theme::GalaxyTheme;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -492,7 +492,7 @@ impl PaneData {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
pub fn render(&self, theme: &WarpTheme, app: &AppContext) -> Box<dyn Element> {
|
||||
pub fn render(&self, theme: &GalaxyTheme, app: &AppContext) -> Box<dyn Element> {
|
||||
match &self.root {
|
||||
PaneNode::Leaf(pane) => pane.render(app),
|
||||
PaneNode::Branch(node) => node.render(theme, &self.hidden_panes, app),
|
||||
@@ -694,7 +694,7 @@ impl PaneNode {
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
hidden_panes: &Vec<HiddenPane>,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
@@ -1023,7 +1023,7 @@ impl PaneBranch {
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
hidden_panes: &Vec<HiddenPane>,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
@@ -1355,7 +1355,7 @@ fn create_divider_placeholder(direction: SplitDirection, position_id: &str) -> B
|
||||
fn create_divider(
|
||||
direction: SplitDirection,
|
||||
item: &Divider,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
) -> Box<dyn Element> {
|
||||
let divider = ConstrainedBox::new(
|
||||
Rect::new()
|
||||
@@ -1393,7 +1393,7 @@ fn create_divider(
|
||||
fn create_minimalist_divider(
|
||||
direction: SplitDirection,
|
||||
item: &Divider,
|
||||
theme: &WarpTheme,
|
||||
theme: &GalaxyTheme,
|
||||
) -> Box<dyn Element> {
|
||||
let divider = ConstrainedBox::new(
|
||||
Rect::new()
|
||||
|
||||
@@ -86,7 +86,7 @@ use crate::cloud_object::{
|
||||
};
|
||||
use crate::code::editor_management::CodeSource;
|
||||
use crate::drive::folders::{CloudFolder, CloudFolderModel, FolderId};
|
||||
use crate::drive::OpenWarpDriveObjectSettings;
|
||||
use crate::drive::OpenGalaxyDriveObjectSettings;
|
||||
use crate::env_vars::{CloudEnvVarCollection, CloudEnvVarCollectionModel};
|
||||
use crate::features::FeatureFlag;
|
||||
use crate::notebooks::{CloudNotebook, NotebookId};
|
||||
@@ -2519,7 +2519,7 @@ fn read_node(conn: &mut SqliteConnection, node: model::PaneNode) -> Result<PaneN
|
||||
Some(path) => NotebookPaneSnapshot::LocalFileNotebook { path: Some(path) },
|
||||
None => NotebookPaneSnapshot::CloudNotebook {
|
||||
notebook_id,
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -2537,7 +2537,7 @@ fn read_node(conn: &mut SqliteConnection, node: model::PaneNode) -> Result<PaneN
|
||||
|
||||
LeafContents::Workflow(WorkflowPaneSnapshot::CloudWorkflow {
|
||||
workflow_id,
|
||||
settings: OpenWarpDriveObjectSettings::default(),
|
||||
settings: OpenGalaxyDriveObjectSettings::default(),
|
||||
})
|
||||
}
|
||||
CODE_PANE_KIND => {
|
||||
|
||||
@@ -2,7 +2,7 @@ use js_sys::ReferenceError;
|
||||
use thiserror::Error;
|
||||
use wasm_bindgen::{JsCast, JsValue};
|
||||
|
||||
pub use galaxy_web_event_bus::{emit_event, WarpEvent};
|
||||
pub use galaxy_web_event_bus::{emit_event, GalaxyEvent};
|
||||
|
||||
/// This function should be called early in application initialization to ensure that
|
||||
/// static variables are initialized.
|
||||
|
||||
@@ -56,7 +56,7 @@ pub(crate) fn migrate_config_dir_via_symlinks(old_dir: &Path, new_dir: &Path) {
|
||||
|
||||
// The existence of new_dir is the migration marker — no separate marker
|
||||
// file is needed. Once this directory exists (whether created by the
|
||||
// migration itself or by ensure_warp_watch_roots_exist on a subsequent
|
||||
// migration itself or by ensure_galaxy_watch_roots_exist on a subsequent
|
||||
// launch), this function is a no-op.
|
||||
if new_dir.exists() || !old_dir.exists() {
|
||||
return;
|
||||
|
||||
@@ -92,7 +92,7 @@ pub enum TipAction {
|
||||
WarpAI,
|
||||
// This toggles Warp Drive rather than opening it. This enum can't directly be
|
||||
// renamed because we serialize it into the welcome tips.
|
||||
OpenWarpDrive,
|
||||
OpenGalaxyDrive,
|
||||
Changelog,
|
||||
// Note that this item has been deprecated from the UI and is not in any section.
|
||||
// We are leaving it in this enum to ensure that we don't re-use `Workflows` as a
|
||||
@@ -112,7 +112,7 @@ impl TipAction {
|
||||
TipAction::ThemePicker => "workspace:show_theme_chooser",
|
||||
TipAction::SaveNewLaunchConfig => "workspace:open_launch_config_save_modal",
|
||||
TipAction::WarpAI => "workspace:toggle_ai_assistant",
|
||||
TipAction::OpenWarpDrive => "workspace:toggle_left_panel",
|
||||
TipAction::OpenGalaxyDrive => "workspace:toggle_left_panel",
|
||||
// Slash commands are also registered as editable bindings, so callers can look them up here
|
||||
// the same way they do regular app actions.
|
||||
TipAction::Changelog => "/changelog",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use galaxy_core::ui::builder::UiBuilder;
|
||||
use galaxyui::{
|
||||
accessibility::{AccessibilityContent, WarpA11yRole},
|
||||
accessibility::{AccessibilityContent, GalaxyA11yRole},
|
||||
elements::{Align, Container, Element, Flex, MouseStateHandle, ParentElement},
|
||||
keymap::FixedBinding,
|
||||
ui_components::button::ButtonVariant,
|
||||
@@ -186,7 +186,7 @@ impl View for RewardView {
|
||||
Some(AccessibilityContent::new(
|
||||
format!("{} {}", TITLE, self.subtitle()),
|
||||
ACCESSIBILITY_HELP,
|
||||
WarpA11yRole::WindowRole,
|
||||
GalaxyA11yRole::WindowRole,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -14,7 +14,7 @@ use crate::cloud_object::model::persistence::CloudModel;
|
||||
use crate::cloud_object::{GenericStringObjectFormat, JsonObjectType, ObjectType};
|
||||
use crate::drive::export::ExportManager;
|
||||
use crate::drive::items::WarpDriveItemId;
|
||||
use crate::drive::{CloudObjectTypeAndId, OpenWarpDriveObjectArgs, OpenWarpDriveObjectSettings};
|
||||
use crate::drive::{CloudObjectTypeAndId, OpenGalaxyDriveObjectArgs, OpenGalaxyDriveObjectSettings};
|
||||
use crate::experiments::{BlockOnboarding, Experiment};
|
||||
use crate::interval_timer::IntervalTimer;
|
||||
use crate::launch_configs::launch_config;
|
||||
@@ -49,7 +49,7 @@ use crate::terminal::keys_settings::KeysSettings;
|
||||
use crate::terminal::shell::ShellType;
|
||||
use crate::terminal::view::{cell_size_and_padding, TerminalAction};
|
||||
use crate::themes::onboarding_theme_picker_themes;
|
||||
use crate::themes::theme::{AnsiColorIdentifier, Blend, Fill, ThemeKind, WarpThemeConfig};
|
||||
use crate::themes::theme::{AnsiColorIdentifier, Blend, Fill, ThemeKind, GalaxyThemeConfig};
|
||||
use crate::uri::OpenMCPSettingsArgs;
|
||||
use crate::util::bindings::{self, is_binding_pty_compliant};
|
||||
use crate::util::traffic_lights::{traffic_light_data, TrafficLightData, TrafficLightMouseStates};
|
||||
@@ -1133,7 +1133,7 @@ fn open_linear_issue_work_in_new_window(args: &LinearIssueWork, ctx: &mut AppCon
|
||||
});
|
||||
}
|
||||
|
||||
fn open_warp_drive_object(arg: &OpenWarpDriveObjectArgs, ctx: &mut AppContext) {
|
||||
fn open_warp_drive_object(arg: &OpenGalaxyDriveObjectArgs, ctx: &mut AppContext) {
|
||||
match arg.object_type {
|
||||
ObjectType::Notebook => open_new_workspace_with_notebook_open(
|
||||
SyncId::ServerId(arg.server_id),
|
||||
@@ -1158,7 +1158,7 @@ fn display_object_missing_error_in_window(window_id: WindowId, ctx: &mut AppCont
|
||||
|
||||
fn open_new_workspace_with_notebook_open(
|
||||
notebook_id: SyncId,
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
settings: OpenGalaxyDriveObjectSettings,
|
||||
ctx: &mut AppContext,
|
||||
) {
|
||||
open_new_with_workspace_source(
|
||||
@@ -1172,7 +1172,7 @@ fn open_new_workspace_with_notebook_open(
|
||||
|
||||
fn open_new_workspace_with_workflow_open(
|
||||
workflow_id: SyncId,
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
settings: OpenGalaxyDriveObjectSettings,
|
||||
ctx: &mut AppContext,
|
||||
) {
|
||||
open_new_with_workspace_source(
|
||||
@@ -1583,11 +1583,11 @@ pub enum NewWorkspaceSource {
|
||||
},
|
||||
NotebookById {
|
||||
id: SyncId,
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
settings: OpenGalaxyDriveObjectSettings,
|
||||
},
|
||||
WorkflowById {
|
||||
id: SyncId,
|
||||
settings: OpenWarpDriveObjectSettings,
|
||||
settings: OpenGalaxyDriveObjectSettings,
|
||||
},
|
||||
AgentSession {
|
||||
options: Box<NewTerminalOptions>,
|
||||
@@ -2153,7 +2153,7 @@ impl RootView {
|
||||
}
|
||||
|
||||
fn onboarding_theme_kind(theme_name: &str) -> Option<ThemeKind> {
|
||||
WarpThemeConfig::new()
|
||||
GalaxyThemeConfig::new()
|
||||
.theme_items()
|
||||
.find_map(|(kind, theme)| {
|
||||
(theme.name().as_deref() == Some(theme_name)).then(|| kind.clone())
|
||||
@@ -2593,7 +2593,7 @@ impl RootView {
|
||||
|
||||
pub fn open_warp_drive_object_in_existing_window(
|
||||
&mut self,
|
||||
arg: &OpenWarpDriveObjectArgs,
|
||||
arg: &OpenGalaxyDriveObjectArgs,
|
||||
ctx: &mut ViewContext<Self>,
|
||||
) -> bool {
|
||||
if let AuthOnboardingState::Terminal(handle) = &self.auth_onboarding_state {
|
||||
@@ -2856,7 +2856,7 @@ impl RootView {
|
||||
ctx.dispatch_typed_action_for_view(
|
||||
window_id,
|
||||
handle.id(),
|
||||
&WorkspaceAction::OpenWarpDrive,
|
||||
&WorkspaceAction::OpenGalaxyDrive,
|
||||
);
|
||||
ctx.windows().show_window_and_focus_app(window_id);
|
||||
} else {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user