Files
galaxy/app/src/lib.rs
T

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
}