2793 lines
111 KiB
Rust
2793 lines
111 KiB
Rust
// Suppress warnings about rustdoc style.
|
|
#![allow(clippy::doc_lazy_continuation)]
|
|
|
|
mod ai;
|
|
mod alloc;
|
|
mod antivirus;
|
|
#[cfg(target_os = "macos")]
|
|
mod app_menus;
|
|
mod app_services;
|
|
mod app_state;
|
|
mod auth;
|
|
mod autoupdate;
|
|
mod banner;
|
|
#[cfg(feature = "bedrock_smoke_test")]
|
|
mod bedrock_smoke_test;
|
|
mod billing;
|
|
mod changelog_model;
|
|
mod chip_configurator;
|
|
mod cloud_object;
|
|
mod code;
|
|
mod code_review;
|
|
mod coding_entrypoints;
|
|
mod coding_panel_enablement_state;
|
|
mod command_palette;
|
|
mod completer;
|
|
#[allow(dead_code)]
|
|
mod context_chips;
|
|
#[cfg(enable_crash_recovery)]
|
|
mod crash_recovery;
|
|
#[cfg(feature = "crash_reporting")]
|
|
mod crash_reporting;
|
|
mod debounce;
|
|
mod debug_dump;
|
|
mod default_terminal;
|
|
mod download_method;
|
|
mod drive;
|
|
#[cfg(windows)]
|
|
mod dynamic_libraries;
|
|
mod env_vars;
|
|
mod experiments;
|
|
mod external_secrets;
|
|
#[cfg(target_family = "wasm")]
|
|
mod font_fallback;
|
|
mod global_resource_handles;
|
|
mod gpu_state;
|
|
mod input_classifier;
|
|
mod interval_timer;
|
|
mod linear;
|
|
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
|
mod login_item;
|
|
mod menu;
|
|
mod modal;
|
|
mod network;
|
|
mod notebooks;
|
|
mod notification;
|
|
mod palette;
|
|
mod persistence;
|
|
mod platform;
|
|
#[cfg(feature = "plugin_host")]
|
|
mod plugin;
|
|
mod prefix;
|
|
#[cfg(target_os = "macos")]
|
|
mod preview_config_migration;
|
|
mod pricing;
|
|
mod profiling;
|
|
mod projects;
|
|
mod prompt;
|
|
mod quit_warning;
|
|
mod referral_theme_status;
|
|
#[allow(dead_code)]
|
|
mod remote_server;
|
|
mod resource_limits;
|
|
mod reward_view;
|
|
mod safe_triangle;
|
|
mod search_bar;
|
|
mod server;
|
|
mod session_management;
|
|
mod shell_indicator;
|
|
mod suggestions;
|
|
mod system;
|
|
mod tab;
|
|
#[cfg(test)]
|
|
mod test_util;
|
|
mod throttle;
|
|
mod tips;
|
|
mod tracing;
|
|
mod ui_components;
|
|
mod undo_close;
|
|
mod uri;
|
|
mod user_config;
|
|
pub mod util;
|
|
mod view_components;
|
|
mod vim_registers;
|
|
mod voice;
|
|
mod voltron;
|
|
mod warp_managed_paths_watcher;
|
|
#[cfg(target_family = "wasm")]
|
|
mod wasm_nux_dialog;
|
|
mod window_settings;
|
|
mod word_block_editor;
|
|
mod workspaces;
|
|
|
|
// PLEASE DO NOT ADD MORE PUBLIC MODULES!
|
|
//
|
|
// Any modules which we make public outside of the `warp` crate lose dead code
|
|
// checking support, as the compiler cannot make any assumptions about whether
|
|
// or not the function/type is used by another crate that pulls in this one as
|
|
// a dependency.
|
|
//
|
|
// If you feel the need to export a module so that a type or function within it
|
|
// can be used by an integration test, you should define a new assertion function
|
|
// in the galaxy::integration_testing::assertions module (or a sub-module). These
|
|
// functions will allow us to keep types internal to this crate and expose a
|
|
// simpler API for integration tests to consume.
|
|
pub mod ai_assistant;
|
|
pub mod appearance;
|
|
pub mod channel;
|
|
pub mod editor;
|
|
pub mod features;
|
|
pub mod input_suggestions;
|
|
#[cfg(feature = "integration_tests")]
|
|
pub mod integration_testing;
|
|
pub mod keyboard;
|
|
pub mod launch_configs;
|
|
pub mod pane_group;
|
|
pub mod resource_center;
|
|
pub mod root_view;
|
|
pub mod search;
|
|
pub mod settings;
|
|
pub mod settings_view;
|
|
pub mod tab_configs;
|
|
pub mod terminal;
|
|
pub mod themes;
|
|
use crate::ai::active_agent_views_model::ActiveAgentViewsModel;
|
|
#[cfg(not(target_family = "wasm"))]
|
|
use crate::ai::aws_credentials::AwsCredentialRefresher as _;
|
|
use crate::ai::mcp::FileBasedMCPManager;
|
|
use crate::ai::mcp::FileMCPWatcher;
|
|
use crate::uri::web_intent_parser::maybe_rewrite_web_url_to_intent;
|
|
use ::ai::index::full_source_code_embedding::manager::CodebaseIndexManager;
|
|
use ::ai::index::full_source_code_embedding::SyncTask;
|
|
use ::ai::index::DEFAULT_SYNC_REQUESTS_PER_MIN;
|
|
use ::ai::project_context::model::ProjectContextModel;
|
|
pub use ai::agent::{todos::AIAgentTodoList, AIAgentActionResultType, FileEdit, TodoOperation};
|
|
use ai::agent_conversations_model::AgentConversationsModel;
|
|
use ai::agent_management::AgentNotificationsModel;
|
|
use ai::ambient_agents::scheduled::ScheduledAgentManager;
|
|
use ai::blocklist::{BlocklistAIHistoryModel, BlocklistAIPermissions};
|
|
use ai::execution_profiles::editor::ExecutionProfileEditorManager;
|
|
use ai::execution_profiles::profiles::AIExecutionProfilesModel;
|
|
use ai::persisted_workspace::PersistedWorkspace;
|
|
use auth::auth_state::AuthStateProvider;
|
|
use auth::{auth_manager::AuthManager, auth_state::AuthState};
|
|
use code::editor_management::CodeManager;
|
|
use code::opened_files::OpenedFilesModel;
|
|
use code_review::GlobalCodeReviewModel;
|
|
use galaxy_cli::GlobalOptions;
|
|
use galaxy_cli::{agent::AgentCommand, CliCommand};
|
|
use quit_warning::UnsavedStateSummary;
|
|
use server::network_log_pane_manager::NetworkLogPaneManager;
|
|
use server::network_logging::NetworkLogModel;
|
|
use server::telemetry::context_provider::AppTelemetryContextProvider;
|
|
use server::voice_transcriber::ServerVoiceTranscriber;
|
|
#[cfg(feature = "local_fs")]
|
|
use settings::import::model::ImportedConfigModel;
|
|
use voice::transcriber::VoiceTranscriber;
|
|
|
|
#[cfg(feature = "local_fs")]
|
|
use repo_metadata::{
|
|
repositories::DetectedRepositories, watcher::DirectoryWatcher, RepoMetadataModel,
|
|
};
|
|
#[cfg(feature = "local_fs")]
|
|
use watcher::HomeDirectoryWatcher;
|
|
|
|
use settings_view::pane_manager::SettingsPaneManager;
|
|
use terminal::general_settings::GeneralSettings;
|
|
use terminal::keys_settings::KeysSettings;
|
|
#[cfg(all(not(target_family = "wasm"), feature = "local_tty"))]
|
|
use terminal::local_shell::LocalShellState;
|
|
pub use util::bindings::cmd_or_ctrl_shift;
|
|
pub mod workflows;
|
|
pub mod workspace;
|
|
|
|
#[cfg(feature = "integration_tests")]
|
|
pub use persistence::testing as sqlite_testing;
|
|
|
|
use ::settings::{Setting, ToggleableSetting};
|
|
pub use galaxy_core::errors::{report_error, report_if_error};
|
|
|
|
use galaxy_core::user_preferences::GetUserPreferences as _;
|
|
use galaxyui::modals::{AlertDialogWithCallbacks, AppModalCallback};
|
|
use galaxyui::platform::app::ApproveTerminateResult;
|
|
#[cfg(feature = "plugin_host")]
|
|
pub use plugin::{run_plugin_host, PLUGIN_HOST_FLAG};
|
|
use window_settings::WindowSettings;
|
|
use workflows::manager::WorkflowManager;
|
|
|
|
use crate::ai::ambient_agents::github_auth_notifier::GitHubAuthNotifier;
|
|
use crate::ai::document::ai_document_model::AIDocumentModel;
|
|
use crate::ai::facts::manager::AIFactManager;
|
|
use crate::ai::llms::LLMPreferences;
|
|
use crate::ai::mcp::MCPGalleryManager;
|
|
use crate::ai::mcp::TemplatableMCPServerManager;
|
|
use crate::ai::outline::RepoOutlines;
|
|
use crate::ai::restored_conversations::RestoredAgentConversations;
|
|
use crate::ai::skills::SkillManager;
|
|
use crate::ai::AIRequestUsageModel;
|
|
use crate::autoupdate::{AutoupdateState, RelaunchModel};
|
|
use crate::changelog_model::ChangelogModel;
|
|
use crate::cloud_object::model::actions::ObjectActions;
|
|
use crate::cloud_object::model::view::CloudViewModel;
|
|
use crate::code::global_buffer_model::GlobalBufferModel;
|
|
#[cfg(feature = "local_fs")]
|
|
use crate::code::language_server_shutdown_manager::LanguageServerShutdownManager;
|
|
use crate::context_chips::prompt::Prompt;
|
|
use crate::default_terminal::DefaultTerminal;
|
|
use crate::drive::export::ExportManager;
|
|
use crate::env_vars::manager::EnvVarCollectionManager;
|
|
use crate::gpu_state::GPUState;
|
|
use crate::network::NetworkStatus;
|
|
use crate::notebooks::editor::keys::NotebookKeybindings;
|
|
use crate::notebooks::manager::NotebookManager;
|
|
use crate::notebooks::CloudNotebook;
|
|
use crate::palette::PaletteMode;
|
|
use crate::persistence::PersistenceWriter;
|
|
use crate::projects::ProjectManagementModel;
|
|
use crate::server::cloud_objects::{listener::Listener, update_manager::UpdateManager};
|
|
use crate::server::experiments::ServerExperiments;
|
|
use crate::server::sync_queue::{QueueItem, SyncQueue};
|
|
use crate::session_management::{RunningSessionSummary, SessionNavigationData};
|
|
use crate::settings::cloud_preferences_syncer::initialize_cloud_preferences_syncer;
|
|
use crate::settings::manager::SettingsManager;
|
|
use crate::settings::{AccessibilitySettings, ScrollSettings, SelectionSettings};
|
|
use crate::settings_view::keybindings::KeybindingChangedNotifier;
|
|
use crate::settings_view::DisplayCount;
|
|
use crate::suggestions::ignored_suggestions_model::IgnoredSuggestionsModel;
|
|
use crate::system::SystemStats;
|
|
use crate::terminal::cli_agent_sessions::CLIAgentSessionsModel;
|
|
use crate::terminal::keys::TerminalKeybindings;
|
|
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::vim_registers::VimRegisters;
|
|
use crate::warp_managed_paths_watcher::{ensure_warp_watch_roots_exist, WarpManagedPathsWatcher};
|
|
use crate::workflows::aliases::WorkflowAliases;
|
|
use crate::workflows::local_workflows::LocalWorkflows;
|
|
use crate::workspace::{ActiveSession, OneTimeModalModel, ToastStack};
|
|
use crate::workspaces::team_tester::TeamTesterStatus;
|
|
use crate::workspaces::user_profiles::UserProfiles;
|
|
#[cfg(feature = "local_tty")]
|
|
use anyhow::Context;
|
|
use anyhow::{anyhow, Result};
|
|
use appearance::{Appearance, AppearanceManager};
|
|
use channel::ChannelState;
|
|
use galaxy_core::execution_mode::{AppExecutionMode, ExecutionMode};
|
|
use galaxy_managed_secrets::ManagedSecretManager;
|
|
use interval_timer::IntervalTimer;
|
|
use itertools::Itertools;
|
|
use referral_theme_status::ReferralThemeStatus;
|
|
use rust_embed::RustEmbed;
|
|
use server::server_api::ServerApiProvider;
|
|
use settings::{ExtraMetaKeys, PrivacySettings};
|
|
use std::borrow::Cow;
|
|
use std::collections::HashSet;
|
|
use std::ops::Deref;
|
|
use std::sync::Arc;
|
|
use terminal::input;
|
|
use terminal::session_settings::SessionSettings;
|
|
use url::Url;
|
|
use workspace::sync_inputs::SyncedInputState;
|
|
|
|
use galaxyui::{integration::TestDriver, App, AssetProvider, Event};
|
|
|
|
use self::features::FeatureFlag;
|
|
use crate::app_state::AppState;
|
|
use crate::cloud_object::model::actions::ObjectAction;
|
|
use crate::cloud_object::model::persistence::CloudModel;
|
|
use crate::drive::CloudObjectTypeAndId;
|
|
use crate::experiments::ImprovedPaletteSearch;
|
|
pub use crate::global_resource_handles::{GlobalResourceHandles, GlobalResourceHandlesProvider};
|
|
use crate::notification::NotificationContext;
|
|
use crate::root_view::{
|
|
quake_mode_window_id, quake_mode_window_is_open, OpenFromRestoredArg, OpenPath,
|
|
};
|
|
pub use crate::server::telemetry::{
|
|
AgentModeEntrypoint, AgentModeEntrypointSelectionType, TelemetryEvent,
|
|
};
|
|
use crate::server::telemetry::{AppStartupInfo, CloseTarget, PaletteSource, TelemetryCollector};
|
|
use crate::terminal::CustomSecretRegexUpdater;
|
|
use crate::util::bindings::is_binding_cross_platform;
|
|
use crate::workspace::{PaneViewLocator, Workspace, WorkspaceAction};
|
|
use crate::workspaces::update_manager::TeamUpdateManager;
|
|
use crate::workspaces::user_workspaces::UserWorkspaces;
|
|
use galaxy_logging::LogDestination;
|
|
|
|
// Re-export the send_telemetry_from_ctx macro at the crate root level
|
|
pub use galaxy_core::send_telemetry_from_app_ctx;
|
|
pub use galaxy_core::send_telemetry_from_ctx;
|
|
|
|
// Re-export the safe logging macros at the crate root level for backwards compatibility
|
|
pub use galaxy_core::{safe_debug, safe_error, safe_info, safe_warn};
|
|
|
|
use crate::antivirus::AntivirusInfo;
|
|
#[cfg(feature = "local_fs")]
|
|
use galaxy_files::FileModel;
|
|
use galaxyui::platform::TerminationMode;
|
|
use galaxyui::windowing::state::ApplicationStage;
|
|
use galaxyui::{AppContext, SingletonEntity, WindowId};
|
|
|
|
#[derive(Clone, Copy, RustEmbed)]
|
|
#[folder = "assets"]
|
|
#[include = "bundled/**"] // Should be kept in sync with BUNDLED_ASSETS_DIR.
|
|
#[include = "async/**"] // Should be kept in sync with ASYNC_ASSETS_DIR.
|
|
#[cfg_attr(target_family = "wasm", exclude = "async/**")]
|
|
// Excludes take precedence.
|
|
// Standalone CLI builds (the `oz` tarball) are headless and never render the
|
|
// onboarding/theme imagery in `async/`, so we exclude those bytes from the
|
|
// embedded asset set to keep the CLI binary small — mirroring the carve-out
|
|
// already applied for the WASM target above.
|
|
#[cfg_attr(feature = "standalone", exclude = "async/**")]
|
|
pub struct Assets;
|
|
|
|
pub static ASSETS: Assets = Assets;
|
|
|
|
fn determine_agent_source(
|
|
launch_mode: &LaunchMode,
|
|
) -> Option<crate::ai::ambient_agents::AgentSource> {
|
|
match launch_mode {
|
|
LaunchMode::CommandLine { .. } => {
|
|
if std::env::var("GITHUB_ACTIONS").ok().as_deref() == Some("true") {
|
|
Some(crate::ai::ambient_agents::AgentSource::GitHubAction)
|
|
} else {
|
|
Some(crate::ai::ambient_agents::AgentSource::Cli)
|
|
}
|
|
}
|
|
LaunchMode::App { .. } | LaunchMode::Test { .. } => {
|
|
Some(crate::ai::ambient_agents::AgentSource::CloudMode)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Launch mode for how to start up Warp.
|
|
#[allow(clippy::large_enum_variant)]
|
|
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`.
|
|
/// Only used on dogfood channels.
|
|
api_key: Option<String>,
|
|
},
|
|
|
|
/// Run the Warp command-line SDK.
|
|
CommandLine {
|
|
command: galaxy_cli::CliCommand,
|
|
global_options: GlobalOptions,
|
|
debug: bool,
|
|
/// Whether this CLI invocation is running in a sandboxed environment.
|
|
is_sandboxed: bool,
|
|
/// Override for computer use permission from CLI flags. If None, uses default behavior.
|
|
computer_use_override: Option<bool>,
|
|
},
|
|
/// Run a test - this may be an integration test or an eval.
|
|
Test {
|
|
driver: Box<Option<TestDriver>>,
|
|
is_integration_test: bool,
|
|
},
|
|
}
|
|
|
|
impl LaunchMode {
|
|
fn args(&self) -> Cow<'_, galaxy_cli::AppArgs> {
|
|
match self {
|
|
LaunchMode::App { args, .. } => Cow::Borrowed(args),
|
|
_ => Cow::Owned(galaxy_cli::AppArgs::default()),
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if this process is running an integration test.
|
|
fn is_integration_test(&self) -> bool {
|
|
match self {
|
|
LaunchMode::Test {
|
|
is_integration_test,
|
|
..
|
|
} => *is_integration_test,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn take_test_driver(&mut self) -> Option<TestDriver> {
|
|
match self {
|
|
LaunchMode::Test { driver, .. } => driver.take(),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Add an URL to open. Only supported for [`LaunchMode::App`]
|
|
#[allow(dead_code)]
|
|
fn add_url(&mut self, url: Url) {
|
|
if let LaunchMode::App { ref mut args, .. } = self {
|
|
args.urls.push(url);
|
|
}
|
|
}
|
|
|
|
fn execution_mode(&self) -> ExecutionMode {
|
|
match self {
|
|
LaunchMode::App { .. } => ExecutionMode::App,
|
|
LaunchMode::CommandLine { .. } => ExecutionMode::Sdk,
|
|
LaunchMode::Test { .. } => ExecutionMode::App,
|
|
}
|
|
}
|
|
|
|
fn is_sandboxed(&self) -> bool {
|
|
match self {
|
|
LaunchMode::CommandLine { is_sandboxed, .. } => *is_sandboxed,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if Warp should run headlessly, without a visible UI.
|
|
fn is_headless(&self) -> bool {
|
|
match self {
|
|
LaunchMode::CommandLine { command, .. } => match command {
|
|
CliCommand::Agent(AgentCommand::Run(args)) => !args.gui,
|
|
_ => true,
|
|
},
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Returns `true` if running in app mode or via `agent run` to permit codebase indexing.
|
|
fn supports_indexing(&self) -> bool {
|
|
match self {
|
|
LaunchMode::CommandLine { command, .. } => {
|
|
matches!(command, CliCommand::Agent(AgentCommand::Run { .. }))
|
|
}
|
|
_ => true,
|
|
}
|
|
}
|
|
|
|
/// Whether or not to start a crash recovery process (on platforms that support it).
|
|
#[cfg(enable_crash_recovery)]
|
|
pub(crate) fn crash_recovery_enabled(&self) -> bool {
|
|
match self {
|
|
LaunchMode::App { .. } => true,
|
|
LaunchMode::CommandLine { .. } => false,
|
|
LaunchMode::Test { .. } => false,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub(crate) fn new_for_unit_test() -> Self {
|
|
LaunchMode::Test {
|
|
driver: Box::new(None),
|
|
is_integration_test: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AssetProvider for Assets {
|
|
fn get(&self, path: &str) -> Result<Cow<'_, [u8]>> {
|
|
<Assets as RustEmbed>::get(path)
|
|
.map(|f| f.data)
|
|
.ok_or_else(|| anyhow!("no asset exists at path {}", path))
|
|
}
|
|
}
|
|
|
|
/// If the given event is a key down event containing alt modifiers, and those
|
|
/// alt modifiers should be treated as meta keys, then remove the alts and
|
|
/// prefix the keys with an escape. See WAR-472.
|
|
fn apply_extra_meta_keys(event: &mut Event, extra_metas: ExtraMetaKeys) {
|
|
if let Event::KeyDown {
|
|
keystroke, details, ..
|
|
} = event
|
|
{
|
|
let left_as_meta = extra_metas.left_alt && details.left_alt;
|
|
let right_as_meta = extra_metas.right_alt && details.right_alt;
|
|
if left_as_meta || right_as_meta {
|
|
let side = match (left_as_meta, right_as_meta) {
|
|
(true, true) => "left+right alt",
|
|
(true, false) => "left alt",
|
|
(false, true) => "right alt",
|
|
(false, false) => unreachable!(),
|
|
};
|
|
log::info!("Treating {side} as meta");
|
|
keystroke.alt = false;
|
|
keystroke.meta = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn apply_scroll_multiplier(event: &mut Event, app: &AppContext) {
|
|
if let Event::ScrollWheel { delta, precise, .. } = event {
|
|
if !*precise {
|
|
let scroll_multiplier = *ScrollSettings::as_ref(app).mouse_scroll_multiplier.value();
|
|
*delta *= scroll_multiplier;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Runs the app. If a subcommand was requested, it'll be run instead of the main application.
|
|
pub fn run() -> Result<()> {
|
|
// Perform any necessary platform-specific initialization.
|
|
platform::init();
|
|
|
|
// Ensure feature flags are initialized before parsing command-line arguments.
|
|
init_feature_flags();
|
|
|
|
// Parse command-line arguments.
|
|
let args = galaxy_cli::Args::from_env();
|
|
|
|
// Server URL overrides are only honored on internal dev channels. Release channels silently
|
|
// ignore `--server-root-url` / `--ws-server-url` / `--session-sharing-server-url` (and their
|
|
// `WARP_*` env-var equivalents) so shipped builds can't be redirected away from their
|
|
// baked-in server URLs. See `Channel::allows_server_url_overrides`.
|
|
if ChannelState::channel().allows_server_url_overrides() {
|
|
if let Some(url) = args.server_root_url() {
|
|
if let Err(e) = ChannelState::override_server_root_url(url.to_owned()) {
|
|
eprintln!("Error: Invalid server root URL: {e:#}");
|
|
}
|
|
}
|
|
|
|
if let Some(url) = args.ws_server_url() {
|
|
if let Err(e) = ChannelState::override_ws_server_url(url.to_owned()) {
|
|
eprintln!("Error: Invalid websocket server URL: {e:#}");
|
|
}
|
|
}
|
|
|
|
if let Some(url) = args.session_sharing_server_url() {
|
|
if let Err(e) = ChannelState::override_session_sharing_server_url(url.to_owned()) {
|
|
eprintln!("Error: Invalid session sharing server URL: {e:#}");
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(command) = args.command() {
|
|
#[cfg(windows)]
|
|
if command.prints_to_stdout() {
|
|
// We attach a console to ensure that all standard output gets printed correctly.
|
|
galaxy_util::windows::attach_to_parent_console();
|
|
}
|
|
match command {
|
|
#[cfg(all(feature = "local_tty", unix))]
|
|
galaxy_cli::Command::Worker(galaxy_cli::WorkerCommand::TerminalServer(args)) => {
|
|
// If we were asked to run as a terminal server (as opposed to the main
|
|
// GUI application), do so immediately. Ideally, the terminal server would
|
|
// be a separate binary, but it's much easier to distribute a single binary,
|
|
// so starting the terminal server event loop immediately is the closest
|
|
// approximation we can get to running a separate binary.
|
|
crate::terminal::local_tty::server::run_terminal_server(args);
|
|
return Ok(());
|
|
}
|
|
#[cfg(feature = "plugin_host")]
|
|
galaxy_cli::Command::Worker(galaxy_cli::WorkerCommand::PluginHost { .. }) => {
|
|
return crate::run_plugin_host();
|
|
}
|
|
#[cfg(feature = "local_tty")]
|
|
galaxy_cli::Command::Worker(galaxy_cli::WorkerCommand::MinidumpServer {
|
|
socket_name,
|
|
}) => {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(all(linux_or_windows, feature = "crash_reporting"))] {
|
|
return crate::crash_reporting::run_minidump_server(socket_name);
|
|
} else {
|
|
let _ = socket_name;
|
|
panic!("The minidump server is not supported on this platform");
|
|
}
|
|
}
|
|
}
|
|
#[cfg(not(target_family = "wasm"))]
|
|
galaxy_cli::Command::Worker(galaxy_cli::WorkerCommand::RemoteServerProxy(args)) => {
|
|
return crate::remote_server::run_proxy(args.identity_key.clone());
|
|
}
|
|
#[cfg(not(target_family = "wasm"))]
|
|
galaxy_cli::Command::Worker(galaxy_cli::WorkerCommand::RemoteServerDaemon(args)) => {
|
|
return crate::remote_server::run_daemon(args.identity_key.clone());
|
|
}
|
|
#[cfg(not(target_family = "wasm"))]
|
|
galaxy_cli::Command::Worker(galaxy_cli::WorkerCommand::RipgrepSearch {
|
|
parent,
|
|
ignore_case,
|
|
multiline,
|
|
pattern,
|
|
paths,
|
|
}) => {
|
|
galaxy_ripgrep::search::run_search_subprocess(
|
|
std::slice::from_ref(pattern),
|
|
paths.clone(),
|
|
*ignore_case,
|
|
*multiline,
|
|
parent.pid,
|
|
)
|
|
.map_err(|err| anyhow!(err.to_string()))?;
|
|
return Ok(());
|
|
}
|
|
#[cfg(not(any(
|
|
feature = "local_tty",
|
|
feature = "plugin_host",
|
|
not(target_family = "wasm")
|
|
)))]
|
|
galaxy_cli::Command::Worker(worker) => {
|
|
// Need this case to handle platforms where there are no enum variants in
|
|
// galaxy_cli::WorkerCommand, as we still need to check Command::Worker.
|
|
|
|
// On wasm, specifically, we should fail spectacularly if we get here.
|
|
#[cfg(target_family = "wasm")]
|
|
panic!("Worker process not supported on WASM: {worker:?}")
|
|
}
|
|
galaxy_cli::Command::Completions { shell } => {
|
|
return galaxy_cli::completions::generate_to_stdout(*shell);
|
|
}
|
|
galaxy_cli::Command::CommandLine(cmd) => {
|
|
let (is_sandboxed, computer_use_override) = match cmd.as_ref() {
|
|
galaxy_cli::CliCommand::Agent(galaxy_cli::agent::AgentCommand::Run(
|
|
run_args,
|
|
)) => (
|
|
run_args.sandboxed,
|
|
run_args.computer_use.computer_use_override(),
|
|
),
|
|
_ => (false, None),
|
|
};
|
|
|
|
return run_internal(LaunchMode::CommandLine {
|
|
command: cmd.as_ref().clone(),
|
|
global_options: GlobalOptions {
|
|
output_format: args.output_format(),
|
|
api_key: args.api_key().cloned(),
|
|
},
|
|
debug: args.debug(),
|
|
is_sandboxed,
|
|
computer_use_override,
|
|
});
|
|
}
|
|
galaxy_cli::Command::DumpDebugInfo => {
|
|
return debug_dump::run();
|
|
}
|
|
#[cfg(not(target_family = "wasm"))]
|
|
galaxy_cli::Command::PrintTelemetryEvents => {
|
|
return TelemetryEvent::print_telemetry_events_json();
|
|
}
|
|
}
|
|
}
|
|
|
|
// If running as a standalone CLI binary or invoked as "oz", print help
|
|
// instead of launching the GUI app.
|
|
let is_cli_binary = cfg!(feature = "standalone")
|
|
|| galaxy_cli::binary_name().is_some_and(|name| name.starts_with("oz"))
|
|
|| std::env::var_os("WARP_CLI_MODE").is_some();
|
|
if is_cli_binary {
|
|
galaxy_cli::Args::clap_command().print_help()?;
|
|
return Ok(());
|
|
}
|
|
|
|
let api_key = args.api_key().cloned();
|
|
run_internal(LaunchMode::App {
|
|
args: args.into_app_args(),
|
|
api_key,
|
|
})
|
|
}
|
|
|
|
/// Runs an integration test using the provided test driver.
|
|
pub fn run_integration_test(driver: TestDriver) -> Result<()> {
|
|
let is_integration_test = std::env::var("WARP_INTEGRATION").is_ok();
|
|
let launch = LaunchMode::Test {
|
|
driver: Box::new(Some(driver)),
|
|
is_integration_test,
|
|
};
|
|
run_internal(launch)
|
|
}
|
|
|
|
/// Runs the app.
|
|
fn run_internal(mut launch_mode: LaunchMode) -> Result<()> {
|
|
#[cfg(windows)]
|
|
dynamic_libraries::configure_library_loading();
|
|
|
|
profiling::init();
|
|
|
|
// The `run` function already initializes feature flags, but ensure they're initialized here
|
|
// for other entrypoints.
|
|
init_feature_flags();
|
|
|
|
let mut timer = IntervalTimer::new();
|
|
|
|
#[cfg(feature = "crash_reporting")]
|
|
{
|
|
// Ensure that the main/root Sentry hub is initialized on the main
|
|
// thread. PtySpawner creates a background thread to receive logs from
|
|
// the terminal server process, and we don't want it to be the host of
|
|
// the primary sentry::Hub.
|
|
sentry::Hub::main();
|
|
}
|
|
|
|
tracing::init()?;
|
|
|
|
let is_cli = matches!(launch_mode, LaunchMode::CommandLine { .. });
|
|
|
|
// Log to a file for CLI commands to not obscure the command output.
|
|
let log_destination = match &launch_mode {
|
|
LaunchMode::CommandLine { debug, .. } => {
|
|
if *debug {
|
|
Some(LogDestination::Stderr)
|
|
} else {
|
|
Some(LogDestination::File)
|
|
}
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(enable_crash_recovery)] {
|
|
if crash_recovery::is_crash_recovery_process(launch_mode.args().as_ref()) {
|
|
galaxy_logging::init_for_crash_recovery_process()?;
|
|
} else {
|
|
galaxy_logging::init(galaxy_logging::LogConfig { is_cli, log_destination })?;
|
|
}
|
|
} else {
|
|
galaxy_logging::init(galaxy_logging::LogConfig { is_cli, log_destination })?;
|
|
}
|
|
}
|
|
|
|
timer.mark_interval_end("LOG_FILE_SETUP_COMPLETE");
|
|
|
|
// Adjust resource limits early, before doing other work, to ensure that
|
|
// any children we spawn (like the terminal server) inherit our adjusted
|
|
// rlimits.
|
|
resource_limits::adjust_resource_limits();
|
|
|
|
// For wasm builds we have this special case to parse out the intent
|
|
// from the url that is used to visite the app on web.
|
|
#[cfg(target_family = "wasm")]
|
|
{
|
|
use uri::web_intent_parser;
|
|
if let Some(intent) = web_intent_parser::parse_web_intent_from_current_url() {
|
|
launch_mode.add_url(intent);
|
|
}
|
|
web_intent_parser::set_context_flags_from_current_url();
|
|
}
|
|
|
|
// Collect errors that occur in run_internal() before the Sentry client is initialized,
|
|
// so they can be replayed to Sentry once it's ready.
|
|
#[cfg_attr(
|
|
not(all(feature = "release_bundle", any(windows, target_os = "linux"))),
|
|
expect(unused_mut)
|
|
)]
|
|
let mut pre_sentry_errors: Vec<anyhow::Error> = Vec::new();
|
|
|
|
#[cfg(all(feature = "release_bundle", target_os = "linux"))]
|
|
if let LaunchMode::App { .. } = launch_mode {
|
|
match app_services::linux::pass_startup_args_to_existing_instance(
|
|
launch_mode.args().as_ref(),
|
|
) {
|
|
// If we were able to contact an existing application instance, quit -
|
|
// we only want to run a single instance of Warp at a time.
|
|
Ok(_) => std::process::exit(0),
|
|
// If Warp isn't already running, we're good to go.
|
|
Err(app_services::linux::StartupArgsForwardingError::NoExistingInstance) => {}
|
|
// If we just finished an auto-update, we should continue running.
|
|
Err(app_services::linux::StartupArgsForwardingError::IgnoredAfterAutoUpdate) => {}
|
|
// If we were unable to perform the forwarding for an unknown reason,
|
|
// it's better to run a second instance than potentially end up in a
|
|
// state where Warp refuses to run even a first instance.
|
|
Err(err) => {
|
|
let err = anyhow::Error::from(err).context("Failed to forward startup args");
|
|
log::error!("{err:#}");
|
|
pre_sentry_errors.push(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(all(feature = "release_bundle", windows))]
|
|
if let LaunchMode::App { .. } = launch_mode {
|
|
match app_services::windows::pass_startup_args_to_existing_instance(
|
|
launch_mode.args().as_ref(),
|
|
) {
|
|
// If we were able to contact an existing application instance, quit -
|
|
// we only want to run a single instance of Warp at a time.
|
|
Ok(_) => std::process::exit(0),
|
|
// If Warp isn't already running, we're good to go.
|
|
Err(app_services::windows::StartupArgsForwardingError::NoExistingInstance) => {}
|
|
// If we just finished an auto-update, we should continue running.
|
|
Err(app_services::windows::StartupArgsForwardingError::IgnoredAfterAutoUpdate) => {}
|
|
// If we were unable to perform the forwarding for an unknown reason,
|
|
// it's better to run a second instance than potentially end up in a
|
|
// state where Warp refuses to run even a first instance.
|
|
Err(err) => {
|
|
let err = anyhow::Error::from(err).context("Failed to forward startup args");
|
|
log::error!("{err:#}");
|
|
pre_sentry_errors.push(err);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sets up a Job Object that we associate with the Warp process to handle
|
|
// shared fate with its child processes. This should be called before we
|
|
// start spawning any child processes.
|
|
#[cfg(windows)]
|
|
command::windows::init();
|
|
|
|
// Configure rustls to use its default crypto provider. This MUST be called
|
|
// before making any network requests that use TLS, otherwise rustls will
|
|
// panic.
|
|
#[cfg(not(target_family = "wasm"))]
|
|
rustls::crypto::aws_lc_rs::default_provider()
|
|
.install_default()
|
|
.expect("must be able to initialize crypto provider for TLS support");
|
|
|
|
let private_preferences = settings::init_private_user_preferences();
|
|
let (public_preferences, startup_toml_parse_error) = settings::init_public_user_preferences();
|
|
|
|
// When the SettingsFile feature flag is enabled, public settings live in
|
|
// the TOML-backed store. When disabled, they live in the platform-native
|
|
// store (same backend as private). Use the correct one for pre-app reads.
|
|
#[cfg_attr(not(any(enable_crash_recovery, target_os = "linux")), expect(unused))]
|
|
let prefs_for_public_settings: &dyn galaxyui_extras::user_preferences::UserPreferences =
|
|
if FeatureFlag::SettingsFile.is_enabled() {
|
|
public_preferences.as_ref()
|
|
} else {
|
|
private_preferences.deref()
|
|
};
|
|
|
|
#[cfg(enable_crash_recovery)]
|
|
let crash_recovery =
|
|
crash_recovery::CrashRecovery::new(&launch_mode, prefs_for_public_settings);
|
|
|
|
// Set up the pty spawner before doing any meaningful work. We want to
|
|
// ensure that the process is in the cleanest possible state (minimal opened
|
|
// files, modified signal handlers, etc.) to avoid unexpected effects on
|
|
// spawned ptys.
|
|
#[cfg(feature = "local_tty")]
|
|
let pty_spawner =
|
|
terminal::local_tty::spawner::PtySpawner::new().context("Failed to create pty spawner")?;
|
|
|
|
let mut app_builder = if launch_mode.is_headless() {
|
|
galaxyui::platform::AppBuilder::new_headless(
|
|
app_callbacks(launch_mode.is_integration_test()),
|
|
Box::new(ASSETS),
|
|
launch_mode.take_test_driver(),
|
|
)
|
|
} else {
|
|
galaxyui::platform::AppBuilder::new(
|
|
app_callbacks(launch_mode.is_integration_test()),
|
|
Box::new(ASSETS),
|
|
launch_mode.take_test_driver(),
|
|
)
|
|
};
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
use galaxyui::platform::mac::AppExt;
|
|
|
|
let activate_on_launch = !launch_mode.is_integration_test()
|
|
|| std::env::var("WARPUI_USE_REAL_DISPLAY_IN_INTEGRATION_TESTS").is_ok();
|
|
app_builder.set_activate_on_launch(activate_on_launch);
|
|
|
|
let dev_icon = ASSETS.get("bundled/png/local.png")?;
|
|
app_builder.set_dev_icon(dev_icon);
|
|
|
|
app_builder.set_menu_bar_builder(app_menus::menu_bar);
|
|
app_builder.set_dock_menu_builder(|_| app_menus::dock_menu());
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
use crate::settings::ForceX11;
|
|
use galaxyui::platform::linux::{self, AppBuilderExt};
|
|
|
|
app_builder.set_window_class(ChannelState::app_id().to_string());
|
|
|
|
let force_x11 = ForceX11::read_from_preferences(prefs_for_public_settings)
|
|
.unwrap_or(ForceX11::default_value());
|
|
// Force use of wayland if the user has passed the `WARP_ENABLE_WAYLAND` env var.
|
|
let allow_wayland = linux::is_wayland_env_var_set() || !force_x11;
|
|
app_builder.force_x11(!allow_wayland);
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
{
|
|
use galaxyui::platform::windows::AppBuilderExt;
|
|
app_builder.set_app_user_model_id(ChannelState::app_id().to_string());
|
|
|
|
// Only use DXC for DirectX shader compilation if we're not running in a Parallels VM
|
|
// Parallels VMs can have issues with DXC shader compilation
|
|
let is_parallels_vm = crate::util::vm_detection::is_running_in_windows_parallels_vm();
|
|
if !is_parallels_vm {
|
|
log::info!("Using DXC for DirectX shader compilation");
|
|
use galaxyui::platform::windows::DXCPath;
|
|
|
|
app_builder.use_dxc_for_directx_shader_compilation(DXCPath {
|
|
dxc_path: "dxcompiler.dll".to_string(),
|
|
dxil_path: "dxil.dll".to_string(),
|
|
});
|
|
} else {
|
|
log::info!("Skipping DXC for DirectX shader compilation; running in a Parallels VM");
|
|
}
|
|
}
|
|
|
|
// Override any bindings that have a `Custom` trigger to a `Keystroke`-based trigger. In theory,
|
|
// this should be a noop on Mac (since the keystrokes registered via the Mac menus first
|
|
// intercept the binding), but just to be safe we only enable this in cases where we don't
|
|
// include mac menus.
|
|
#[cfg(not(target_os = "macos"))]
|
|
app_builder.convert_custom_triggers_to_keystroke_triggers(
|
|
crate::util::bindings::custom_tag_to_keystroke,
|
|
);
|
|
|
|
#[cfg(target_os = "macos")]
|
|
app_builder.register_default_keystroke_triggers_for_custom_actions(
|
|
crate::util::bindings::custom_tag_to_keystroke,
|
|
);
|
|
|
|
app_builder.run(move |ctx| {
|
|
#[cfg(not(target_family = "wasm"))]
|
|
// Rotate the log files in the background.
|
|
ctx.background_executor()
|
|
.spawn(galaxy_logging::rotate_log_files())
|
|
.detach();
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
AppExecutionMode::new(
|
|
launch_mode.execution_mode(),
|
|
launch_mode.is_sandboxed(),
|
|
ctx,
|
|
)
|
|
});
|
|
#[cfg(feature = "crash_reporting")]
|
|
crate::crash_reporting::set_client_type_tag(launch_mode.execution_mode().client_id());
|
|
|
|
// Add the terminal server singleton to the application.
|
|
#[cfg(feature = "local_tty")]
|
|
ctx.add_singleton_model(move |_ctx| pty_spawner);
|
|
|
|
// Register user preferences. This must be done before initializing
|
|
// feature flags or experiments, both of which check user preferences for
|
|
// overrides.
|
|
ctx.add_singleton_model(move |_ctx| ::settings::PublicPreferences::new(public_preferences));
|
|
ctx.add_singleton_model(move |_ctx| private_preferences);
|
|
let startup_toml_parse_error = startup_toml_parse_error;
|
|
|
|
// Tell the settings crate whether the TOML settings file is active.
|
|
// This must happen after preferences are registered but before settings
|
|
// are initialized, so the routing logic picks the correct backend.
|
|
::settings::set_settings_file_enabled(FeatureFlag::SettingsFile.is_enabled());
|
|
|
|
#[cfg(enable_crash_recovery)]
|
|
ctx.add_singleton_model(move |_ctx| crash_recovery);
|
|
|
|
#[cfg(feature = "plugin_host")]
|
|
ctx.add_singleton_model(move |ctx| {
|
|
plugin::PluginHost::new(ctx).expect("Could not instantiate PluginHost")
|
|
});
|
|
let app_state = initialize_app(
|
|
&launch_mode,
|
|
timer,
|
|
startup_toml_parse_error,
|
|
ctx,
|
|
pre_sentry_errors,
|
|
);
|
|
|
|
if ImprovedPaletteSearch::improved_search_enabled(ctx) {
|
|
FeatureFlag::UseTantivySearch.set_enabled(true);
|
|
}
|
|
|
|
launch(ctx, app_state, launch_mode);
|
|
})
|
|
}
|
|
|
|
pub struct UpdateQuakeModeEventArg {
|
|
active_window_id: Option<WindowId>,
|
|
}
|
|
|
|
fn initialize_app(
|
|
launch_mode: &LaunchMode,
|
|
mut timer: IntervalTimer,
|
|
startup_toml_parse_error: Option<galaxyui_extras::user_preferences::Error>,
|
|
ctx: &mut galaxyui::AppContext,
|
|
_pre_sentry_errors: impl IntoIterator<Item = anyhow::Error>,
|
|
) -> Option<AppState> {
|
|
// WARNING: Errors that happen here before crash_reporting::init will not be collected in
|
|
// Sentry. Only the dependencies of crash_reporting should be initialized here. Avoid adding
|
|
// any other stuff here, as failures will be silent. Push them to pre_sentry_errors instead.
|
|
let data_domain = ChannelState::data_domain();
|
|
|
|
// Register an implementation of the secure storage service.
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(feature = "integration_tests")] {
|
|
galaxyui_extras::secure_storage::register_noop(&data_domain, ctx);
|
|
} else if #[cfg(target_os = "linux")] {
|
|
galaxyui_extras::secure_storage::register_with_fallback(&data_domain, galaxy_core::paths::state_dir(), ctx)
|
|
} else if #[cfg(target_os = "windows")] {
|
|
galaxyui_extras::secure_storage::register_with_dir(&data_domain, galaxy_core::paths::state_dir(), ctx)
|
|
} else {
|
|
galaxyui_extras::secure_storage::register(&data_domain, ctx);
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
#[cfg(target_os = "macos")]
|
|
preview_config_migration::migrate_preview_config_dir_if_needed();
|
|
|
|
ensure_warp_watch_roots_exist();
|
|
ctx.add_singleton_model(WarpManagedPathsWatcher::new);
|
|
|
|
ctx.add_singleton_model(WarpConfig::new);
|
|
ctx.add_singleton_model(|_ctx| SettingsManager::default());
|
|
|
|
let user_defaults_on_startup = settings::init(startup_toml_parse_error, ctx);
|
|
timer.mark_interval_end("READ_USER_DEFAULTS_AND_INITIALIZE_SETTINGS");
|
|
|
|
if FeatureFlag::UIZoom.is_enabled() {
|
|
ctx.set_zoom_factor(WindowSettings::as_ref(ctx).zoom_level.as_zoom_factor());
|
|
}
|
|
|
|
// Extract API key from command line options, if applicable.
|
|
let api_key = match launch_mode {
|
|
LaunchMode::CommandLine { global_options, .. } => global_options.api_key.clone(),
|
|
LaunchMode::App { api_key, .. } if ChannelState::channel().is_dogfood() => api_key.clone(),
|
|
_ => None,
|
|
};
|
|
let api_key = if FeatureFlag::APIKeyAuthentication.is_enabled() {
|
|
api_key
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let auth_state = Arc::new(AuthState::initialize(ctx, api_key));
|
|
timer.mark_interval_end("AUTH_MANAGER_SET_USER");
|
|
|
|
let agent_source = determine_agent_source(launch_mode);
|
|
|
|
// NetworkLogModel must be registered before ServerApiProvider so that
|
|
// `network_logging::init` (invoked from within `ServerApiProvider::new`)
|
|
// can reach it via `NetworkLogModel::handle(ctx)` when forwarding items
|
|
// captured by the HTTP client hooks.
|
|
ctx.add_singleton_model(|_ctx| NetworkLogModel::default());
|
|
|
|
let server_api_provider = ctx
|
|
.add_singleton_model(|ctx| ServerApiProvider::new(auth_state.clone(), agent_source, ctx));
|
|
let server_api = server_api_provider.as_ref(ctx).get();
|
|
let ai_client = server_api_provider.as_ref(ctx).get_ai_client();
|
|
|
|
ctx.add_singleton_model(|_ctx| AuthStateProvider::new(auth_state.clone()));
|
|
|
|
ctx.add_singleton_model(AppTelemetryContextProvider::new_context_provider);
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
AuthManager::new(
|
|
server_api.clone(),
|
|
server_api_provider.as_ref(ctx).get_auth_client(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
ctx.add_singleton_model(|_ctx| GPUState::new());
|
|
|
|
PrivacySettings::register_singleton(ctx);
|
|
|
|
// If any part of sqlite initialization fails, we just don't do session restoration (i.e.
|
|
// feature degradation).
|
|
let (sqlite_data, writer_handles) = persistence::initialize(ctx);
|
|
timer.mark_interval_end("SQLITE_INITIALIZED");
|
|
|
|
let persistence_writer = PersistenceWriter::new(writer_handles);
|
|
|
|
let model_event_sender = persistence_writer.sender();
|
|
|
|
let referral_theme_status = ctx.add_model(ReferralThemeStatus::new);
|
|
let tips_handle = ctx.add_model(|_| user_defaults_on_startup.tips_data);
|
|
let user_default_shell_unsupported_banner_model_handle =
|
|
ctx.add_model(|_| user_defaults_on_startup.user_default_shell_unsupported_banner_state);
|
|
// Extract the full-file parse error (if any) before the settings_file_error
|
|
// value is moved below. Only FileParseFailed gates the broken-file guard
|
|
// in `initialize_cloud_preferences_syncer`; InvalidSettings means TOML
|
|
// parsed but individual values were wrong, which doesn't mean local
|
|
// state is unusable.
|
|
let startup_toml_parse_error_for_syncer = user_defaults_on_startup
|
|
.settings_file_error
|
|
.as_ref()
|
|
.and_then(|err| match err {
|
|
settings::SettingsFileError::FileParseFailed(msg) => Some(msg.clone()),
|
|
settings::SettingsFileError::InvalidSettings(_) => None,
|
|
});
|
|
let settings_file_error = user_defaults_on_startup.settings_file_error;
|
|
ctx.add_singleton_model(move |_ctx| {
|
|
GlobalResourceHandlesProvider::new(GlobalResourceHandles {
|
|
model_event_sender,
|
|
tips_completed: tips_handle,
|
|
referral_theme_status,
|
|
user_default_shell_unsupported_banner_model_handle,
|
|
settings_file_error,
|
|
})
|
|
});
|
|
|
|
let (
|
|
cloud_objects,
|
|
cached_workspaces,
|
|
current_workspace_uid,
|
|
app_state,
|
|
command_history,
|
|
restored_user_profiles,
|
|
time_of_next_force_object_refresh,
|
|
object_actions,
|
|
experiments,
|
|
ai_queries,
|
|
persisted_workspaces,
|
|
workspace_language_servers,
|
|
multi_agent_conversations,
|
|
persisted_projects,
|
|
persisted_project_rules,
|
|
persisted_ignored_suggestions,
|
|
persisted_mcp_server_installations,
|
|
mcp_servers_to_restore,
|
|
) = sqlite_data
|
|
.map(|sqlite_data| {
|
|
(
|
|
sqlite_data.cloud_objects,
|
|
sqlite_data.workspaces,
|
|
sqlite_data.current_workspace_uid,
|
|
Some(sqlite_data.app_state),
|
|
sqlite_data.command_history,
|
|
sqlite_data.user_profiles,
|
|
sqlite_data.time_of_next_force_object_refresh,
|
|
sqlite_data.object_actions,
|
|
sqlite_data.experiments,
|
|
sqlite_data.ai_queries,
|
|
sqlite_data.codebase_indices,
|
|
sqlite_data.workspace_language_servers,
|
|
sqlite_data.multi_agent_conversations,
|
|
sqlite_data.projects,
|
|
sqlite_data.project_rules,
|
|
sqlite_data.ignored_suggestions,
|
|
sqlite_data.mcp_server_installations,
|
|
sqlite_data.mcp_servers_to_restore,
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
(
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
Default::default(),
|
|
)
|
|
});
|
|
|
|
// Initialize a global model to track server-side experiment state.
|
|
// This depends on the [`GlobalResourceHandlesProvider`] and so it must
|
|
// be initialized after it.
|
|
ctx.add_singleton_model(|ctx| ServerExperiments::new_from_cache(experiments, ctx));
|
|
|
|
ctx.add_singleton_model(|ctx| AIRequestUsageModel::new(ai_client, ctx));
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
UserWorkspaces::new(
|
|
server_api_provider.as_ref(ctx).get_team_client(),
|
|
server_api_provider.as_ref(ctx).get_workspace_client(),
|
|
cached_workspaces,
|
|
current_workspace_uid,
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
// Initialize ApiKeyManager after UserWorkspaces so it can subscribe to workspace/settings changes
|
|
ctx.add_singleton_model(|ctx| {
|
|
#[cfg_attr(target_family = "wasm", allow(unused_mut))]
|
|
let mut manager = ::ai::api_keys::ApiKeyManager::new(ctx);
|
|
#[cfg(not(target_family = "wasm"))]
|
|
manager.subscribe_to_settings_changes(ctx);
|
|
manager
|
|
});
|
|
|
|
ctx.add_singleton_model(AntivirusInfo::new);
|
|
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(feature = "crash_reporting")] {
|
|
let is_crash_reporting_enabled = crash_reporting::init(ctx);
|
|
} else {
|
|
let is_crash_reporting_enabled = false;
|
|
}
|
|
}
|
|
// Send buffered pre-init errors to Sentry now that the client is ready.
|
|
#[cfg(feature = "crash_reporting")]
|
|
for err in _pre_sentry_errors {
|
|
sentry::integrations::anyhow::capture_anyhow(&err);
|
|
}
|
|
timer.mark_interval_end("INIT_CRASH_REPORTING");
|
|
|
|
if let LaunchMode::App { .. } = launch_mode {
|
|
autoupdate::check_and_report_update_errors(ctx);
|
|
}
|
|
|
|
ctx.set_fallback_font_source_provider(|url| ::asset_cache::url_source(url));
|
|
|
|
ctx.set_default_binding_validator(is_binding_cross_platform);
|
|
|
|
if FeatureFlag::Autoupdate.is_enabled() {
|
|
// Attempt to clean up any old executable, whether or not we were
|
|
// explicitly launched as part of the auto-update process. We may have
|
|
// failed to remove the executable on a previous launch of the app and
|
|
// should try again.
|
|
if let Err(e) = autoupdate::remove_old_executable() {
|
|
log::error!("Failed to remove old executable: {e:?}");
|
|
}
|
|
}
|
|
|
|
experiments::init(ctx);
|
|
|
|
// Initialize timestamp for session id and last active event
|
|
App::record_last_active_timestamp();
|
|
|
|
ctx.add_singleton_model(|_| SettingsPaneManager::new());
|
|
ctx.add_singleton_model(|_| AIFactManager::new());
|
|
ctx.add_singleton_model(|_| ExecutionProfileEditorManager::default());
|
|
ctx.add_singleton_model(|_| NetworkLogPaneManager::default());
|
|
ctx.add_singleton_model(|_| pricing::PricingInfoModel::new());
|
|
ctx.add_singleton_model(|ctx| {
|
|
// Not using the *Provider types isn't ideal, but it's worth it for the ability to move managed secrets to a separate crate.
|
|
ManagedSecretManager::new(
|
|
server_api_provider.as_ref(ctx).get_managed_secrets_client(),
|
|
auth_state.clone(),
|
|
)
|
|
});
|
|
|
|
#[cfg(target_os = "macos")]
|
|
if !launch_mode.is_headless() {
|
|
AppearanceManager::as_ref(ctx).set_app_icon(ctx);
|
|
}
|
|
|
|
#[cfg(feature = "local_tty")]
|
|
terminal::available_shells::register(ctx);
|
|
|
|
// Add truly global actions that don't depend on the existence of any view here
|
|
ctx.add_global_action("app:toggle_user_ps1", move |_args: &(), ctx| {
|
|
SessionSettings::handle(ctx).update(ctx, |session_settings, ctx| {
|
|
report_if_error!(session_settings.honor_ps1.toggle_and_save_value(ctx));
|
|
});
|
|
});
|
|
ctx.add_global_action("app:toggle_copy_on_select", move |_args: &(), ctx| {
|
|
SelectionSettings::handle(ctx).update(ctx, |selection_settings, ctx| {
|
|
report_if_error!(selection_settings.copy_on_select.toggle_and_save_value(ctx));
|
|
});
|
|
});
|
|
|
|
ctx.add_singleton_model(|_ctx| SyncedInputState::new());
|
|
|
|
ctx.add_singleton_model(remote_server::manager::RemoteServerManager::new);
|
|
#[cfg(not(target_family = "wasm"))]
|
|
remote_server::wire_auth_token_rotation(ctx);
|
|
|
|
log::info!(
|
|
"Starting warp with channel state {} and version {:?}",
|
|
ChannelState::debug_str(),
|
|
ChannelState::app_version()
|
|
);
|
|
|
|
// Teach our app that sometimes option means meta.
|
|
ctx.set_event_munger(move |event, ctx| {
|
|
let extra_meta_keys = *KeysSettings::as_ref(ctx).extra_meta_keys;
|
|
apply_extra_meta_keys(event, extra_meta_keys);
|
|
apply_scroll_multiplier(event, ctx);
|
|
});
|
|
|
|
// Rewrite recognized Warp web URLs (sessions, Drive, settings, home) into local
|
|
// intent URLs when possible so they open directly in the desktop app.
|
|
ctx.set_before_open_url(|url_str, _ctx| {
|
|
if let Ok(url) = Url::parse(url_str) {
|
|
if let Some(intent) = maybe_rewrite_web_url_to_intent(&url) {
|
|
return intent.to_string();
|
|
}
|
|
}
|
|
url_str.to_owned()
|
|
});
|
|
|
|
ctx.set_a11y_verbosity(*AccessibilitySettings::as_ref(ctx).a11y_verbosity);
|
|
|
|
#[cfg(enable_crash_recovery)]
|
|
ctx.on_draw_frame_error(|ctx, window_id| {
|
|
crash_recovery::CrashRecovery::handle(ctx).update(ctx, |crash_recovery, _ctx| {
|
|
crash_recovery.on_draw_frame_error(window_id);
|
|
});
|
|
});
|
|
|
|
let user_is_logged_in = auth_state.is_logged_in();
|
|
|
|
if user_is_logged_in {
|
|
// Skip refresh_user for CLI mode — the CLI handles auth refresh in
|
|
// ensure_auth_state so it can detect invalid credentials before running
|
|
// a command.
|
|
if !matches!(launch_mode, LaunchMode::CommandLine { .. }) {
|
|
AuthManager::handle(ctx).update(ctx, |auth_manager, ctx| {
|
|
auth_manager.refresh_user(ctx);
|
|
});
|
|
}
|
|
|
|
// Set the first frame callback to record the app's startup time.
|
|
// This is only sent for logged-in users so that new users don't skew performance metrics.
|
|
let is_screen_reader_enabled = ctx.is_screen_reader_enabled();
|
|
let from_relaunch = launch_mode.args().finish_update;
|
|
ctx.on_first_frame_drawn(move |ctx| {
|
|
let timing_data = IntervalTimer::handle(ctx).update(ctx, |timer, _| {
|
|
timer.mark_interval_end("FIRST_FRAME_DRAWN");
|
|
timer.compute_stats()
|
|
});
|
|
let event = TelemetryEvent::AppStartup(AppStartupInfo {
|
|
is_session_restoration_on: user_defaults_on_startup.should_restore_session,
|
|
is_screen_reader_enabled,
|
|
from_relaunch,
|
|
is_crash_reporting_enabled,
|
|
timing_data,
|
|
});
|
|
|
|
GPUState::handle(ctx).update(ctx, |gpu_state, ctx| {
|
|
gpu_state.set_has_lower_power_gpu(
|
|
galaxyui::rendering::is_low_power_gpu_available(),
|
|
ctx,
|
|
);
|
|
});
|
|
|
|
for window_id in ctx.window_ids().collect_vec() {
|
|
SettingsPaneManager::handle(ctx)
|
|
.read(ctx, |model, _| model.settings_view(window_id))
|
|
.update(ctx, |settings, ctx| {
|
|
settings.refresh_preferred_graphics_backend_dropdown(ctx);
|
|
})
|
|
}
|
|
|
|
send_telemetry_from_app_ctx!(event, ctx);
|
|
});
|
|
|
|
#[cfg(enable_crash_recovery)]
|
|
ctx.on_frame_drawn(|ctx, window_id| {
|
|
crash_recovery::CrashRecovery::handle(ctx).update(ctx, |crash_recovery, ctx| {
|
|
crash_recovery.on_frame_drawn(window_id, ctx);
|
|
});
|
|
})
|
|
} else {
|
|
// If the app was opened while logged out, record an event for measuring new users.
|
|
// This is sent immediately in case they quit the app on the signup screen.
|
|
send_telemetry_sync_from_app_ctx!(TelemetryEvent::LoggedOutStartup, ctx);
|
|
download_method::determine_and_report(
|
|
auth_state.clone(),
|
|
ctx.background_executor().clone(),
|
|
);
|
|
}
|
|
|
|
#[cfg(not(target_family = "wasm"))]
|
|
{
|
|
ctx.add_singleton_model(DirectoryWatcher::new);
|
|
ctx.add_singleton_model(|_| DetectedRepositories::default());
|
|
if let Some(home_dir) = dirs::home_dir() {
|
|
ctx.add_singleton_model(|ctx| HomeDirectoryWatcher::new(home_dir, ctx));
|
|
} else {
|
|
log::info!("Home directory not found; skipping HomeDirectoryWatcher registration");
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "local_fs")]
|
|
{
|
|
let imported_config_model = ctx.add_singleton_model(ImportedConfigModel::new);
|
|
|
|
if FeatureFlag::SettingsImport.is_enabled()
|
|
&& ChannelState::channel() != galaxy_core::channel::Channel::Integration
|
|
{
|
|
imported_config_model.update(ctx, |model, ctx| {
|
|
model.search_for_settings_to_import(ctx);
|
|
});
|
|
}
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
let model = RepoMetadataModel::new(ctx);
|
|
|
|
// Subscribe to RemoteServerManager push events so that remote repo
|
|
// metadata snapshots and incremental updates populate the remote
|
|
// sub-model and trigger RepoMetadataEvent emissions.
|
|
{
|
|
use remote_server::manager::{RemoteServerManager, RemoteServerManagerEvent};
|
|
let mgr = RemoteServerManager::handle(ctx);
|
|
ctx.subscribe_to_model(&mgr, |me, event, ctx| match event {
|
|
RemoteServerManagerEvent::RepoMetadataSnapshot { host_id, update } => {
|
|
me.insert_remote_snapshot(host_id.clone(), update, ctx);
|
|
}
|
|
RemoteServerManagerEvent::RepoMetadataUpdated { host_id, update }
|
|
| RemoteServerManagerEvent::RepoMetadataDirectoryLoaded { host_id, update } => {
|
|
me.apply_remote_incremental_update(host_id, update, ctx);
|
|
}
|
|
RemoteServerManagerEvent::HostDisconnected { host_id } => {
|
|
me.remove_remote_repositories_for_host(host_id, ctx);
|
|
}
|
|
_ => {}
|
|
});
|
|
}
|
|
|
|
model
|
|
});
|
|
}
|
|
|
|
{
|
|
use code_review::git_status_update::GitStatusUpdateModel;
|
|
ctx.add_singleton_model(|_| GitStatusUpdateModel::new());
|
|
}
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
ProjectManagementModel::new(persisted_projects, persistence_writer.sender(), ctx)
|
|
});
|
|
|
|
ctx.add_singleton_model(move |_| History::new(command_history));
|
|
|
|
ctx.add_singleton_model(CustomSecretRegexUpdater::new);
|
|
|
|
// Register the `TelemetryCollection` singleton model.
|
|
let server_api_clone = server_api.clone();
|
|
ctx.add_singleton_model(|ctx| {
|
|
let telemetry_collector = TelemetryCollector::new(server_api_clone);
|
|
telemetry_collector.initialize_telemetry_collection(ctx);
|
|
telemetry_collector
|
|
});
|
|
timer.mark_interval_end("INITIALIZE_TELEMETRY_COLLECTION");
|
|
|
|
// Register initial keybindings prior to creating menus
|
|
ai::init(ctx);
|
|
app_services::init(ctx);
|
|
// // TODO: Temporarily disabling keybindings for WASM builds. Will be implemented in future WASM support.
|
|
#[cfg(not(target_family = "wasm"))]
|
|
code::editor::find::view::init(ctx);
|
|
workspace::init(ctx);
|
|
pane_group::init(ctx);
|
|
terminal::init(ctx);
|
|
input::init(ctx);
|
|
editor::init(ctx);
|
|
onboarding::init(ctx);
|
|
menu::init(ctx);
|
|
tips::tip_view::init(ctx);
|
|
launch_configs::init(ctx);
|
|
workflows::init(ctx);
|
|
themes::theme_chooser::init(ctx);
|
|
themes::theme_creator_modal::init(ctx);
|
|
themes::theme_deletion_modal::init(ctx);
|
|
root_view::init(ctx);
|
|
voltron::init(ctx);
|
|
auth::init(ctx);
|
|
reward_view::init(ctx);
|
|
crate::view_components::find::init(ctx);
|
|
prompt::editor_modal::init(ctx);
|
|
ai::blocklist::agent_view::editor::init(ctx);
|
|
undo_close::init(ctx);
|
|
billing::shared_objects_creation_denied_modal::init(ctx);
|
|
tab_configs::new_worktree_modal::init(ctx);
|
|
tab_configs::params_modal::init(ctx);
|
|
ai::blocklist::init(ctx);
|
|
ai::blocklist::block::status_bar::init(ctx);
|
|
drive::index::init(ctx);
|
|
drive::sharing::dialog::init(ctx);
|
|
ai_assistant::panel::init(ctx);
|
|
settings_view::update_environment_form::init(ctx);
|
|
env_vars::env_var_collection_block::init(ctx);
|
|
terminal::ssh::install_tmux::init(ctx);
|
|
terminal::ssh::warpify::init(ctx);
|
|
terminal::ssh::error::init(ctx);
|
|
context_chips::display_menu::init(ctx);
|
|
context_chips::node_version_popup::init(ctx);
|
|
env_vars::view::env_var_collection::init(ctx);
|
|
ai::agent::todos::popup::init(ctx);
|
|
terminal::view::init_environment::mode_selector::init(ctx);
|
|
coding_entrypoints::project_buttons::init(ctx);
|
|
if FeatureFlag::CodeReviewSaveChanges.is_enabled() {
|
|
code_review::init(ctx);
|
|
}
|
|
|
|
let display_count = ctx.windows().display_count();
|
|
ctx.add_singleton_model(|_| DisplayCount(display_count));
|
|
|
|
ctx.add_singleton_model(|_| RelaunchModel::new());
|
|
ctx.add_singleton_model(|_| ChangelogModel::new(server_api.clone()));
|
|
ctx.add_singleton_model(|_| GitHubAuthNotifier::new());
|
|
ctx.add_singleton_model(|_| NetworkStatus::new());
|
|
ctx.add_singleton_model(|_| SystemStats::new());
|
|
ctx.add_singleton_model(|_| KeybindingChangedNotifier::new());
|
|
ctx.add_singleton_model(|_| search::command_palette::SelectedItems::new());
|
|
ctx.add_singleton_model(search::files::model::FileSearchModel::new);
|
|
ctx.add_singleton_model(|_| VimRegisters::new());
|
|
ctx.add_singleton_model(UndoCloseStack::new);
|
|
ctx.add_singleton_model(|_| ToastStack);
|
|
ctx.add_singleton_model(|_| GlobalCodeReviewModel);
|
|
ctx.add_singleton_model(workspace::OneTimeModalModel::new);
|
|
ctx.add_singleton_model(
|
|
workspace::bonus_grant_notification_model::BonusGrantNotificationModel::new,
|
|
);
|
|
#[cfg(feature = "local_fs")]
|
|
ctx.add_singleton_model(FileModel::new);
|
|
ctx.add_singleton_model(GlobalBufferModel::new);
|
|
#[cfg(windows)]
|
|
ctx.add_singleton_model(util::traffic_lights::windows::RendererState::new);
|
|
#[cfg(feature = "local_fs")]
|
|
ctx.add_singleton_model(|_| LanguageServerShutdownManager::new());
|
|
|
|
#[cfg(feature = "voice_input")]
|
|
ctx.add_singleton_model(voice_input::VoiceInput::new);
|
|
ctx.add_singleton_model(|_| {
|
|
VoiceTranscriber::new(Arc::new(ServerVoiceTranscriber::new(server_api.clone())))
|
|
});
|
|
|
|
let notebooks = cloud_objects
|
|
.iter()
|
|
.filter_map(|object| {
|
|
let notebook: Option<&CloudNotebook> = object.into();
|
|
notebook
|
|
})
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut all_queue_items = Vec::new();
|
|
let objects_with_pending_changes = cloud_objects
|
|
.iter()
|
|
.filter(|object| object.metadata().has_pending_content_changes())
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
all_queue_items.extend(QueueItem::from_cached_objects(
|
|
objects_with_pending_changes.into_iter(),
|
|
));
|
|
|
|
let cloud_model = ctx.add_singleton_model(|_ctx| {
|
|
CloudModel::new(
|
|
persistence_writer.sender(),
|
|
cloud_objects,
|
|
time_of_next_force_object_refresh,
|
|
)
|
|
});
|
|
|
|
let unsynced_actions: Vec<(CloudObjectTypeAndId, ObjectAction)> = object_actions
|
|
.iter()
|
|
.filter(|action| action.is_pending())
|
|
.filter_map(|action| {
|
|
cloud_model.read(ctx, |model, _| {
|
|
let object = model.get_by_uid(&action.uid);
|
|
object.map(|o| (o.cloud_object_type_and_id(), action.clone()))
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
all_queue_items.extend(QueueItem::from_unsynced_actions(
|
|
unsynced_actions.into_iter(),
|
|
));
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
SyncQueue::new(
|
|
all_queue_items,
|
|
server_api_provider.as_ref(ctx).get_cloud_objects_client(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
{
|
|
let conversations = &multi_agent_conversations;
|
|
ctx.add_singleton_model(move |_| BlocklistAIHistoryModel::new(ai_queries, conversations));
|
|
}
|
|
ctx.add_singleton_model(move |_| RestoredAgentConversations::new(multi_agent_conversations));
|
|
ctx.add_singleton_model(|_| CLIAgentSessionsModel::new());
|
|
// ActiveAgentViewsModel is used to track active agent conversations and notify listeners when they change.
|
|
ctx.add_singleton_model(|_| ActiveAgentViewsModel::new());
|
|
ctx.add_singleton_model(AgentNotificationsModel::new);
|
|
ctx.add_singleton_model(BlocklistAIPermissions::new);
|
|
ctx.add_singleton_model(ai::blocklist::orchestration_events::OrchestrationEventService::new);
|
|
ctx.add_singleton_model(ai::blocklist::task_status_sync_model::TaskStatusSyncModel::new);
|
|
if galaxy_core::features::FeatureFlag::OrchestrationV2.is_enabled() {
|
|
ctx.add_singleton_model(
|
|
ai::blocklist::orchestration_event_streamer::OrchestrationEventStreamer::new,
|
|
);
|
|
}
|
|
|
|
ctx.add_singleton_model(RepoOutlines::new);
|
|
ctx.add_singleton_model(|ctx| {
|
|
galaxy_core::sync_queue::SyncQueue::<SyncTask>::new_with_rate_limit(
|
|
&ctx.background_executor(),
|
|
Some(DEFAULT_SYNC_REQUESTS_PER_MIN),
|
|
)
|
|
});
|
|
|
|
ctx.add_singleton_model(|_| UserProfiles::new(restored_user_profiles));
|
|
|
|
ctx.add_singleton_model(|_| ObjectActions::new(object_actions));
|
|
|
|
ctx.add_singleton_model(|_| AudibleBell::new());
|
|
|
|
// This model has to be registered after the user workspaces model because it relies on it,
|
|
// and before the UpdateManager models because they rely on the TeamTester model.
|
|
ctx.add_singleton_model(TeamTesterStatus::new);
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
TeamUpdateManager::new(
|
|
server_api_provider.as_ref(ctx).get_team_client(),
|
|
persistence_writer.sender(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
UpdateManager::new(
|
|
persistence_writer.sender(),
|
|
server_api_provider.as_ref(ctx).get_cloud_objects_client(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
let toml_file_path = settings::user_preferences_toml_file_path();
|
|
ctx.add_singleton_model(move |ctx| {
|
|
initialize_cloud_preferences_syncer(
|
|
toml_file_path,
|
|
startup_toml_parse_error_for_syncer.as_deref(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
// LogManager must be registered before any subsystem (e.g. MCP, LSP) that creates file-based loggers.
|
|
ctx.add_singleton_model(|_| simple_logger::manager::LogManager::new());
|
|
|
|
let running_mcp_servers = app_state
|
|
.as_ref()
|
|
.map(|app_state| app_state.running_mcp_servers.as_slice())
|
|
.unwrap_or(&[]);
|
|
|
|
// FileMCPWatcher must be registered before FileBasedMCPManager, which subscribes to it.
|
|
ctx.add_singleton_model(FileMCPWatcher::new);
|
|
ctx.add_singleton_model(FileBasedMCPManager::new);
|
|
|
|
// TemplatableMCPServerManager must be registered after UpdateManager and MCPServerManager so it can migrate legacy MCPs on start up
|
|
// It should also be registered after FileBasedMCPManager so it can receive file-based server updates.
|
|
ctx.add_singleton_model(|ctx| {
|
|
TemplatableMCPServerManager::new(
|
|
persisted_mcp_server_installations,
|
|
mcp_servers_to_restore,
|
|
running_mcp_servers,
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
// MCPGalleryManager subscribes to UpdateManager so that it can be notified when gallery items are updated.
|
|
// The registration of this singleton must be after UpdateManager is registered.
|
|
ctx.add_singleton_model(MCPGalleryManager::new);
|
|
|
|
// SkillManager is used to cache SKILL.md files for all active terminal views and their working directories
|
|
ctx.add_singleton_model(SkillManager::new);
|
|
|
|
// CloudViewModel subscribes to UpdateManager so that it can be notified when objects are
|
|
// created on the server.
|
|
ctx.add_singleton_model(CloudViewModel::new);
|
|
|
|
// AIDocumentModel subscribes to UpdateManager so that it can be notified when notebooks are created on the server.
|
|
ctx.add_singleton_model(AIDocumentModel::new);
|
|
|
|
// AgentConversationsModel subscribes to UpdateManager for RTC task updates.
|
|
ctx.add_singleton_model(AgentConversationsModel::new);
|
|
|
|
// ByoLlmAuthBannerSessionState tracks dismissal of the BYO LLM auth banner (e.g., AWS Bedrock login).
|
|
ctx.add_singleton_model(ByoLlmAuthBannerSessionState::new);
|
|
|
|
ctx.add_singleton_model(ExportManager::new);
|
|
ctx.add_singleton_model(|ctx| NotebookManager::new(notebooks, ctx));
|
|
ctx.add_singleton_model(|_| CodeManager::default());
|
|
ctx.add_singleton_model(|_| OpenedFilesModel::new());
|
|
ctx.add_singleton_model(NotebookKeybindings::new);
|
|
ctx.add_singleton_model(TerminalKeybindings::new);
|
|
ctx.add_singleton_model(|_| ActiveSession::default());
|
|
ctx.add_singleton_model(|ctx| {
|
|
Listener::new(
|
|
server_api_provider.as_ref(ctx).get_cloud_objects_client(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
#[cfg(all(not(target_family = "wasm"), feature = "local_tty"))]
|
|
{
|
|
ctx.add_singleton_model(LocalShellState::new);
|
|
ctx.add_singleton_model(system::SystemInfo::new);
|
|
}
|
|
|
|
// Add a singleton model that holds the current prompt configuration.
|
|
ctx.add_singleton_model(Prompt::new);
|
|
|
|
// Add a singleton model for resizable modals whose size should be persisted through restarts.
|
|
ctx.add_singleton_model(|_| ResizableData::default());
|
|
|
|
// Add a singleton model to maintain state of shared session across all windows.
|
|
ctx.add_singleton_model(terminal::shared_session::manager::Manager::new);
|
|
|
|
ctx.add_singleton_model(
|
|
terminal::shared_session::permissions_manager::SessionPermissionsManager::new,
|
|
);
|
|
|
|
ctx.add_singleton_model(EnvVarCollectionManager::new);
|
|
ctx.add_singleton_model(WorkflowManager::new);
|
|
|
|
if FeatureFlag::ScheduledAmbientAgents.is_enabled() {
|
|
ctx.add_singleton_model(ScheduledAgentManager::new);
|
|
}
|
|
|
|
AutoupdateState::register(ctx, server_api.clone());
|
|
|
|
ctx.add_singleton_model(LocalWorkflows::new);
|
|
|
|
ctx.add_singleton_model(LLMPreferences::new);
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
ai::agent_tips::AITipModel::<ai::AgentTip>::new_for_agent_tips(ctx)
|
|
});
|
|
|
|
timer.mark_interval_end("SINGLETON_MODELS_REGISTERED");
|
|
|
|
ctx.add_singleton_model(move |_| timer);
|
|
|
|
let is_ssh_tmux_wrapper_enabled = ctx
|
|
.private_user_preferences()
|
|
.read_value("SshTmuxWrapperOverride")
|
|
.ok()
|
|
.flatten()
|
|
.and_then(|s| s.parse().ok());
|
|
|
|
if let Some(is_ssh_tmux_wrapper_enabled) = is_ssh_tmux_wrapper_enabled {
|
|
FeatureFlag::SSHTmuxWrapper.set_user_preference(is_ssh_tmux_wrapper_enabled);
|
|
}
|
|
|
|
ctx.add_singleton_model(|ctx| AIExecutionProfilesModel::new(launch_mode, ctx));
|
|
|
|
ctx.add_singleton_model(DefaultTerminal::new);
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
let indices_to_restore = if UserWorkspaces::as_ref(ctx).is_codebase_context_enabled(ctx)
|
|
&& launch_mode.supports_indexing()
|
|
{
|
|
persisted_workspaces.clone()
|
|
} else {
|
|
vec![]
|
|
};
|
|
|
|
let codebase_limits = AIRequestUsageModel::as_ref(ctx).codebase_context_limits();
|
|
|
|
CodebaseIndexManager::new(
|
|
indices_to_restore,
|
|
codebase_limits.max_indices_allowed,
|
|
codebase_limits.max_files_per_repo,
|
|
codebase_limits.embedding_generation_batch_size,
|
|
server_api_provider.as_ref(ctx).get(),
|
|
ctx,
|
|
)
|
|
});
|
|
|
|
ctx.add_singleton_model(|ctx| {
|
|
ProjectContextModel::new_from_persisted(persisted_project_rules, ctx)
|
|
});
|
|
ctx.add_singleton_model(|ctx| {
|
|
PersistedWorkspace::new(
|
|
persisted_workspaces,
|
|
workspace_language_servers,
|
|
persistence_writer.sender(),
|
|
ctx,
|
|
)
|
|
});
|
|
ctx.add_singleton_model(move |_| persistence_writer);
|
|
|
|
ctx.add_singleton_model(input_classifier::InputClassifierModel::new);
|
|
|
|
ctx.add_singleton_model(move |_| IgnoredSuggestionsModel::new(persisted_ignored_suggestions));
|
|
|
|
// Subscribe WorkflowAliases to the UpdateManager so that it can be notified when objects are
|
|
// trashed.
|
|
WorkflowAliases::handle(ctx).update(ctx, |aliases, ctx| {
|
|
aliases.connect(ctx);
|
|
});
|
|
|
|
// When running natively, add the http server singleton to the application.
|
|
#[cfg(not(target_family = "wasm"))]
|
|
ctx.add_singleton_model(move |ctx| {
|
|
let routers = vec![
|
|
app_installation_detection::make_router(),
|
|
profiling::make_router(),
|
|
];
|
|
http_server::HttpServer::new(routers, ctx)
|
|
});
|
|
|
|
app_state
|
|
}
|
|
|
|
fn app_callbacks(is_integration_test: bool) -> galaxyui::platform::AppCallbacks {
|
|
galaxyui::platform::AppCallbacks {
|
|
on_internet_reachability_changed: Some(Box::new(move |reachable, ctx| {
|
|
NetworkStatus::handle(ctx)
|
|
.update(ctx, move |me, ctx| me.reachability_changed(reachable, ctx));
|
|
})),
|
|
on_become_active: Some(Box::new(move |ctx| {
|
|
let auth_state = AuthStateProvider::as_ref(ctx).get();
|
|
ctx.record_app_focus(
|
|
auth_state.user_id().map(|uid| uid.as_string()),
|
|
auth_state.anonymous_id(),
|
|
);
|
|
})),
|
|
on_screen_changed: Some(Box::new(move |ctx| {
|
|
ctx.dispatch_global_action(
|
|
"root_view:move_quake_mode_window_from_screen_change",
|
|
&KeysSettings::as_ref(ctx)
|
|
.quake_mode_settings
|
|
.value()
|
|
.clone(),
|
|
);
|
|
|
|
let new_display_count = ctx.windows().display_count();
|
|
DisplayCount::handle(ctx).update(ctx, |display_count, ctx| {
|
|
display_count.0 = new_display_count;
|
|
ctx.notify();
|
|
});
|
|
})),
|
|
on_cpu_awakened: Some(Box::new(move |ctx| {
|
|
SystemStats::handle(ctx).update(ctx, move |system, ctx| {
|
|
log::info!("System has returned from sleep");
|
|
system.dispatch_cpu_was_awakened(ctx);
|
|
});
|
|
})),
|
|
on_cpu_will_sleep: Some(Box::new(move |ctx| {
|
|
SystemStats::handle(ctx).update(ctx, move |system, ctx| {
|
|
log::info!("System is going to sleep...");
|
|
system.dispatch_cpu_will_sleep(ctx);
|
|
});
|
|
})),
|
|
on_resigned_active: Some(Box::new(move |ctx| {
|
|
let active_window_id = ctx.windows().active_window();
|
|
let update_quake_mode_arg = UpdateQuakeModeEventArg { active_window_id };
|
|
|
|
#[cfg(feature = "voice_input")]
|
|
{
|
|
if let voice_input::VoiceInputState::Listening { enabled_from, .. } =
|
|
voice_input::VoiceInput::as_ref(ctx).state()
|
|
{
|
|
// Abort the voice input if it's toggled from a key press, as we cannot listen to key events
|
|
// if the user is focused on a different app - we could miss the release of the key.
|
|
if matches!(
|
|
*enabled_from,
|
|
voice_input::VoiceInputToggledFrom::Key { .. }
|
|
) {
|
|
ctx.dispatch_global_action("root_view:abort_voice_input", &());
|
|
}
|
|
}
|
|
}
|
|
ctx.dispatch_global_action("root_view:update_quake_mode_state", &update_quake_mode_arg);
|
|
|
|
let auth_state = AuthStateProvider::as_ref(ctx).get();
|
|
ctx.record_app_blur(
|
|
auth_state.user_id().map(|uid| uid.as_string()),
|
|
auth_state.anonymous_id(),
|
|
);
|
|
})),
|
|
on_will_terminate: Some(Box::new(move |ctx| {
|
|
NotebookManager::handle(ctx).update(ctx, |manager, ctx| {
|
|
// Notebooks are only saved periodically, so ensure that any pending changes have
|
|
// been sent to the writer thread before terminating.
|
|
manager.close_notebooks(ctx);
|
|
});
|
|
|
|
PersistenceWriter::handle(ctx).update(ctx, |writer, _ctx| {
|
|
writer.terminate();
|
|
});
|
|
|
|
let auth_state = AuthStateProvider::as_ref(ctx).get();
|
|
ctx.try_record_daily_app_focus_duration(
|
|
auth_state.user_id().map(|uid| uid.as_string()),
|
|
auth_state.anonymous_id(),
|
|
);
|
|
TelemetryCollector::handle(ctx).update(ctx, |telemetry_collector, ctx| {
|
|
telemetry_collector.flush_telemetry_events_for_shutdown(ctx);
|
|
});
|
|
|
|
// Shutdown all LSP servers gracefully before app termination
|
|
lsp::LspManagerModel::handle(ctx).update(ctx, |manager, ctx| {
|
|
manager.terminate(ctx);
|
|
});
|
|
|
|
// We want to tear down the terminal server before relaunching for
|
|
// autoupdate, to ensure we're not running any extra Warp processes
|
|
// when we bring up the new process. Additionally, this must occur
|
|
// after terminating the persistence writer, so we don't keep track
|
|
// of the fact that the shell sessions terminated.
|
|
#[cfg(feature = "local_tty")]
|
|
terminal::local_tty::spawner::PtySpawner::handle(ctx).update(ctx, |pty_spawner, _| {
|
|
pty_spawner.prepare_for_app_termination();
|
|
});
|
|
|
|
#[cfg(all(feature = "local_tty", windows))]
|
|
terminal::local_tty::shutdown_all_pty_event_loops(ctx);
|
|
|
|
// Tear down app services before spawning the new process, to
|
|
// ensure that the new process doesn't find the old process while
|
|
// attempting to enforce our single-instance policy on Linux.
|
|
app_services::teardown(ctx);
|
|
autoupdate::spawn_child_if_necessary(ctx);
|
|
|
|
// Tear down any application profilers that are running, writing
|
|
// results to disk.
|
|
profiling::teardown();
|
|
|
|
#[cfg(enable_crash_recovery)]
|
|
crash_recovery::CrashRecovery::handle(ctx).update(ctx, |crash_recovery, _ctx| {
|
|
crash_recovery.teardown();
|
|
});
|
|
|
|
// Tear down crash reporting as the last thing we do before the application
|
|
// terminates.
|
|
#[cfg(feature = "crash_reporting")]
|
|
crash_reporting::uninit_sentry();
|
|
})),
|
|
on_should_close_window: Some(Box::new(move |window_id, ctx| {
|
|
let general_settings = GeneralSettings::as_ref(ctx);
|
|
// On Linux or Windows, if we're about to close the final window, we should quit the app instead.
|
|
// On Mac, we do this conditionally based on a user setting.
|
|
let quit_on_last_window_closed = cfg!(any(target_os = "linux", windows))
|
|
|| *general_settings.quit_on_last_window_closed;
|
|
if ctx.window_ids().count() == 1 && quit_on_last_window_closed {
|
|
log::info!("No windows left, terminating app");
|
|
ctx.terminate_app(TerminationMode::Cancellable, None);
|
|
return ApproveTerminateResult::Cancel;
|
|
}
|
|
|
|
let summary = UnsavedStateSummary::for_window(window_id, ctx);
|
|
|
|
send_telemetry_from_app_ctx!(
|
|
TelemetryEvent::UserInitiatedClose {
|
|
initiated_on: CloseTarget::Window,
|
|
},
|
|
ctx
|
|
);
|
|
|
|
// Don't show dialog on integration test. Machine can't press buttons.
|
|
if !is_integration_test && summary.should_display_warning(ctx) {
|
|
let shown = summary
|
|
.dialog()
|
|
.on_confirm(move |ctx| {
|
|
ctx.windows()
|
|
.close_window(window_id, TerminationMode::ForceTerminate);
|
|
})
|
|
.on_cancel(move |ctx| {
|
|
on_close_window_cancelled(window_id, false, ctx);
|
|
})
|
|
.on_show_processes(move |ctx| {
|
|
on_close_window_cancelled(window_id, true, ctx);
|
|
})
|
|
.show(ctx);
|
|
if shown {
|
|
ApproveTerminateResult::Cancel
|
|
} else {
|
|
ApproveTerminateResult::Terminate
|
|
}
|
|
} else {
|
|
ApproveTerminateResult::Terminate
|
|
}
|
|
})),
|
|
on_should_terminate_app: Some(Box::new(move |ctx| {
|
|
send_telemetry_from_app_ctx!(
|
|
TelemetryEvent::UserInitiatedClose {
|
|
initiated_on: CloseTarget::App,
|
|
},
|
|
ctx
|
|
);
|
|
|
|
// If there's a pending autoupdate, apply that before showing the unsaved changes
|
|
// dialog. We apply the update first so that the dialog can force-terminate.
|
|
let applying_update = autoupdate::apply_pending_update(ctx, |ctx| {
|
|
// Once the deferred update is applied, re-terminate the app. This termination is
|
|
// cancellable so that we still show the unsaved changes dialog.
|
|
log::info!("Deferred autoupdate applied, terminating app");
|
|
ctx.terminate_app(TerminationMode::Cancellable, None);
|
|
});
|
|
if applying_update {
|
|
return ApproveTerminateResult::Cancel;
|
|
}
|
|
|
|
let summary = UnsavedStateSummary::for_app(ctx);
|
|
// Don't show dialog on integration test. Machine can't press buttons.
|
|
if !is_integration_test && summary.should_display_warning(ctx) {
|
|
let shown = summary
|
|
.dialog()
|
|
.on_confirm(|ctx| ctx.terminate_app(TerminationMode::ForceTerminate, None))
|
|
.on_show_processes(|ctx| on_close_app_cancelled(true, ctx))
|
|
.on_cancel(|ctx| on_close_app_cancelled(false, ctx))
|
|
.show(ctx);
|
|
if shown {
|
|
return ApproveTerminateResult::Cancel;
|
|
}
|
|
}
|
|
|
|
ApproveTerminateResult::Terminate
|
|
})),
|
|
on_disable_warning_modal: Some(Box::new(move |ctx| {
|
|
GeneralSettings::handle(ctx).update(ctx, |general_settings, ctx| {
|
|
report_if_error!(general_settings
|
|
.show_warning_before_quitting
|
|
.toggle_and_save_value(ctx));
|
|
});
|
|
send_telemetry_from_app_ctx!(TelemetryEvent::QuitModalDisabled, ctx);
|
|
})),
|
|
on_notification_clicked: Some(Box::new(move |notification_response, ctx| {
|
|
if let Some(notification_data) = notification_response.data() {
|
|
let context: serde_json::Result<NotificationContext> =
|
|
serde_json::from_str(notification_data);
|
|
if let Ok(NotificationContext::BlockOrigin {
|
|
window_id,
|
|
pane_group_id,
|
|
pane_id,
|
|
}) = context
|
|
{
|
|
// Ensure the window ID exists, if so dispatch an action to focus
|
|
// the correct pane.
|
|
if ctx.window_ids().contains(&window_id) {
|
|
if let Some(root_view_id) = ctx.root_view_id(window_id) {
|
|
ctx.dispatch_action(
|
|
window_id,
|
|
&[root_view_id],
|
|
"root_view:handle_notification_click",
|
|
&PaneViewLocator {
|
|
pane_group_id,
|
|
pane_id,
|
|
},
|
|
log::Level::Info,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})),
|
|
on_new_window_requested: Some(Box::new(move |ctx| {
|
|
// This one is called when the app is requested to open a new window,
|
|
// e.g. clicking on the Dock icon. It is NOT called from the New Window
|
|
// menu item.
|
|
App::record_last_active_timestamp();
|
|
ctx.dispatch_global_action("root_view:open_new", &());
|
|
ctx.dispatch_global_action("workspace:save_app", &());
|
|
})),
|
|
on_open_urls: Some(Box::new(move |urls, ctx| {
|
|
for url in &urls {
|
|
let parsed_url = Url::parse(url);
|
|
match parsed_url {
|
|
Ok(url) => uri::handle_incoming_uri(&url, ctx),
|
|
Err(e) => log::warn!("Unable to parse received url: {e}"),
|
|
}
|
|
}
|
|
})),
|
|
on_os_appearance_changed: Some(Box::new(move |ctx| {
|
|
AppearanceManager::handle(ctx).update(ctx, |appearance_manager, ctx| {
|
|
appearance_manager.refresh_theme_state(ctx);
|
|
});
|
|
})),
|
|
on_active_window_changed: Some(Box::new(move |ctx| {
|
|
let windowing_model = ctx.windows();
|
|
let active_window_id = windowing_model.active_window();
|
|
let key_window_is_modal_panel = windowing_model.key_window_is_modal_panel();
|
|
|
|
if !key_window_is_modal_panel {
|
|
let update_quake_mode_arg = UpdateQuakeModeEventArg { active_window_id };
|
|
ctx.dispatch_global_action(
|
|
"root_view:update_quake_mode_state",
|
|
&update_quake_mode_arg,
|
|
);
|
|
}
|
|
|
|
if let Some(active_window_id) = active_window_id {
|
|
OneTimeModalModel::handle(ctx).update(ctx, |model, ctx| {
|
|
model.update_target_window_id(active_window_id, ctx);
|
|
});
|
|
}
|
|
|
|
ctx.dispatch_global_action("workspace:save_app", &());
|
|
})),
|
|
on_window_will_close: Some(Box::new(move |closed_window_data, ctx| {
|
|
if ctx.windows().stage() == ApplicationStage::Terminating {
|
|
return;
|
|
}
|
|
|
|
if let Some(window_data) = closed_window_data {
|
|
UndoCloseStack::handle(ctx).update(ctx, |stack, ctx| {
|
|
stack.handle_window_closed(window_data, ctx);
|
|
});
|
|
}
|
|
ctx.dispatch_global_action("workspace:save_app", &());
|
|
})),
|
|
on_window_moved: Some(Box::new(move |ctx| {
|
|
ctx.dispatch_global_action("workspace:save_app", &());
|
|
})),
|
|
on_window_resized: Some(Box::new(move |ctx| {
|
|
ctx.dispatch_global_action("workspace:save_app", &());
|
|
})),
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Focuses the active window or if there isn't one then a window with a running process
|
|
/// and then shows the native modal.
|
|
fn focus_running_window_and_show_native_modal(
|
|
sessions_summary: RunningSessionSummary,
|
|
dialog_with_callbacks: AlertDialogWithCallbacks<AppModalCallback>,
|
|
ctx: &mut AppContext,
|
|
) {
|
|
let windowing_model = ctx.windows();
|
|
let active_window_id = windowing_model.active_window();
|
|
// Show the nav palette in the active window. If there is no active window,
|
|
// arbitrarily pick one of the windows having a running process.
|
|
let window_id_to_focus = active_window_id.unwrap_or_else(|| {
|
|
*sessions_summary
|
|
.windows_running()
|
|
.iter()
|
|
.next()
|
|
.expect("already checked len > 0")
|
|
});
|
|
ctx.windows().show_window_and_focus_app(window_id_to_focus);
|
|
if let Some(workspaces) = ctx.views_of_type::<Workspace>(window_id_to_focus) {
|
|
if let Some(handle) = workspaces.first() {
|
|
handle.update(ctx, |view, ctx| {
|
|
view.show_native_modal(dialog_with_callbacks, ctx);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_close_app_cancelled(open_navigation_palette: bool, ctx: &mut AppContext) {
|
|
autoupdate::cancel_relaunch(ctx);
|
|
|
|
send_telemetry_from_app_ctx!(
|
|
TelemetryEvent::QuitModalCancel {
|
|
nav_palette: open_navigation_palette,
|
|
modal_for: CloseTarget::App,
|
|
},
|
|
ctx
|
|
);
|
|
|
|
let sessions = SessionNavigationData::all_sessions(ctx).collect_vec();
|
|
let sessions_summary = RunningSessionSummary::new(&sessions);
|
|
|
|
// If open_navigation_palette is false, return early. Otherwise, we honor the open_navigation_palette
|
|
// param which is true if the user clicked the modal button for that. However, if the running
|
|
// processes in this window have finished since the modal popped, there is nothing to do now and we
|
|
// can return early
|
|
if !open_navigation_palette || sessions_summary.long_running_cmds.is_empty() {
|
|
return;
|
|
}
|
|
|
|
let windowing_model = ctx.windows();
|
|
let active_window_id = windowing_model.active_window();
|
|
// show the nav palette in the active window. if there is no active window,
|
|
// arbitrarily pick one of the windows having a running process
|
|
let window_id_to_focus = active_window_id.unwrap_or_else(|| {
|
|
*sessions_summary
|
|
.windows_running()
|
|
.iter()
|
|
.next()
|
|
.expect("already checked len > 0")
|
|
});
|
|
|
|
windowing_model.show_window_and_focus_app(window_id_to_focus);
|
|
|
|
// open the nav palette in the selected window
|
|
if let Some(workspaces) = ctx.views_of_type::<Workspace>(window_id_to_focus) {
|
|
if let Some(handle) = workspaces.first() {
|
|
ctx.dispatch_typed_action_for_view(
|
|
window_id_to_focus,
|
|
handle.id(),
|
|
&WorkspaceAction::OpenPalette {
|
|
mode: PaletteMode::Navigation,
|
|
source: PaletteSource::QuitModal,
|
|
query: Some("running".to_owned()),
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_close_window_cancelled(
|
|
window_id: WindowId,
|
|
open_navigation_palette: bool,
|
|
ctx: &mut AppContext,
|
|
) {
|
|
send_telemetry_from_app_ctx!(
|
|
TelemetryEvent::QuitModalCancel {
|
|
nav_palette: open_navigation_palette,
|
|
modal_for: CloseTarget::Window,
|
|
},
|
|
ctx
|
|
);
|
|
|
|
let sessions = SessionNavigationData::all_sessions(ctx).collect_vec();
|
|
let sessions_summary = RunningSessionSummary::new(&sessions);
|
|
let num_processes_in_window = sessions_summary.processes_in_window(&window_id).len();
|
|
|
|
// If open_navigation_palette is false, return early. Otherwise, we honor the
|
|
// open_navigation_palette param which is true if the user clicked the modal
|
|
// button for that. However, if the running processes in this window have finished
|
|
// since the modal popped, there is nothing to do now and we can return early
|
|
if !open_navigation_palette || num_processes_in_window == 0 {
|
|
return;
|
|
}
|
|
|
|
ctx.windows().show_window_and_focus_app(window_id);
|
|
|
|
// if we haven't returned early, it means open_navigation_palette is true as the
|
|
// user pressed the modal button for opening the navigation palette to show their
|
|
// running processes
|
|
if let Some(workspaces) = ctx.views_of_type::<Workspace>(window_id) {
|
|
if let Some(handle) = workspaces.first() {
|
|
ctx.dispatch_typed_action_for_view(
|
|
window_id,
|
|
handle.id(),
|
|
&WorkspaceAction::OpenPalette {
|
|
mode: PaletteMode::Navigation,
|
|
source: PaletteSource::QuitModal,
|
|
query: Some("running".to_owned()),
|
|
},
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn launch(ctx: &mut galaxyui::AppContext, app_state: Option<AppState>, launch_mode: LaunchMode) {
|
|
IntervalTimer::handle(ctx).update(ctx, |timer, _ctx| {
|
|
timer.mark_interval_end("APP_LAUNCHED");
|
|
});
|
|
|
|
keyboard::load_custom_keybindings(ctx);
|
|
|
|
IntervalTimer::handle(ctx).update(ctx, |timer, _ctx| {
|
|
timer.mark_interval_end("KEYBINDINGS_LOADED");
|
|
});
|
|
|
|
// For now, we only specify application-level fallback fonts on web.
|
|
#[cfg(target_family = "wasm")]
|
|
ctx.set_fallback_font_fn(font_fallback::fallback_font_fn);
|
|
|
|
match &launch_mode {
|
|
LaunchMode::App { .. } | LaunchMode::Test { .. } => {
|
|
// Attempt to restore windows from the persisted application state.
|
|
let arg = OpenFromRestoredArg { app_state };
|
|
ctx.dispatch_global_action("root_view:open_from_restored", &arg);
|
|
|
|
// Process any URLs that were provided on the command line (which may be
|
|
// file:// URLs or ones using our custom URL scheme).
|
|
for url in launch_mode.args().urls.iter() {
|
|
uri::handle_incoming_uri(url, ctx);
|
|
}
|
|
|
|
// If, after session restoration and command-line argument handling, we
|
|
// haven't opened any windows, open a new window.
|
|
if ctx.window_ids().count() == 0 {
|
|
ctx.dispatch_global_action("root_view:open_new", &());
|
|
}
|
|
|
|
IntervalTimer::handle(ctx).update(ctx, |timer, _| {
|
|
timer.mark_interval_end("WINDOWS_CREATED");
|
|
});
|
|
|
|
// TODO(ben): We should skip this for LaunchMode::Test.
|
|
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
|
{
|
|
use crate::login_item::maybe_register_app_as_login_item;
|
|
use crate::terminal::general_settings::GeneralSettingsChangedEvent;
|
|
// Note that we put this here because it depends on settings already having been initialized.
|
|
ctx.subscribe_to_model(&GeneralSettings::handle(ctx), |_, event, ctx| {
|
|
if matches!(event, GeneralSettingsChangedEvent::LoginItem { .. }) {
|
|
maybe_register_app_as_login_item(ctx);
|
|
}
|
|
});
|
|
maybe_register_app_as_login_item(ctx);
|
|
}
|
|
}
|
|
#[cfg_attr(target_family = "wasm", allow(unused_variables))]
|
|
LaunchMode::CommandLine {
|
|
command,
|
|
global_options,
|
|
..
|
|
} => {
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(target_family = "wasm")] {
|
|
panic!("Cannot execute CLI command {command:?} on the web");
|
|
} else {
|
|
if let Err(err) = crate::ai::agent_sdk::run(ctx, command.clone(), global_options.clone()) {
|
|
eprintln!("{err:#}");
|
|
report_error!(err);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Initializes the logger before running tests.
|
|
///
|
|
/// The `ctor` attribute here means that this runs BEFORE main(), whenever the
|
|
/// binary is executed. For this reason, we need to ensure that this function
|
|
/// only exists within unit test code. Production bundles and integration tests
|
|
/// also initialize the logging system, and initializing it twice causes a panic.
|
|
///
|
|
/// Additionally, we must not write anything to stdout in this function, as it
|
|
/// can interfere with test harnesses collecting the set of tests to run. (This
|
|
/// is why we're not simply calling the init() function above.)
|
|
#[ctor::ctor]
|
|
#[cfg(test)]
|
|
fn init_logging_for_unit_tests_glue() {
|
|
// Initialize terminal-friendly logging for tests from the shared logger crate.
|
|
galaxy_logging::init_logging_for_unit_tests();
|
|
}
|
|
|
|
/// Mark all features which should be enabled on the current channel as enabled.
|
|
/// This sets global feature flag state and should never be called in a unit test.
|
|
pub fn init_feature_flags() {
|
|
for flag in enabled_features() {
|
|
flag.set_enabled(true);
|
|
}
|
|
features::mark_initialized();
|
|
}
|
|
|
|
/// Returns all feature flags which should be enabled in the current channel.
|
|
pub fn enabled_features() -> HashSet<FeatureFlag> {
|
|
// Enable features overridden for the given channel.
|
|
let mut flags = ChannelState::additional_features();
|
|
|
|
// Enable flags for release builds, if appropriate.
|
|
if ChannelState::is_release_bundle() {
|
|
flags.extend(features::RELEASE_FLAGS);
|
|
}
|
|
|
|
flags.extend([
|
|
#[cfg(feature = "autoupdate")]
|
|
FeatureFlag::Autoupdate,
|
|
#[cfg(feature = "changelog")]
|
|
FeatureFlag::Changelog,
|
|
#[cfg(feature = "cocoa_sentry")]
|
|
FeatureFlag::CocoaSentry,
|
|
#[cfg(feature = "crash_reporting")]
|
|
FeatureFlag::CrashReporting,
|
|
#[cfg(feature = "log_expensive_frames_in_sentry")]
|
|
FeatureFlag::LogExpensiveFramesInSentry,
|
|
#[cfg(feature = "record_app_active_events")]
|
|
FeatureFlag::RecordAppActiveEvents,
|
|
#[cfg(feature = "runtime_feature_flags")]
|
|
FeatureFlag::RuntimeFeatureFlags,
|
|
#[cfg(feature = "sequential_storage")]
|
|
FeatureFlag::SequentialStorage,
|
|
#[cfg(feature = "in_band_generators_ssh")]
|
|
FeatureFlag::InBandGeneratorsForSSH,
|
|
#[cfg(feature = "run_generators_with_cmd_exe")]
|
|
FeatureFlag::RunGeneratorsWithCmdExe,
|
|
#[cfg(feature = "ligatures")]
|
|
FeatureFlag::Ligatures,
|
|
#[cfg(feature = "selectable_prompt")]
|
|
FeatureFlag::SelectablePrompt,
|
|
#[cfg(feature = "viewing_shared_sessions")]
|
|
FeatureFlag::ViewingSharedSessions,
|
|
#[cfg(feature = "creating_shared_sessions")]
|
|
FeatureFlag::CreatingSharedSessions,
|
|
#[cfg(feature = "agent_mode")]
|
|
FeatureFlag::AgentMode,
|
|
#[cfg(feature = "shared_session_long_running_commands")]
|
|
FeatureFlag::SharedSessionWriteToLongRunningCommands,
|
|
#[cfg(feature = "resize_fix")]
|
|
FeatureFlag::ResizeFix,
|
|
#[cfg(feature = "richtext_multiselect")]
|
|
FeatureFlag::RichTextMultiselect,
|
|
#[cfg(feature = "default_waterfall_mode")]
|
|
FeatureFlag::DefaultWaterfallMode,
|
|
#[cfg(feature = "settings_file")]
|
|
FeatureFlag::SettingsFile,
|
|
#[cfg(feature = "settings_import")]
|
|
FeatureFlag::SettingsImport,
|
|
#[cfg(feature = "rect_selection")]
|
|
FeatureFlag::RectSelection,
|
|
#[cfg(feature = "alacritty_settings_import")]
|
|
FeatureFlag::AlacrittySettingsImport,
|
|
#[cfg(feature = "dynamic_workflow_enums")]
|
|
FeatureFlag::DynamicWorkflowEnums,
|
|
#[cfg(feature = "shared_with_me")]
|
|
FeatureFlag::SharedWithMe,
|
|
#[cfg(feature = "am_workflows")]
|
|
FeatureFlag::AgentModeWorkflows,
|
|
#[cfg(feature = "ai_rules")]
|
|
FeatureFlag::AIRules,
|
|
#[cfg(feature = "ssh_tmux_wrapper")]
|
|
FeatureFlag::SSHTmuxWrapper,
|
|
#[cfg(feature = "less_horizontal_terminal_padding")]
|
|
FeatureFlag::LessHorizontalTerminalPadding,
|
|
#[cfg(feature = "shell_selector")]
|
|
FeatureFlag::ShellSelector,
|
|
#[cfg(feature = "block_toolbelt_save_as_workflow")]
|
|
FeatureFlag::BlockToolbeltSaveAsWorkflow,
|
|
#[cfg(feature = "integration_command")]
|
|
FeatureFlag::IntegrationCommand,
|
|
#[cfg(feature = "artifact_command")]
|
|
FeatureFlag::ArtifactCommand,
|
|
#[cfg(feature = "cloud_environments")]
|
|
FeatureFlag::CloudEnvironments,
|
|
#[cfg(all(feature = "simulate_github_unauthed", debug_assertions))]
|
|
FeatureFlag::SimulateGithubUnauthed,
|
|
#[cfg(feature = "session_sharing_acls")]
|
|
FeatureFlag::SessionSharingAcls,
|
|
#[cfg(feature = "full_screen_zen_mode")]
|
|
FeatureFlag::FullScreenZenMode,
|
|
#[cfg(feature = "minimalist_ui")]
|
|
FeatureFlag::MinimalistUI,
|
|
#[cfg(feature = "remove_alt_screen_padding")]
|
|
FeatureFlag::RemoveAltScreenPadding,
|
|
#[cfg(feature = "avatar_in_tab_bar")]
|
|
FeatureFlag::AvatarInTabBar,
|
|
#[cfg(feature = "workflow_aliases")]
|
|
FeatureFlag::WorkflowAliases,
|
|
#[cfg(feature = "ssh_drag_and_drop")]
|
|
FeatureFlag::SshDragAndDrop,
|
|
#[cfg(feature = "drag_tabs_to_windows")]
|
|
FeatureFlag::DragTabsToWindows,
|
|
#[cfg(feature = "cycle_next_command_suggestion")]
|
|
FeatureFlag::CycleNextCommandSuggestion,
|
|
#[cfg(feature = "multi_workspace")]
|
|
FeatureFlag::MultiWorkspace,
|
|
#[cfg(feature = "ime_marked_text")]
|
|
FeatureFlag::ImeMarkedText,
|
|
#[cfg(feature = "partial_next_command_suggestions")]
|
|
FeatureFlag::PartialNextCommandSuggestions,
|
|
#[cfg(feature = "iterm_images")]
|
|
FeatureFlag::ITermImages,
|
|
#[cfg(feature = "validate_autosuggestions")]
|
|
FeatureFlag::ValidateAutosuggestions,
|
|
#[cfg(feature = "prompt_suggestions_via_maa")]
|
|
FeatureFlag::PromptSuggestionsViaMAA,
|
|
#[cfg(feature = "clear_autosuggestion_on_escape")]
|
|
FeatureFlag::ClearAutosuggestionOnEscape,
|
|
#[cfg(feature = "autoupdate_ui_revamp")]
|
|
FeatureFlag::AutoupdateUIRevamp,
|
|
#[cfg(all(not(windows), feature = "kitty_images"))]
|
|
FeatureFlag::KittyImages,
|
|
#[cfg(feature = "warp_packs")]
|
|
FeatureFlag::WarpPacks,
|
|
#[cfg(feature = "global_ai_analytics_banner")]
|
|
FeatureFlag::GlobalAIAnalyticsBanner,
|
|
#[cfg(feature = "global_ai_analytics_collection")]
|
|
FeatureFlag::GlobalAIAnalyticsCollection,
|
|
#[cfg(feature = "default_adeberry_theme")]
|
|
FeatureFlag::DefaultAdeberryTheme,
|
|
#[cfg(feature = "agent_mode_primary_xml")]
|
|
FeatureFlag::AgentModePrimaryXML,
|
|
#[cfg(feature = "agent_mode_pre_plan_xml")]
|
|
FeatureFlag::AgentModePrePlanXML,
|
|
#[cfg(feature = "agent_onboarding")]
|
|
FeatureFlag::AgentOnboarding,
|
|
#[cfg(feature = "agent_shared_sessions")]
|
|
FeatureFlag::AgentSharedSessions,
|
|
#[cfg(feature = "suggested_rules")]
|
|
FeatureFlag::SuggestedRules,
|
|
#[cfg(feature = "suggested_agent_mode_workflows")]
|
|
FeatureFlag::SuggestedAgentModeWorkflows,
|
|
#[cfg(feature = "command_correction_key")]
|
|
FeatureFlag::CommandCorrectionKey,
|
|
#[cfg(feature = "predict_am_queries")]
|
|
FeatureFlag::PredictAMQueries,
|
|
#[cfg(feature = "full_source_code_embedding")]
|
|
FeatureFlag::FullSourceCodeEmbedding,
|
|
#[cfg(feature = "use_tantivy_search")]
|
|
FeatureFlag::UseTantivySearch,
|
|
#[cfg(feature = "grep_tool")]
|
|
FeatureFlag::GrepTool,
|
|
#[cfg(feature = "mcp_server")]
|
|
FeatureFlag::McpServer,
|
|
#[cfg(feature = "mcp_debugging_ids")]
|
|
FeatureFlag::McpDebuggingIds,
|
|
#[cfg(feature = "markdown_tables")]
|
|
FeatureFlag::MarkdownTables,
|
|
#[cfg(feature = "blocklist_markdown_table_rendering")]
|
|
FeatureFlag::BlocklistMarkdownTableRendering,
|
|
#[cfg(feature = "blocklist_markdown_images")]
|
|
FeatureFlag::BlocklistMarkdownImages,
|
|
#[cfg(feature = "markdown_mermaid")]
|
|
FeatureFlag::MarkdownMermaid,
|
|
#[cfg(feature = "editable_markdown_mermaid")]
|
|
FeatureFlag::EditableMarkdownMermaid,
|
|
#[cfg(feature = "image_as_context")]
|
|
FeatureFlag::ImageAsContext,
|
|
#[cfg(feature = "msys2_shells")]
|
|
FeatureFlag::MSYS2Shells,
|
|
#[cfg(feature = "file_retrieval_tools")]
|
|
FeatureFlag::FileRetrievalTools,
|
|
#[cfg(feature = "reload_stale_conversation_files")]
|
|
FeatureFlag::ReloadStaleConversationFiles,
|
|
#[cfg(feature = "shared_block_title_generation")]
|
|
FeatureFlag::SharedBlockTitleGeneration,
|
|
#[cfg(feature = "retry_truncated_code_responses")]
|
|
FeatureFlag::RetryTruncatedCodeResponses,
|
|
#[cfg(feature = "read_image_files")]
|
|
FeatureFlag::ReadImageFiles,
|
|
#[cfg(feature = "usage_based_pricing")]
|
|
FeatureFlag::UsageBasedPricing,
|
|
#[cfg(feature = "cross_repo_context")]
|
|
FeatureFlag::CrossRepoContext,
|
|
#[cfg(feature = "codebase_index_persistence")]
|
|
FeatureFlag::CodebaseIndexPersistence,
|
|
#[cfg(feature = "ai_context_menu")]
|
|
FeatureFlag::AIContextMenuEnabled,
|
|
#[cfg(feature = "at_menu_outside_of_ai_mode")]
|
|
FeatureFlag::AtMenuOutsideOfAIMode,
|
|
#[cfg(feature = "ai_resume_button")]
|
|
FeatureFlag::AIResumeButton,
|
|
#[cfg(feature = "figma_detection")]
|
|
FeatureFlag::FigmaDetection,
|
|
#[cfg(feature = "agent_decides_command_execution")]
|
|
FeatureFlag::AgentDecidesCommandExecution,
|
|
#[cfg(feature = "codebase_index_speedbump")]
|
|
FeatureFlag::CodebaseIndexSpeedbump,
|
|
#[cfg(feature = "context_line_review_comments")]
|
|
FeatureFlag::ContextLineReviewComments,
|
|
#[cfg(feature = "nld_fasttext_model")]
|
|
FeatureFlag::NLDClassifierModelEnabled,
|
|
#[cfg(feature = "fast_forward_autoexecute_button")]
|
|
FeatureFlag::FastForwardAutoexecuteButton,
|
|
#[cfg(feature = "code_find_replace")]
|
|
FeatureFlag::CodeFindReplace,
|
|
#[cfg(feature = "command_palette_file_search")]
|
|
FeatureFlag::CommandPaletteFileSearch,
|
|
#[cfg(feature = "ai_context_menu_commands")]
|
|
FeatureFlag::AIContextMenuCommands,
|
|
#[cfg(feature = "ai_context_menu_code")]
|
|
FeatureFlag::AIContextMenuCode,
|
|
#[cfg(feature = "expand_edit_to_pane")]
|
|
FeatureFlag::ExpandEditToPane,
|
|
#[cfg(feature = "fallback_model_load_output_messaging")]
|
|
FeatureFlag::FallbackModelLoadOutputMessaging,
|
|
#[cfg(feature = "tab_close_button_on_left")]
|
|
FeatureFlag::TabCloseButtonOnLeft,
|
|
#[cfg(feature = "profiles_design_revamp")]
|
|
FeatureFlag::ProfilesDesignRevamp,
|
|
#[cfg(feature = "search_codebase_ui")]
|
|
FeatureFlag::SearchCodebaseUI,
|
|
#[cfg(feature = "changed_lines_only_apply_diff_result")]
|
|
FeatureFlag::ChangedLinesOnlyApplyDiffResult,
|
|
#[cfg(feature = "linked_code_blocks")]
|
|
FeatureFlag::LinkedCodeBlocks,
|
|
#[cfg(feature = "tabbed_editor_view")]
|
|
FeatureFlag::TabbedEditorView,
|
|
#[cfg(feature = "send_telemetry_to_file")]
|
|
FeatureFlag::SendTelemetryToFile,
|
|
#[cfg(feature = "undo_closed_panes")]
|
|
FeatureFlag::UndoClosedPanes,
|
|
#[cfg(feature = "multi_profile")]
|
|
FeatureFlag::MultiProfile,
|
|
#[cfg(feature = "conversation_artifacts")]
|
|
FeatureFlag::ConversationArtifacts,
|
|
#[cfg(feature = "sync_ambient_plans")]
|
|
FeatureFlag::SyncAmbientPlans,
|
|
#[cfg(feature = "get_started_tab")]
|
|
FeatureFlag::GetStartedTab,
|
|
#[cfg(feature = "welcome_tab")]
|
|
FeatureFlag::WelcomeTab,
|
|
#[cfg(feature = "projects")]
|
|
FeatureFlag::Projects,
|
|
#[cfg(feature = "drive_objects_as_context")]
|
|
FeatureFlag::DriveObjectsAsContext,
|
|
#[cfg(feature = "pr_comments_slash_command")]
|
|
FeatureFlag::PRCommentsSlashCommand,
|
|
#[cfg(feature = "pr_comments_v2")]
|
|
FeatureFlag::PRCommentsV2,
|
|
#[cfg(feature = "pr_comments_skill")]
|
|
FeatureFlag::PRCommentsSkill,
|
|
#[cfg(feature = "selection_as_context")]
|
|
FeatureFlag::SelectionAsContext,
|
|
#[cfg(feature = "code_mode_chip")]
|
|
FeatureFlag::CodeModeChip,
|
|
#[cfg(feature = "github_pr_prompt_chip")]
|
|
FeatureFlag::GithubPrPromptChip,
|
|
#[cfg(feature = "create_project_flow")]
|
|
FeatureFlag::CreateProjectFlow,
|
|
#[cfg(feature = "vim_code_editor")]
|
|
FeatureFlag::VimCodeEditor,
|
|
#[cfg(feature = "allow_opening_file_links_using_editor_env")]
|
|
FeatureFlag::AllowOpeningFileLinksUsingEditorEnv,
|
|
#[cfg(feature = "nld_improvements")]
|
|
FeatureFlag::NldImprovements,
|
|
#[cfg(feature = "revert_diff_hunk")]
|
|
FeatureFlag::RevertDiffHunk,
|
|
#[cfg(feature = "code_review_save_changes")]
|
|
FeatureFlag::CodeReviewSaveChanges,
|
|
#[cfg(feature = "file_tree")]
|
|
FeatureFlag::FileTree,
|
|
#[cfg(feature = "allow_ignoring_input_suggestions")]
|
|
FeatureFlag::AllowIgnoringInputSuggestions,
|
|
#[cfg(feature = "ambient_agents_command_line")]
|
|
FeatureFlag::AmbientAgentsCommandLine,
|
|
#[cfg(feature = "ambient_agents_image_upload")]
|
|
FeatureFlag::AmbientAgentsImageUpload,
|
|
#[cfg(feature = "scheduled_ambient_agents")]
|
|
FeatureFlag::ScheduledAmbientAgents,
|
|
#[cfg(feature = "code_launch_modal")]
|
|
FeatureFlag::CodeLaunchModal,
|
|
#[cfg(feature = "api_key_authentication")]
|
|
FeatureFlag::APIKeyAuthentication,
|
|
#[cfg(feature = "api_key_management")]
|
|
FeatureFlag::APIKeyManagement,
|
|
#[cfg(feature = "mcp_oauth")]
|
|
FeatureFlag::McpOauth,
|
|
#[cfg(feature = "file_based_mcp")]
|
|
FeatureFlag::FileBasedMcp,
|
|
#[cfg(feature = "diff_set_as_context")]
|
|
FeatureFlag::DiffSetAsContext,
|
|
#[cfg(feature = "discard_per_file_and_all_changes")]
|
|
FeatureFlag::DiscardPerFileAndAllChanges,
|
|
#[cfg(feature = "summarization_cancellation_confirmation")]
|
|
FeatureFlag::SummarizationCancellationConfirmation,
|
|
#[cfg(feature = "code_review_find")]
|
|
FeatureFlag::CodeReviewFind,
|
|
#[cfg(feature = "ui_zoom")]
|
|
FeatureFlag::UIZoom,
|
|
#[cfg(feature = "auto_open_code_review_pane")]
|
|
FeatureFlag::AutoOpenCodeReviewPane,
|
|
#[cfg(feature = "inline_code_review")]
|
|
FeatureFlag::InlineCodeReview,
|
|
#[cfg(feature = "create_environment_slash_command")]
|
|
FeatureFlag::CreateEnvironmentSlashCommand,
|
|
#[cfg(feature = "summarize_conversation_command")]
|
|
FeatureFlag::SummarizationConversationCommand,
|
|
#[cfg(feature = "mcp_grouped_server_context")]
|
|
FeatureFlag::MCPGroupedServerContext,
|
|
#[cfg(feature = "web_search_ui")]
|
|
FeatureFlag::WebSearchUI,
|
|
#[cfg(feature = "web_fetch_ui")]
|
|
FeatureFlag::WebFetchUI,
|
|
#[cfg(feature = "fork_from_command")]
|
|
FeatureFlag::ForkFromCommand,
|
|
#[cfg(feature = "context_window_usage_v2")]
|
|
FeatureFlag::ContextWindowUsageV2,
|
|
#[cfg(feature = "global_search")]
|
|
FeatureFlag::GlobalSearch,
|
|
#[cfg(feature = "embedded_code_review_comments")]
|
|
FeatureFlag::EmbeddedCodeReviewComments,
|
|
#[cfg(feature = "file_and_diff_set_comments")]
|
|
FeatureFlag::FileAndDiffSetComments,
|
|
#[cfg(feature = "revert_to_checkpoints")]
|
|
FeatureFlag::RevertToCheckpoints,
|
|
#[cfg(feature = "rewind_slash_command")]
|
|
FeatureFlag::RewindSlashCommand,
|
|
#[cfg(feature = "agent_management_view")]
|
|
FeatureFlag::AgentManagementView,
|
|
#[cfg(feature = "agent_management_details_view")]
|
|
FeatureFlag::AgentManagementDetailsView,
|
|
#[cfg(feature = "agent_view")]
|
|
FeatureFlag::AgentView,
|
|
#[cfg(feature = "agent_view_block_context")]
|
|
FeatureFlag::AgentViewBlockContext,
|
|
#[cfg(feature = "galaxy_managed_secrets")]
|
|
FeatureFlag::WarpManagedSecrets,
|
|
#[cfg(feature = "v4a_file_diffs")]
|
|
FeatureFlag::V4AFileDiffs,
|
|
#[cfg(feature = "interactive_conversation_management_view")]
|
|
FeatureFlag::InteractiveConversationManagementView,
|
|
#[cfg(feature = "agent_tips")]
|
|
FeatureFlag::AgentTips,
|
|
#[cfg(feature = "agent_mode_computer_use")]
|
|
FeatureFlag::AgentModeComputerUse,
|
|
#[cfg(feature = "local_computer_use")]
|
|
FeatureFlag::LocalComputerUse,
|
|
#[cfg(feature = "team_api_keys")]
|
|
FeatureFlag::TeamApiKeys,
|
|
#[cfg(feature = "cloud_conversations")]
|
|
FeatureFlag::CloudConversations,
|
|
#[cfg(feature = "agent_toolbar_editor")]
|
|
FeatureFlag::AgentToolbarEditor,
|
|
#[cfg(feature = "configurable_toolbar")]
|
|
FeatureFlag::ConfigurableToolbar,
|
|
#[cfg(feature = "agent_view_prompt_chip")]
|
|
FeatureFlag::AgentViewPromptChip,
|
|
#[cfg(feature = "ambient_agents_rtc")]
|
|
FeatureFlag::AmbientAgentsRTC,
|
|
#[cfg(feature = "classic_completions")]
|
|
FeatureFlag::ClassicCompletions,
|
|
#[cfg(feature = "force_classic_completions")]
|
|
FeatureFlag::ForceClassicCompletions,
|
|
#[cfg(feature = "agent_view_conversation_list_view")]
|
|
FeatureFlag::AgentViewConversationListView,
|
|
#[cfg(feature = "inline_history_menu")]
|
|
FeatureFlag::InlineHistoryMenu,
|
|
#[cfg(feature = "inline_repo_menu")]
|
|
FeatureFlag::InlineRepoMenu,
|
|
#[cfg(feature = "cloud_mode")]
|
|
FeatureFlag::CloudMode,
|
|
#[cfg(feature = "cloud_mode_from_local_session")]
|
|
FeatureFlag::CloudModeFromLocalSession,
|
|
#[cfg(feature = "cloud_mode_image_context")]
|
|
FeatureFlag::CloudModeImageContext,
|
|
#[cfg(feature = "summarization_via_message_replacement")]
|
|
FeatureFlag::SummarizationViaMessageReplacement,
|
|
#[cfg(feature = "pluggable_notifications")]
|
|
FeatureFlag::PluggableNotifications,
|
|
#[cfg(feature = "list_skills")]
|
|
FeatureFlag::ListSkills,
|
|
#[cfg(feature = "ask_user_question")]
|
|
FeatureFlag::AskUserQuestion,
|
|
#[cfg(feature = "lsp_as_a_tool")]
|
|
FeatureFlag::LSPAsATool,
|
|
#[cfg(feature = "inline_profile_selector")]
|
|
FeatureFlag::InlineProfileSelector,
|
|
#[cfg(feature = "oz_platform_skills")]
|
|
FeatureFlag::OzPlatformSkills,
|
|
#[cfg(feature = "oz_identity_federation")]
|
|
FeatureFlag::OzIdentityFederation,
|
|
#[cfg(feature = "oz_changelog_updates")]
|
|
FeatureFlag::OzChangelogUpdates,
|
|
#[cfg(feature = "bundled_skills")]
|
|
FeatureFlag::BundledSkills,
|
|
#[cfg(feature = "oz_launch_modal")]
|
|
FeatureFlag::OzLaunchModal,
|
|
#[cfg(feature = "open_warp_launch_modal")]
|
|
FeatureFlag::OpenWarpLaunchModal,
|
|
#[cfg(feature = "new_tab_styling")]
|
|
FeatureFlag::NewTabStyling,
|
|
#[cfg(feature = "skill_arguments")]
|
|
FeatureFlag::SkillArguments,
|
|
#[cfg(feature = "active_conversation_requires_interaction")]
|
|
FeatureFlag::ActiveConversationRequiresInteraction,
|
|
#[cfg(feature = "conversations_as_context")]
|
|
FeatureFlag::ConversationsAsContext,
|
|
#[cfg(feature = "incremental_auto_reload")]
|
|
FeatureFlag::IncrementalAutoReload,
|
|
#[cfg(feature = "orchestration")]
|
|
FeatureFlag::Orchestration,
|
|
#[cfg(feature = "orchestration_v2")]
|
|
FeatureFlag::OrchestrationV2,
|
|
#[cfg(feature = "pending_user_query_indicator")]
|
|
FeatureFlag::PendingUserQueryIndicator,
|
|
#[cfg(feature = "queue_slash_command")]
|
|
FeatureFlag::QueueSlashCommand,
|
|
#[cfg(feature = "kitty_keyboard_protocol")]
|
|
FeatureFlag::KittyKeyboardProtocol,
|
|
#[cfg(feature = "inline_menu_headers")]
|
|
FeatureFlag::InlineMenuHeaders,
|
|
#[cfg(feature = "directory_tab_colors")]
|
|
FeatureFlag::DirectoryTabColors,
|
|
#[cfg(feature = "open_warp_new_settings_modes")]
|
|
FeatureFlag::OpenWarpNewSettingsModes,
|
|
#[cfg(feature = "hoa_code_review")]
|
|
FeatureFlag::HoaCodeReview,
|
|
#[cfg(feature = "vertical_tabs")]
|
|
FeatureFlag::VerticalTabs,
|
|
#[cfg(feature = "vertical_tabs_summary_mode")]
|
|
FeatureFlag::VerticalTabsSummaryMode,
|
|
#[cfg(feature = "tab_configs")]
|
|
FeatureFlag::TabConfigs,
|
|
#[cfg(feature = "agent_harness")]
|
|
FeatureFlag::AgentHarness,
|
|
#[cfg(feature = "oz_handoff")]
|
|
FeatureFlag::OzHandoff,
|
|
#[cfg(feature = "hoa_notifications")]
|
|
FeatureFlag::HOANotifications,
|
|
#[cfg(feature = "open_code_notifications")]
|
|
FeatureFlag::OpenCodeNotifications,
|
|
#[cfg(feature = "cli_agent_rich_input")]
|
|
FeatureFlag::CLIAgentRichInput,
|
|
#[cfg(feature = "transfer_control_tool")]
|
|
FeatureFlag::TransferControlTool,
|
|
#[cfg(feature = "warpify_footer")]
|
|
FeatureFlag::WarpifyFooter,
|
|
#[cfg(feature = "solo_user_byok")]
|
|
FeatureFlag::SoloUserByok,
|
|
#[cfg(feature = "skip_firebase_anonymous_user")]
|
|
FeatureFlag::SkipFirebaseAnonymousUser,
|
|
#[cfg(feature = "hoa_onboarding_flow")]
|
|
FeatureFlag::HOAOnboardingFlow,
|
|
#[cfg(feature = "git_operations_in_code_review")]
|
|
FeatureFlag::GitOperationsInCodeReview,
|
|
#[cfg(feature = "hoa_remote_control")]
|
|
FeatureFlag::HOARemoteControl,
|
|
#[cfg(feature = "codex_notifications")]
|
|
FeatureFlag::CodexNotifications,
|
|
#[cfg(feature = "trim_trailing_blank_lines")]
|
|
FeatureFlag::TrimTrailingBlankLines,
|
|
#[cfg(feature = "cloud_mode_setup_v2")]
|
|
FeatureFlag::CloudModeSetupV2,
|
|
#[cfg(feature = "cloud_mode_input_v2")]
|
|
FeatureFlag::CloudModeInputV2,
|
|
]);
|
|
|
|
flags
|
|
}
|