244 lines
10 KiB
Rust
244 lines
10 KiB
Rust
use galaxy_core::features::FeatureFlag;
|
|
use onboarding::slides::{AgentAutonomy, AgentDevelopmentSettings};
|
|
use onboarding::{SelectedSettings, SessionDefault, UICustomizationSettings};
|
|
use settings::Setting as _;
|
|
use warpui::{AppContext, SingletonEntity as _};
|
|
|
|
use crate::ai::execution_profiles::profiles::AIExecutionProfilesModel;
|
|
use crate::ai::execution_profiles::{ActionPermission, WriteToPtyPermission};
|
|
use crate::drive::settings::WarpDriveSettings;
|
|
use crate::report_if_error;
|
|
use crate::settings::ai::DefaultSessionMode;
|
|
use crate::settings::{AISettings, CodeSettings};
|
|
use crate::workspace::tab_settings::TabSettings;
|
|
use crate::workspaces::user_workspaces::UserWorkspaces;
|
|
|
|
/// Applies onboarding settings based on the user's selected mode.
|
|
///
|
|
/// `has_account` indicates whether the user has (or is creating) a real Warp
|
|
/// account. Warp's AI features run on a Warp account, so agent intent only
|
|
/// enables AI when `has_account` is true; skipping login leaves AI off.
|
|
pub fn apply_onboarding_settings(
|
|
selected_settings: &SelectedSettings,
|
|
has_account: bool,
|
|
app: &mut AppContext,
|
|
) {
|
|
let is_ai_enabled = match selected_settings {
|
|
SelectedSettings::AgentDrivenDevelopment {
|
|
agent_settings,
|
|
ui_customization,
|
|
..
|
|
} => {
|
|
apply_agent_settings(agent_settings, app);
|
|
if let Some(ui) = ui_customization {
|
|
apply_ui_customization_settings(ui, true, app);
|
|
}
|
|
// Agent intent means the user wants AI, but Warp's AI features run
|
|
// on a Warp account, so AI is only enabled once they have one.
|
|
// Skipping login leaves AI off even for agent intent (including the
|
|
// bring-your-own-agents `disable_oz` path).
|
|
has_account
|
|
}
|
|
SelectedSettings::Terminal {
|
|
ui_customization,
|
|
cli_agent_toolbar_enabled,
|
|
show_agent_notifications,
|
|
} => {
|
|
// In old onboarding, there's nothing to set for terminal intent.
|
|
if !FeatureFlag::OpenWarpNewSettingsModes.is_enabled() {
|
|
true
|
|
} else {
|
|
if let Some(ui) = ui_customization {
|
|
apply_ui_customization_settings(ui, false, app);
|
|
}
|
|
AISettings::handle(app).update(app, |settings, ctx| {
|
|
report_if_error!(settings
|
|
.should_render_cli_agent_footer
|
|
.set_value(*cli_agent_toolbar_enabled, ctx));
|
|
report_if_error!(settings
|
|
.show_agent_notifications
|
|
.set_value(*show_agent_notifications, ctx));
|
|
});
|
|
false
|
|
}
|
|
}
|
|
};
|
|
|
|
if FeatureFlag::OpenWarpNewSettingsModes.is_enabled() {
|
|
AISettings::handle(app).update(app, |settings, ctx| {
|
|
report_if_error!(settings.is_any_ai_enabled.set_value(is_ai_enabled, ctx));
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Applies the explicit UI customization settings chosen during the
|
|
/// "Customize your UI" onboarding slide.
|
|
fn apply_ui_customization_settings(
|
|
ui: &UICustomizationSettings,
|
|
is_agent_intent: bool,
|
|
app: &mut AppContext,
|
|
) {
|
|
// Customize UI slide should only exist with this flag enabled.
|
|
if !FeatureFlag::OpenWarpNewSettingsModes.is_enabled() {
|
|
return;
|
|
}
|
|
TabSettings::handle(app).update(app, |settings, ctx| {
|
|
report_if_error!(settings
|
|
.use_vertical_tabs
|
|
.set_value(ui.use_vertical_tabs, ctx));
|
|
report_if_error!(settings
|
|
.show_code_review_button
|
|
.set_value(ui.show_code_review_button, ctx));
|
|
});
|
|
|
|
WarpDriveSettings::handle(app).update(app, |settings, ctx| {
|
|
report_if_error!(settings
|
|
.enable_warp_drive
|
|
.set_value(ui.show_warp_drive, ctx));
|
|
});
|
|
|
|
CodeSettings::handle(app).update(app, |settings, ctx| {
|
|
report_if_error!(settings
|
|
.show_project_explorer
|
|
.set_value(ui.show_project_explorer, ctx));
|
|
report_if_error!(settings
|
|
.show_global_search
|
|
.set_value(ui.show_global_search, ctx));
|
|
});
|
|
|
|
// For agent intent, configure showing conversation history.
|
|
// For terminal intent, this option was not surfaced in onboarding, so leave the default.
|
|
// It will be hidden anyway because AI is off, but we want to keep the default in case they enable AI later.
|
|
if is_agent_intent {
|
|
AISettings::handle(app).update(app, |settings, ctx| {
|
|
report_if_error!(settings
|
|
.show_conversation_history
|
|
.set_value(ui.show_conversation_history, ctx));
|
|
});
|
|
}
|
|
}
|
|
|
|
fn apply_agent_settings(agent_settings: &AgentDevelopmentSettings, app: &mut AppContext) {
|
|
// Apply session default mode.
|
|
let default_mode = match agent_settings.session_default {
|
|
SessionDefault::Agent => DefaultSessionMode::Agent,
|
|
SessionDefault::Terminal => DefaultSessionMode::Terminal,
|
|
};
|
|
AISettings::handle(app).update(app, |settings, ctx| {
|
|
report_if_error!(settings
|
|
.default_session_mode_internal
|
|
.set_value(default_mode, ctx));
|
|
});
|
|
|
|
let workspace_autonomy_settings = UserWorkspaces::as_ref(app).ai_autonomy_settings();
|
|
|
|
AISettings::handle(app).update(app, |settings, ctx| {
|
|
report_if_error!(settings
|
|
.should_render_cli_agent_footer
|
|
.set_value(agent_settings.cli_agent_toolbar_enabled, ctx));
|
|
report_if_error!(settings
|
|
.show_agent_notifications
|
|
.set_value(agent_settings.show_agent_notifications, ctx));
|
|
});
|
|
|
|
AIExecutionProfilesModel::handle(app).update(app, |profiles, ctx| {
|
|
let default_profile_info = profiles.default_profile(ctx);
|
|
let default_profile_id = *default_profile_info.id();
|
|
|
|
// Preserve the existing cloud default profile for users who are
|
|
// already logged in (or who log in at the end of onboarding). A
|
|
// `Some` sync_id means the profile is backed by a cloud object that
|
|
// was either loaded at startup or reconciled during the post-login
|
|
// initial load, and its values represent what the user has stored
|
|
// previously. Overwriting those with the onboarding-selected
|
|
// base_model / autonomy would silently discard their prior
|
|
// customizations. Fresh `Unsynced` default profiles (brand-new
|
|
// users, or users without any cloud default yet) still receive the
|
|
// onboarding values.
|
|
if default_profile_info.sync_id().is_some() {
|
|
log::info!(
|
|
"Preserving existing cloud default execution profile; skipping \
|
|
onboarding-driven overrides for profile {default_profile_id:?}"
|
|
);
|
|
return;
|
|
}
|
|
|
|
profiles.set_base_model(
|
|
default_profile_id,
|
|
Some(agent_settings.selected_model_id.clone()),
|
|
ctx,
|
|
);
|
|
|
|
// If autonomy is None, the workspace enforces autonomy settings, so skip setting them.
|
|
let Some(autonomy) = agent_settings.autonomy else {
|
|
return;
|
|
};
|
|
|
|
let permissions = action_permissions_for_onboarding_autonomy(autonomy);
|
|
|
|
// Only set permissions that are not enforced by the workspace
|
|
if !workspace_autonomy_settings.has_override_for_code_diffs() {
|
|
profiles.set_apply_code_diffs(default_profile_id, &permissions.apply_code_diffs, ctx);
|
|
}
|
|
if !workspace_autonomy_settings.has_override_for_read_files() {
|
|
profiles.set_read_files(default_profile_id, &permissions.read_files, ctx);
|
|
}
|
|
if !workspace_autonomy_settings.has_override_for_execute_commands() {
|
|
profiles.set_execute_commands(default_profile_id, &permissions.execute_commands, ctx);
|
|
}
|
|
// Note: MCP permissions don't have a workspace-level override, so always set them
|
|
profiles.set_mcp_permissions(default_profile_id, &permissions.mcp_permissions, ctx);
|
|
if !workspace_autonomy_settings.has_override_for_write_to_pty() {
|
|
profiles.set_write_to_pty(default_profile_id, &permissions.write_to_pty, ctx);
|
|
}
|
|
});
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
struct OnboardingAutonomyPermissions {
|
|
apply_code_diffs: ActionPermission,
|
|
read_files: ActionPermission,
|
|
execute_commands: ActionPermission,
|
|
mcp_permissions: ActionPermission,
|
|
write_to_pty: WriteToPtyPermission,
|
|
}
|
|
|
|
fn action_permissions_for_onboarding_autonomy(
|
|
autonomy: AgentAutonomy,
|
|
) -> OnboardingAutonomyPermissions {
|
|
match autonomy {
|
|
// Full autonomy promises "Runs commands, writes code, and reads files
|
|
// without asking," so every permission is `AlwaysAllow`. The command
|
|
// denylist still takes precedence at runtime when a specific command
|
|
// is considered unsafe.
|
|
AgentAutonomy::Full => OnboardingAutonomyPermissions {
|
|
apply_code_diffs: ActionPermission::AlwaysAllow,
|
|
read_files: ActionPermission::AlwaysAllow,
|
|
execute_commands: ActionPermission::AlwaysAllow,
|
|
mcp_permissions: ActionPermission::AlwaysAllow,
|
|
write_to_pty: WriteToPtyPermission::AlwaysAllow,
|
|
},
|
|
// Partial autonomy: reads are always allowed, applying code diffs
|
|
// always asks, and the agent decides on command / MCP execution
|
|
// (asking only for sensitive actions).
|
|
AgentAutonomy::Partial => OnboardingAutonomyPermissions {
|
|
apply_code_diffs: ActionPermission::AlwaysAsk,
|
|
read_files: ActionPermission::AlwaysAllow,
|
|
execute_commands: ActionPermission::AgentDecides,
|
|
mcp_permissions: ActionPermission::AgentDecides,
|
|
write_to_pty: WriteToPtyPermission::AlwaysAsk,
|
|
},
|
|
AgentAutonomy::None => OnboardingAutonomyPermissions {
|
|
apply_code_diffs: ActionPermission::AlwaysAsk,
|
|
read_files: ActionPermission::AlwaysAsk,
|
|
execute_commands: ActionPermission::AlwaysAsk,
|
|
mcp_permissions: ActionPermission::AlwaysAsk,
|
|
write_to_pty: WriteToPtyPermission::AlwaysAsk,
|
|
},
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "onboarding_tests.rs"]
|
|
mod tests;
|