Files
galaxy/app/src/terminal/session_settings.rs
T

431 lines
15 KiB
Rust

pub mod new_session_shell;
pub mod startup_shell;
pub mod working_directory_config;
use galaxy_core::settings::macros::define_settings_group;
use galaxy_core::settings::{
ChangeEventReason, RespectUserSyncSetting, Setting, SupportedPlatforms, SyncToCloud,
};
use instant::Duration;
use lazy_static::lazy_static;
pub use new_session_shell::*;
use serde::{Deserialize, Serialize};
pub use startup_shell::*;
pub use working_directory_config::*;
use crate::ai::blocklist::agent_view::toolbar_item::AgentToolbarItemKind;
use crate::context_chips::prompt::PromptSelection;
use crate::context_chips::ContextChipKind;
lazy_static! {
pub static ref DEFAULT_THRESHOLD_FOR_LONG_RUNNING_NOTIFICATION: Duration =
Duration::from_secs(30);
}
#[derive(
Copy,
Clone,
Debug,
Serialize,
Deserialize,
PartialEq,
Eq,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
#[schemars(
description = "Whether the user has enabled or disabled notifications.",
rename_all = "snake_case"
)]
pub enum NotificationsMode {
// User has not been shown notifications banner before or has seen it before but decided not to dismiss it.
#[schemars(description = "Notifications have not been configured yet.")]
Unset,
// User has asked not to be shown notifications banner again.
#[schemars(description = "The notifications banner has been dismissed.")]
Dismissed,
// User has enabled system notifications and wants to receive notifications.
#[schemars(description = "Notifications are enabled.")]
Enabled,
// User had previously enabled notifications, but has now disabled them.
#[schemars(description = "Notifications are disabled.")]
Disabled,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, settings_value::SettingsValue)]
/**
* Added [serde(default)] to ensure that new notification settings are backwards compatible with old clients.
* Otherwise, clients will fail to deserialize existing settings after updating.
*
* @see https://github.com/warpdotdev/warp-internal/pull/14596/files#diff-90221c7ecae01c6faf8f170158dea3e49d34d40225a306da42ccc76489d1f84cR43-R44
*
* Alternative considered: Using Option<bool> fields would have required more
* complex defaulting logic to set the default value to true.
*
*/
#[serde(default)]
#[derive(schemars::JsonSchema)]
#[schemars(description = "Notification preferences for terminal events.")]
pub struct NotificationsSettings {
#[schemars(
description = "Whether notifications are enabled, disabled, or not yet configured."
)]
pub mode: NotificationsMode,
#[schemars(description = "Whether to notify when a long-running command completes.")]
pub is_long_running_enabled: bool,
#[schemars(
with = "u64",
description = "Threshold in seconds for long-running command notifications."
)]
pub long_running_threshold: Duration,
/// Legacy. To be combined with `is_needs_attention_enabled` when desktop notifs are unflagged.
#[schemars(description = "Whether to notify when a password prompt is detected.")]
pub is_password_prompt_enabled: bool,
#[schemars(description = "Whether to notify when an agent task completes.")]
pub is_agent_task_completed_enabled: bool,
#[schemars(description = "Whether to notify when a session needs attention.")]
pub is_needs_attention_enabled: bool,
#[schemars(description = "Whether to play a sound with notifications.")]
pub play_notification_sound: bool,
}
impl Default for NotificationsSettings {
fn default() -> Self {
Self {
mode: NotificationsMode::Unset,
is_long_running_enabled: true,
long_running_threshold: *DEFAULT_THRESHOLD_FOR_LONG_RUNNING_NOTIFICATION,
is_password_prompt_enabled: true,
is_agent_task_completed_enabled: true,
is_needs_attention_enabled: true,
play_notification_sound: true,
}
}
}
#[derive(
Copy,
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
pub enum GithubPrPromptChipDefaultValidation {
#[default]
Unvalidated,
Validated,
Suppressed,
}
impl GithubPrPromptChipDefaultValidation {
pub fn is_suppressed(self) -> bool {
matches!(self, Self::Suppressed)
}
}
/// Shared behavior for toolbar chip selection types.
/// Each variant stores either a `Default` (resolved via type-specific defaults) or `Custom` left/right item lists.
pub trait ToolbarChipSelection {
fn default_left_items() -> Vec<AgentToolbarItemKind>;
fn default_right_items() -> Vec<AgentToolbarItemKind>;
fn left_items(&self) -> Vec<AgentToolbarItemKind>;
fn right_items(&self) -> Vec<AgentToolbarItemKind>;
fn left_chips(&self) -> Vec<ContextChipKind> {
self.left_items()
.into_iter()
.filter_map(|item| match item {
AgentToolbarItemKind::ContextChip(kind) => Some(kind),
_ => None,
})
.collect()
}
fn right_chips(&self) -> Vec<ContextChipKind> {
self.right_items()
.into_iter()
.filter_map(|item| match item {
AgentToolbarItemKind::ContextChip(kind) => Some(kind),
_ => None,
})
.collect()
}
fn all_chips(&self) -> Vec<ContextChipKind> {
let mut chips = self.left_chips();
chips.extend(self.right_chips());
chips
}
fn all_items(&self) -> Vec<AgentToolbarItemKind> {
let mut items = self.left_items();
items.extend(self.right_items());
items
}
}
#[derive(
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
#[schemars(
description = "Agent toolbar layout configuration.",
rename_all = "snake_case"
)]
pub enum AgentToolbarChipSelection {
#[default]
#[schemars(description = "Use the default toolbar layout.")]
Default,
#[schemars(description = "Use a custom arrangement of toolbar items.")]
Custom {
left: Vec<AgentToolbarItemKind>,
right: Vec<AgentToolbarItemKind>,
},
}
impl ToolbarChipSelection for AgentToolbarChipSelection {
fn default_left_items() -> Vec<AgentToolbarItemKind> {
AgentToolbarItemKind::default_left()
}
fn default_right_items() -> Vec<AgentToolbarItemKind> {
AgentToolbarItemKind::default_right()
}
fn left_items(&self) -> Vec<AgentToolbarItemKind> {
match self {
Self::Default => Self::default_left_items(),
Self::Custom { left, .. } => left.clone(),
}
}
fn right_items(&self) -> Vec<AgentToolbarItemKind> {
match self {
Self::Default => Self::default_right_items(),
Self::Custom { right, .. } => right.clone(),
}
}
}
#[derive(
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
schemars::JsonSchema,
settings_value::SettingsValue,
)]
#[schemars(
description = "CLI agent toolbar layout configuration.",
rename_all = "snake_case"
)]
pub enum CLIAgentToolbarChipSelection {
#[default]
#[schemars(description = "Use the default toolbar layout.")]
Default,
#[schemars(description = "Use a custom arrangement of toolbar items.")]
Custom {
left: Vec<AgentToolbarItemKind>,
right: Vec<AgentToolbarItemKind>,
},
}
impl ToolbarChipSelection for CLIAgentToolbarChipSelection {
fn default_left_items() -> Vec<AgentToolbarItemKind> {
AgentToolbarItemKind::cli_default_left()
}
fn default_right_items() -> Vec<AgentToolbarItemKind> {
AgentToolbarItemKind::cli_default_right()
}
fn left_items(&self) -> Vec<AgentToolbarItemKind> {
match self {
Self::Default => Self::default_left_items(),
Self::Custom { left, .. } => left.clone(),
}
}
fn right_items(&self) -> Vec<AgentToolbarItemKind> {
match self {
Self::Default => Self::default_right_items(),
Self::Custom { right, .. } => right.clone(),
}
}
}
define_settings_group!(SessionSettings, settings: [
working_directory_config: WorkingDirectoryConfig,
startup_shell_override: StartupShellOverride {
type: StartupShell,
default: StartupShell::default(),
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "session.startup_shell_override",
description: "The shell to use when Galaxy starts up.",
},
new_session_shell_override: NewSessionShellOverride {
type: Option<NewSessionShell>,
default: None,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "session.new_session_shell_override",
description: "The shell to use when opening a new session.",
}
honor_ps1: HonorPS1 {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "terminal.input.honor_ps1",
description: "Whether to use your shell's PS1 prompt instead of the Galaxy prompt.",
},
saved_prompt: SavedPrompt {
type: PromptSelection,
default: PromptSelection::default(),
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: true,
},
should_add_agent_mode_chip: ShouldAddAgentModeChip {
type: bool,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: true,
},
should_confirm_close_session: ShouldConfirmCloseSession {
type: bool,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "general.should_confirm_close_session",
description: "Whether to show a confirmation dialog when closing a session.",
},
// Value is saved here but not shown in ui (can't be toggled in settings)
should_confirm_shared_session_edit_access: ShouldConfirmSharedSessionEditAccess {
type: bool,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: true,
}
notifications: Notifications {
type: NotificationsSettings,
default: NotificationsSettings::default(),
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "notifications.preferences",
max_table_depth: 1,
description: "Notification preferences for terminal events.",
}
// This is a legacy setting that we no longer allow users to toggle after
// context chips were introduced. We keep it only to respect users who
// had previously disabled the dirty files chip via this setting.
git_prompt_dirty_indicator: LegacyGitPromptDirtyIndicator {
type: bool,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: true,
storage_key: "GitPromptDirtyIndicator",
},
// TODO: Remove this setting when `FeatureFlag::ProfilesDesignRevamp` is cleaned up.
// When ProfilesDesignRevamp is enabled, model selectors are always shown in the prompt.
// This setting only controls visibility when ProfilesDesignRevamp is disabled.
show_model_selectors_in_prompt: ShowModelSelectorsInPrompt {
type: bool,
default: true,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "agents.warp_agent.input.show_model_selectors_in_prompt",
description: "Whether to show AI model selectors in the input prompt.",
},
agent_footer_chip_selection: AgentToolbarChipSelectionSetting {
type: AgentToolbarChipSelection,
default: AgentToolbarChipSelection::default(),
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "agents.warp_agent.input.agent_toolbar_chip_selection_setting",
description: "Controls the layout of context chips in the Agent Mode toolbar.",
},
cli_agent_footer_chip_selection: CLIAgentToolbarChipSelectionSetting {
type: CLIAgentToolbarChipSelection,
default: CLIAgentToolbarChipSelection::default(),
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "agents.third_party.cli_agent_toolbar_chip_selection_setting",
description: "Controls the layout of context chips in the CLI Agent toolbar.",
},
notification_toast_duration_secs: NotificationToastDurationSecs {
type: u64,
default: 8,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: false,
toml_path: "notifications.toast_duration_secs",
description: "How long notification toasts are displayed, in seconds.",
},
// Tracks whether the `gh` CLI is installed and authenticated on this machine.
// Not synced because `gh` CLI availability is machine-specific.
github_pr_chip_default_validation: GithubPrChipDefaultValidation {
type: GithubPrPromptChipDefaultValidation,
default: GithubPrPromptChipDefaultValidation::Unvalidated,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Never,
private: true,
},
// One-time flag: whether we've already migrated the handoff-to-cloud chip
// into a user's custom agent toolbar layout. When `Default`, the chip is
// already present via `AgentToolbarItemKind::default_right()`, so this
// only matters for `Custom` layouts that were saved before the chip existed.
did_add_handoff_chip_to_toolbar: DidAddHandoffChipToToolbar {
type: bool,
default: false,
supported_platforms: SupportedPlatforms::ALL,
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
private: true,
},
]);
settings::macros::implement_setting_for_enum!(
WorkingDirectoryConfig,
SessionSettings,
SupportedPlatforms::ALL,
SyncToCloud::Never,
private: false,
toml_path: "session.working_directory_config",
max_table_depth: 1,
description: "Controls the working directory used when opening new sessions.",
);