first pass of merging in warp (doesn't build)
This commit is contained in:
+373
-339
@@ -6,29 +6,32 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
pub use cloud_object_models::{
|
||||
AgentModeCommandExecutionPredicate, DEFAULT_COMMAND_EXECUTION_ALLOWLIST,
|
||||
DEFAULT_COMMAND_EXECUTION_DENYLIST,
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use regex::Regex;
|
||||
use serde::de::Deserializer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{
|
||||
define_settings_group, RespectUserSyncSetting, Setting, SupportedPlatforms, SyncToCloud,
|
||||
};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
use galaxy_core::execution_mode::AppExecutionMode;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use warpui::platform::keyboard::KeyCode;
|
||||
use warpui::platform::OperatingSystem;
|
||||
use warpui::{AppContext, Entity, ModelContext, SingletonEntity, UpdateModel};
|
||||
|
||||
use crate::ai::request_usage_model::RequestLimitInfo;
|
||||
use crate::auth::AuthStateProvider;
|
||||
use crate::report_if_error;
|
||||
use crate::settings::PrivacySettings;
|
||||
use crate::terminal::CLIAgent;
|
||||
use crate::workspaces::user_workspaces::UserWorkspaces;
|
||||
use cfg_if::cfg_if;
|
||||
use chrono::{DateTime, Utc};
|
||||
use galaxyui::platform::OperatingSystem;
|
||||
use galaxyui::{
|
||||
platform::keyboard::KeyCode, AppContext, Entity, ModelContext, SingletonEntity, UpdateModel,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
|
||||
use galaxy_core::execution_mode::AppExecutionMode;
|
||||
use galaxy_core::features::FeatureFlag;
|
||||
use settings::{define_settings_group, Setting, SupportedPlatforms, SyncToCloud};
|
||||
|
||||
use serde::{de::Deserializer, Deserialize, Serialize};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
pub enum FocusedTerminalInfoEvent {
|
||||
TerminalInfoUpdated,
|
||||
@@ -392,7 +395,7 @@ impl ThinkingDisplayMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Authentication method for AWS Bedrock.
|
||||
/// Controls how child-agent message bodies are displayed.
|
||||
#[derive(
|
||||
Default,
|
||||
Debug,
|
||||
@@ -406,107 +409,193 @@ impl ThinkingDisplayMode {
|
||||
settings_value::SettingsValue,
|
||||
)]
|
||||
#[schemars(
|
||||
description = "Authentication method for AWS Bedrock.",
|
||||
description = "Controls how child-agent messages are displayed.",
|
||||
rename_all = "snake_case"
|
||||
)]
|
||||
pub enum BedrockAuthMethod {
|
||||
pub enum OrchestrationMessageDisplayMode {
|
||||
/// Show child-agent messages while streaming, then collapse them.
|
||||
ShowAndCollapse,
|
||||
/// Keep child-agent message bodies expanded.
|
||||
AlwaysShow,
|
||||
/// Keep child-agent message bodies collapsed.
|
||||
#[default]
|
||||
Profile,
|
||||
StaticKeys,
|
||||
Sso,
|
||||
AlwaysCollapse,
|
||||
}
|
||||
|
||||
settings::macros::implement_setting_for_enum!(
|
||||
BedrockAuthMethod,
|
||||
OrchestrationMessageDisplayMode,
|
||||
AISettings,
|
||||
SupportedPlatforms::DESKTOP,
|
||||
SyncToCloud::Never,
|
||||
SupportedPlatforms::ALL,
|
||||
SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "ai.bedrock.auth_method",
|
||||
description: "Authentication method for AWS Bedrock.",
|
||||
toml_path: "agents.warp_agent.other.orchestration_message_display_mode",
|
||||
description: "Controls how child-agent messages are displayed.",
|
||||
);
|
||||
|
||||
impl BedrockAuthMethod {
|
||||
impl OrchestrationMessageDisplayMode {
|
||||
/// Display name for the settings dropdown.
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
BedrockAuthMethod::Profile => "AWS Profile",
|
||||
BedrockAuthMethod::StaticKeys => "Static Keys",
|
||||
BedrockAuthMethod::Sso => "SSO",
|
||||
OrchestrationMessageDisplayMode::ShowAndCollapse => "Show & collapse",
|
||||
OrchestrationMessageDisplayMode::AlwaysShow => "Always show",
|
||||
OrchestrationMessageDisplayMode::AlwaysCollapse => "Always collapse",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command_palette_description(&self) -> &'static str {
|
||||
match self {
|
||||
OrchestrationMessageDisplayMode::ShowAndCollapse => {
|
||||
"Set child-agent message display: show & collapse"
|
||||
}
|
||||
OrchestrationMessageDisplayMode::AlwaysShow => {
|
||||
"Set child-agent message display: always show"
|
||||
}
|
||||
OrchestrationMessageDisplayMode::AlwaysCollapse => {
|
||||
"Set child-agent message display: always collapse"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether child-agent message bodies should expand while streaming.
|
||||
pub fn should_expand_agent_message_body(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
OrchestrationMessageDisplayMode::ShowAndCollapse
|
||||
| OrchestrationMessageDisplayMode::AlwaysShow
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether child-agent message bodies should collapse after streaming.
|
||||
pub fn should_collapse_agent_message_body_on_finish(&self) -> bool {
|
||||
matches!(self, OrchestrationMessageDisplayMode::ShowAndCollapse)
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls what happens when a user submits a new prompt while the agent is
|
||||
/// still responding to an earlier prompt.
|
||||
///
|
||||
/// This is the *default* used when a conversation has no explicit auto-queue
|
||||
/// override. Per-conversation overrides live on `QueuedQueryModel` and take
|
||||
/// precedence over this setting.
|
||||
#[derive(
|
||||
Default,
|
||||
Debug,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
PartialEq,
|
||||
Copy,
|
||||
Clone,
|
||||
EnumIter,
|
||||
schemars::JsonSchema,
|
||||
settings_value::SettingsValue,
|
||||
)]
|
||||
#[schemars(
|
||||
description = "Default behavior when submitting a new prompt while the agent is still responding.",
|
||||
rename_all = "snake_case"
|
||||
)]
|
||||
pub enum PromptSubmissionMode {
|
||||
/// Cancel the in-flight response and submit the new prompt immediately
|
||||
/// (default).
|
||||
#[default]
|
||||
Interrupt,
|
||||
/// Hold the new prompt until the in-flight response finishes, then submit.
|
||||
Queue,
|
||||
}
|
||||
|
||||
settings::macros::implement_setting_for_enum!(
|
||||
PromptSubmissionMode,
|
||||
AISettings,
|
||||
SupportedPlatforms::ALL,
|
||||
SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "agents.warp_agent.other.default_prompt_submission_mode",
|
||||
description: "Default behavior when submitting a new prompt while the agent is still responding.",
|
||||
feature_flag: FeatureFlag::QueueSlashCommand,
|
||||
);
|
||||
|
||||
impl PromptSubmissionMode {
|
||||
/// Display name for the settings dropdown.
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
PromptSubmissionMode::Interrupt => "Interrupt response",
|
||||
PromptSubmissionMode::Queue => "Queue until response finishes",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command_palette_description(&self) -> &'static str {
|
||||
match self {
|
||||
PromptSubmissionMode::Interrupt => "Set default prompt submission: interrupt response",
|
||||
PromptSubmissionMode::Queue => {
|
||||
"Set default prompt submission: queue until response finishes"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for a single Bedrock model.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
|
||||
#[schemars(description = "Configuration for a single AWS Bedrock model.")]
|
||||
pub struct BedrockModelConfig {
|
||||
#[schemars(
|
||||
description = "The Bedrock model ID (e.g. anthropic.claude-sonnet-4-20250514-v1:0)."
|
||||
)]
|
||||
pub model_id: String,
|
||||
#[schemars(description = "Display name shown in the model picker.")]
|
||||
pub display_name: String,
|
||||
#[serde(default)]
|
||||
#[schemars(description = "Whether the model supports image/vision input.")]
|
||||
pub vision_supported: bool,
|
||||
#[serde(default = "default_context_size")]
|
||||
#[schemars(description = "Maximum context window size in tokens.")]
|
||||
pub context_size: u32,
|
||||
}
|
||||
|
||||
fn default_context_size() -> u32 {
|
||||
200_000
|
||||
}
|
||||
|
||||
impl settings_value::SettingsValue for BedrockModelConfig {}
|
||||
|
||||
/// Configuration for a single OpenAI-compatible (LiteLLM) model.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
|
||||
#[schemars(description = "Configuration for a single OpenAI-compatible model (e.g. via LiteLLM).")]
|
||||
pub struct OpenAIModelConfig {
|
||||
#[schemars(
|
||||
description = "The model ID to send in the API request (e.g. claude-sonnet-4-20250514)."
|
||||
)]
|
||||
pub model_id: String,
|
||||
#[schemars(description = "Display name shown in the model picker.")]
|
||||
pub display_name: String,
|
||||
#[serde(default)]
|
||||
#[schemars(description = "Whether the model supports image/vision input.")]
|
||||
pub vision_supported: bool,
|
||||
#[serde(default = "default_context_size")]
|
||||
#[schemars(description = "Maximum context window size in tokens.")]
|
||||
pub context_size: u32,
|
||||
#[serde(default)]
|
||||
#[schemars(
|
||||
description = "Optional provider hint (e.g. anthropic, openai, google) for icon display."
|
||||
)]
|
||||
pub provider: Option<String>,
|
||||
}
|
||||
|
||||
impl settings_value::SettingsValue for OpenAIModelConfig {}
|
||||
|
||||
/// Configuration for a single OpenAI-compatible provider endpoint.
|
||||
/// What happens when a prompt is submitted while an agent controls an agent-requested
|
||||
/// long-running command (LRC).
|
||||
///
|
||||
/// Multiple providers can be configured simultaneously (e.g. LiteLLM for cloud models,
|
||||
/// Ollama for local models, etc.). Each provider has its own endpoint, credentials, and model list.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, schemars::JsonSchema)]
|
||||
#[schemars(
|
||||
description = "Configuration for an OpenAI-compatible provider endpoint (e.g. LiteLLM, Ollama, vLLM)."
|
||||
/// Only consulted when [`PromptSubmissionMode`] is `Interrupt`: in `Queue` mode
|
||||
/// prompts always queue until the full response finishes, so this setting is
|
||||
/// hidden and ignored.
|
||||
#[derive(
|
||||
Default,
|
||||
Debug,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
PartialEq,
|
||||
Copy,
|
||||
Clone,
|
||||
EnumIter,
|
||||
schemars::JsonSchema,
|
||||
settings_value::SettingsValue,
|
||||
)]
|
||||
pub struct OpenAIProviderConfig {
|
||||
#[schemars(description = "Display name for this provider (shown in model picker).")]
|
||||
pub name: String,
|
||||
#[schemars(description = "Base URL for the OpenAI-compatible API endpoint.")]
|
||||
pub base_url: String,
|
||||
#[serde(default)]
|
||||
#[schemars(description = "API key for this endpoint (optional if the proxy handles auth).")]
|
||||
pub api_key: Option<String>,
|
||||
#[serde(default)]
|
||||
#[schemars(description = "Models available from this provider.")]
|
||||
pub models: Vec<OpenAIModelConfig>,
|
||||
#[schemars(
|
||||
description = "What happens when a prompt is submitted while an agent controls an agent-requested long-running command.",
|
||||
rename_all = "snake_case"
|
||||
)]
|
||||
pub enum LongRunningCommandSubmissionMode {
|
||||
/// Send the prompt to the agent immediately, steering it mid-command.
|
||||
SendImmediately,
|
||||
/// Queue the prompt and send it to the agent when the command finishes
|
||||
/// (default).
|
||||
#[default]
|
||||
QueueUntilCommandCompletes,
|
||||
}
|
||||
|
||||
impl settings_value::SettingsValue for OpenAIProviderConfig {}
|
||||
settings::macros::implement_setting_for_enum!(
|
||||
LongRunningCommandSubmissionMode,
|
||||
AISettings,
|
||||
SupportedPlatforms::ALL,
|
||||
SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "agents.warp_agent.other.long_running_command_submission_mode",
|
||||
description: "What happens when a prompt is submitted while an agent controls an agent-requested long-running command.",
|
||||
feature_flag: FeatureFlag::QueueSlashCommand,
|
||||
);
|
||||
|
||||
impl LongRunningCommandSubmissionMode {
|
||||
/// Display name for the settings dropdown.
|
||||
pub fn display_name(&self) -> &'static str {
|
||||
match self {
|
||||
LongRunningCommandSubmissionMode::SendImmediately => "Send immediately",
|
||||
LongRunningCommandSubmissionMode::QueueUntilCommandCompletes => {
|
||||
"Queue until command finishes"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command_palette_description(&self) -> &'static str {
|
||||
match self {
|
||||
LongRunningCommandSubmissionMode::SendImmediately => {
|
||||
"Set long-running command submission: send immediately"
|
||||
}
|
||||
LongRunningCommandSubmissionMode::QueueUntilCommandCompletes => {
|
||||
"Set long-running command submission: queue until command finishes"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks the state of the quota reset banner
|
||||
#[derive(
|
||||
@@ -598,148 +687,6 @@ pub enum AgentModeCodingPermissionsType {
|
||||
AllowReadingSpecificFiles,
|
||||
}
|
||||
|
||||
/// Predicate types to match commands that can be executed by Agent Mode.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
enum AgentModeCommandExecutionPredicateType {
|
||||
/// A regex with start (`^`) and end (`$`) anchors.
|
||||
///
|
||||
/// We want regex rules to apply to the entire cmd string so we anchor them
|
||||
/// (there isn't any efficient way to apply to the entire cmd string at match-time).
|
||||
#[serde(with = "serde_regex")]
|
||||
AnchoredRegex(Regex),
|
||||
}
|
||||
|
||||
impl AgentModeCommandExecutionPredicateType {
|
||||
fn new_regex(regex: &str) -> Result<Self, regex::Error> {
|
||||
// Redundant anchors aren't a problem so we can unconditionally add them.
|
||||
let anchored_regex = Regex::new(&format!("^{regex}$"))?;
|
||||
Ok(Self::AnchoredRegex(anchored_regex))
|
||||
}
|
||||
|
||||
fn matches(&self, cmd: &str) -> bool {
|
||||
match self {
|
||||
Self::AnchoredRegex(regex) => regex.is_match(cmd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for AgentModeCommandExecutionPredicateType {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::AnchoredRegex(a), Self::AnchoredRegex(b)) => {
|
||||
// Indexing should be safe since they're guaranteed to have at least
|
||||
// the anchors around them.
|
||||
let a_unanchored = &a.as_str()[1..a.as_str().len() - 1];
|
||||
let b_unanchored = &b.as_str()[1..b.as_str().len() - 1];
|
||||
a_unanchored == b_unanchored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AgentModeCommandExecutionPredicateType {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::AnchoredRegex(regex) => {
|
||||
write!(f, "{}", ®ex.as_str()[1..regex.as_str().len() - 1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around [`AgentModeCommandExecutionPredicateType`] to enforce
|
||||
/// the use of the provided constructors rather than direct construction of the variants.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||
#[serde(transparent)]
|
||||
pub struct AgentModeCommandExecutionPredicate(AgentModeCommandExecutionPredicateType);
|
||||
|
||||
impl schemars::JsonSchema for AgentModeCommandExecutionPredicate {
|
||||
fn schema_name() -> std::borrow::Cow<'static, str> {
|
||||
std::borrow::Cow::Borrowed("AgentModeCommandExecutionPredicate")
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::SchemaGenerator) -> schemars::Schema {
|
||||
// In the settings file, predicates are serialized as plain regex strings.
|
||||
gen.subschema_for::<String>()
|
||||
}
|
||||
}
|
||||
|
||||
impl AgentModeCommandExecutionPredicate {
|
||||
pub fn new_regex(regex: &str) -> Result<Self, regex::Error> {
|
||||
Ok(Self(AgentModeCommandExecutionPredicateType::new_regex(
|
||||
regex,
|
||||
)?))
|
||||
}
|
||||
|
||||
pub fn matches(&self, cmd: &str) -> bool {
|
||||
self.0.matches(cmd)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for AgentModeCommandExecutionPredicate {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl settings_value::SettingsValue for AgentModeCommandExecutionPredicate {
|
||||
fn to_file_value(&self) -> serde_json::Value {
|
||||
serde_json::Value::String(self.to_string())
|
||||
}
|
||||
|
||||
fn from_file_value(value: &serde_json::Value) -> Option<Self> {
|
||||
value.as_str().and_then(|s| Self::new_regex(s).ok())
|
||||
}
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
// Matches optional args / options for a top-level command.
|
||||
static ref OPTIONAL_ARGS_REGEX: Regex = Regex::new(r"(\s.*)?").expect("Can parse optional args regex");
|
||||
}
|
||||
|
||||
cfg_if! {
|
||||
// Compiling the regexes for the default command execution allowlist/denylist can be slow
|
||||
// in an unoptimized build, so we use empty lists in unit tests.
|
||||
if #[cfg(test)] {
|
||||
lazy_static! {
|
||||
pub static ref DEFAULT_COMMAND_EXECUTION_ALLOWLIST: Vec<AgentModeCommandExecutionPredicate> = vec![];
|
||||
pub static ref DEFAULT_COMMAND_EXECUTION_DENYLIST: Vec<AgentModeCommandExecutionPredicate> = vec![];
|
||||
}
|
||||
} else {
|
||||
lazy_static! {
|
||||
pub static ref DEFAULT_COMMAND_EXECUTION_ALLOWLIST: Vec<AgentModeCommandExecutionPredicate> = vec![
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("cat{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default cat rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("echo{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default echo rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex("find .*").expect("Can parse default find rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("grep{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default grep rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("ls{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default ls rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex("which .*").expect("Can parse default which rule into regex"),
|
||||
];
|
||||
|
||||
pub static ref DEFAULT_COMMAND_EXECUTION_DENYLIST: Vec<AgentModeCommandExecutionPredicate> = vec![
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("bash{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default bash rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("fish{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default fish rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("pwsh{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default pwsh rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("sh{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default sh rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("zsh{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default zsh rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("curl{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default curl rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("eval{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default eval rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("exec{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default exec rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("source{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default source rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("wget{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default wget rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("dig{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default dig rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("nslookup{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default nslookup rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("host{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default host rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("ssh{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default ssh rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("scp{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default scp rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("rsync{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default rsync rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("telnet{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default telnet rule into regex"),
|
||||
AgentModeCommandExecutionPredicate::new_regex(&format!("rm{}", OPTIONAL_ARGS_REGEX.as_str())).expect("Can parse default rm rule into regex"),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps custom toolbar command regex patterns to CLI agent names.
|
||||
/// Keys are regex patterns (insertion-ordered), values are serialized CLIAgent names (e.g. "Claude").
|
||||
/// An empty string value means "Any CLI Agent" (CLIAgent::Unknown).
|
||||
@@ -1133,8 +1080,21 @@ define_settings_group!(AISettings, settings: [
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: true,
|
||||
}
|
||||
// Whether direct Bedrock integration is enabled (client calls Bedrock API directly).
|
||||
bedrock_enabled: BedrockEnabled {
|
||||
// Whether or not we should show the one-shot speedbump on Ask-User-Question cards.
|
||||
//
|
||||
// Not a user-visible setting - we model it as a setting so we can track state.
|
||||
// Intentionally NOT cloud-synced: we want users to see the first-time nudge on
|
||||
// each fresh device, and we avoid a cloud-sync race that would make the flag
|
||||
// silently stay `false` on new devices after being consumed once elsewhere.
|
||||
should_show_agent_mode_ask_user_question_speedbump: ShouldShowAgentModeAskUserQuestionSpeedbump {
|
||||
type: bool,
|
||||
default: true,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: true,
|
||||
}
|
||||
// Whether to use locally loaded AWS credentials for Bedrock-enabled requests.
|
||||
aws_bedrock_credentials_enabled: AwsBedrockCredentialsEnabled {
|
||||
type: bool,
|
||||
default: true,
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
@@ -1229,70 +1189,19 @@ define_settings_group!(AISettings, settings: [
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: true,
|
||||
}
|
||||
// Whether the OpenAI-compatible (LiteLLM) provider is enabled.
|
||||
openai_enabled: OpenAIEnabled {
|
||||
// Whether to mint and attach Gemini Enterprise (GEAP) credentials to eligible agent
|
||||
// requests, routing them through the workspace's Google Cloud project. Only consulted
|
||||
// when the admin sets the GEAP host to RESPECT_USER_SETTING; ENFORCE bypasses it.
|
||||
// Prefer [`UserWorkspaces::is_gemini_enterprise_credentials_enabled`] to interpret
|
||||
// this setting.
|
||||
gemini_enterprise_credentials_enabled: GeminiEnterpriseCredentialsEnabled {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "ai.openai.enabled",
|
||||
description: "Whether to use an OpenAI-compatible endpoint (e.g. LiteLLM) for AI requests.",
|
||||
}
|
||||
// Base URL for the OpenAI-compatible API endpoint.
|
||||
// Never synced — machine-local infrastructure (e.g. localhost).
|
||||
openai_base_url: OpenAIBaseUrl {
|
||||
type: String,
|
||||
default: "http://localhost:4000/v1".to_string(),
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: false,
|
||||
toml_path: "ai.openai.base_url",
|
||||
description: "Base URL for the OpenAI-compatible API endpoint (e.g. LiteLLM proxy).",
|
||||
}
|
||||
// API key for the OpenAI-compatible endpoint (optional if proxy handles auth).
|
||||
openai_api_key: OpenAIApiKey {
|
||||
type: String,
|
||||
default: String::new(),
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: false,
|
||||
toml_path: "ai.openai.api_key",
|
||||
description: "API key for the OpenAI-compatible endpoint (optional if proxy handles auth).",
|
||||
}
|
||||
// Model name to send to the OpenAI-compatible endpoint. Empty = use selected model ID.
|
||||
// Never synced — tied to the specific endpoint configuration.
|
||||
openai_model: OpenAIModel {
|
||||
type: String,
|
||||
default: String::new(),
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: false,
|
||||
toml_path: "ai.openai.model",
|
||||
description: "Model name to send to the OpenAI-compatible endpoint. Leave empty to use the selected model ID.",
|
||||
}
|
||||
// Custom OpenAI-compatible model configurations (fetched from LiteLLM or manually configured).
|
||||
// Never synced to cloud — these are machine-local (tied to local endpoints).
|
||||
openai_models: OpenAIModels {
|
||||
type: Vec<OpenAIModelConfig>,
|
||||
default: Vec::new(),
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: false,
|
||||
toml_path: "ai.openai.models",
|
||||
description: "Custom OpenAI-compatible model configurations (e.g. from LiteLLM).",
|
||||
}
|
||||
// Multiple OpenAI-compatible provider endpoints (LiteLLM, Ollama, vLLM, etc.).
|
||||
// Each provider has its own name, base_url, api_key, and model list.
|
||||
// Never synced to cloud — these are machine-local infrastructure configs.
|
||||
openai_providers: OpenAIProviders {
|
||||
type: Vec<OpenAIProviderConfig>,
|
||||
default: Vec::new(),
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: false,
|
||||
toml_path: "ai.providers",
|
||||
description: "Multiple OpenAI-compatible provider endpoints (e.g. LiteLLM, Ollama, local models).",
|
||||
toml_path: "cloud_platform.third_party_api_keys.gemini_enterprise_credentials_enabled",
|
||||
description: "Whether Warp should route eligible requests through your workspace's Gemini Enterprise Google Cloud project.",
|
||||
}
|
||||
// Whether or not the user wants agent mode requests to use their saved rules.
|
||||
memory_enabled: MemoryEnabled {
|
||||
@@ -1397,6 +1306,42 @@ define_settings_group!(AISettings, settings: [
|
||||
// We model it as a setting so it's only shown once to a given user regardless of the number of
|
||||
// devices they use.
|
||||
did_check_to_trigger_oz_launch_modal: DidShowOzLaunchModal {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
|
||||
private: true,
|
||||
}
|
||||
|
||||
// This is not a user-visible setting - it's merely a one-time flag to track if the
|
||||
// orchestration launch modal has been shown to the user.
|
||||
//
|
||||
// We model it as a setting so it's only shown once to a given user regardless of the number of
|
||||
// devices they use.
|
||||
did_check_to_trigger_orchestration_launch_modal: DidShowOrchestrationLaunchModal {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
|
||||
private: true,
|
||||
}
|
||||
|
||||
// This is not a user-visible setting - it's merely a one-time flag to track if the
|
||||
// free-AI-removal notice modal has been shown to (or silently marked as seen for) the user.
|
||||
//
|
||||
// We model it as a setting so it's only shown once to a given user regardless of the number of
|
||||
// devices they use.
|
||||
did_check_to_trigger_free_ai_removal_modal: DidShowFreeAiRemovalModal {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
|
||||
private: true,
|
||||
}
|
||||
|
||||
// Used to determine whether the "What's new in Oz" section of the agent view
|
||||
// zero state is expanded or collapsed by default.
|
||||
should_expand_oz_updates: ShouldExpandOzUpdates {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
@@ -1406,16 +1351,16 @@ define_settings_group!(AISettings, settings: [
|
||||
|
||||
|
||||
|
||||
// Whether or not the user has enabled the ability to use Warp credits even when providing
|
||||
// their own LLM provider API key.
|
||||
can_use_warp_credits_with_byok: CanUseWarpCreditsWithByok {
|
||||
// Whether or not the user has enabled fallback to Warp credits for user-provided models.
|
||||
can_use_warp_credits_for_fallback: CanUseWarpCreditsForFallback {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: false,
|
||||
storage_key: "CanUseWarpCreditsWithByok",
|
||||
toml_path: "cloud_platform.third_party_api_keys.can_use_warp_credits_with_byok",
|
||||
description: "Whether Galaxy credits can be used even when providing your own API key.",
|
||||
description: "Whether Warp credits can be used as a fallback for user-provided models.",
|
||||
}
|
||||
|
||||
should_render_use_agent_footer_for_user_commands: ShouldRenderUseAgentToolbarForUserCommands {
|
||||
@@ -1478,6 +1423,18 @@ define_settings_group!(AISettings, settings: [
|
||||
description: "Whether CLI agent Rich Input automatically closes after the user submits a prompt.",
|
||||
}
|
||||
|
||||
// When enabled, the Rich Input editor submits on Ctrl+Enter instead of Enter.
|
||||
// Enter inserts a newline; Ctrl+Enter submits.
|
||||
submit_on_ctrl_enter: SubmitRichInputOnCtrlEnter {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "agents.third_party.submit_on_ctrl_enter",
|
||||
description: "When enabled, the Rich Input editor submits on Ctrl+Enter instead of Enter. Enter inserts a newline.",
|
||||
}
|
||||
|
||||
// Maps custom toolbar command regex patterns to specific CLI agents.
|
||||
// Keys are regex patterns matched against the full command string.
|
||||
// Values are serialized CLIAgent names (empty string = any agent).
|
||||
@@ -1507,31 +1464,6 @@ define_settings_group!(AISettings, settings: [
|
||||
private: true,
|
||||
}
|
||||
|
||||
// This is not a user-visible setting - it tracks whether the FTU model picker callout
|
||||
// has been shown to the user. We set this to `true` as soon as the callout is first
|
||||
// displayed (not when it's dismissed), so it never re-appears.
|
||||
//
|
||||
// Note: this setting was originally named "dismissed" but we now use it to mean "shown".
|
||||
// We kept the same setting key so that users who already dismissed the callout on an
|
||||
// older client don't see it again.
|
||||
ftu_model_callout_dismissed: FtuModelCalloutDismissed {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: true,
|
||||
}
|
||||
|
||||
// Tracks whether we've done the one-time auto-open of the conversation list for discoverability.
|
||||
// Once set to true, the conversation list visibility will be restored from workspace state.
|
||||
has_auto_opened_conversation_list: HasAutoOpenedConversationList {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: true,
|
||||
}
|
||||
|
||||
// Whether the ambient agent trial widget has been dismissed by the user.
|
||||
//
|
||||
// Not a user-visible setting - we model it as a setting so we can track state.
|
||||
@@ -1559,6 +1491,18 @@ define_settings_group!(AISettings, settings: [
|
||||
toml_path: "general.default_tab_config_path",
|
||||
}
|
||||
|
||||
// Whether computer use is enabled for cloud agent conversations started from the Warp app.
|
||||
// This setting is only used when the AI autonomy setting is AlwaysAsk or not set.
|
||||
cloud_agent_computer_use_enabled: CloudAgentComputerUseEnabled {
|
||||
type: bool,
|
||||
default: galaxy_core::channel::ChannelState::channel().is_dogfood(),
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "agents.warp_agent.other.cloud_agent_computer_use_enabled",
|
||||
description: "Whether computer use is enabled for cloud agent conversations.",
|
||||
}
|
||||
|
||||
|
||||
// Whether file-based MCP servers from third-party AI tools (e.g. Claude, Codex) should
|
||||
// be automatically detected and spawned. Warp-native config files (.warp-core/.mcp.json) are
|
||||
@@ -1576,6 +1520,19 @@ define_settings_group!(AISettings, settings: [
|
||||
// Controls how agent thinking/reasoning traces are displayed.
|
||||
thinking_display_mode: ThinkingDisplayMode,
|
||||
|
||||
// Controls how orchestration message bodies are expanded by default.
|
||||
orchestration_message_display_mode: OrchestrationMessageDisplayMode,
|
||||
|
||||
// Default behavior when the user submits a new prompt while the agent is still
|
||||
// responding. Per-conversation overrides live on `QueuedQueryModel`; this
|
||||
// setting is the fallback used when a conversation has no explicit override.
|
||||
default_prompt_submission_mode: PromptSubmissionMode,
|
||||
|
||||
// What happens when a prompt is submitted while an agent controls an agent-requested
|
||||
// long-running command. Only consulted when `default_prompt_submission_mode` is `Interrupt`;
|
||||
// per-LRC manual overrides live on `QueuedQueryModel`.
|
||||
long_running_command_submission_mode: LongRunningCommandSubmissionMode,
|
||||
|
||||
// Whether agent-executed shell commands should be included in command history
|
||||
// (up-arrow, Ctrl-R search, inline history menu).
|
||||
// When false, commands run by the AI agent are excluded from history.
|
||||
@@ -1659,6 +1616,49 @@ define_settings_group!(AISettings, settings: [
|
||||
sync_to_cloud: SyncToCloud::Never,
|
||||
private: true,
|
||||
}
|
||||
|
||||
should_force_disable_cloud_handoff: ShouldForceDisableCloudHandoff {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "agents.warp_agent.other.should_force_disable_cloud_handoff",
|
||||
description: "Whether to force-disable local-to-cloud handoff.",
|
||||
}
|
||||
|
||||
should_force_disable_ampersand_handoff: ShouldForceDisableAmpersandHandoff {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::DESKTOP,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "agents.warp_agent.other.should_force_disable_ampersand_handoff",
|
||||
description: "Whether to force-disable the & prefix for cloud handoff compose mode.",
|
||||
}
|
||||
|
||||
auto_handoff_on_sleep_enabled: AutoHandoffOnSleepEnabled {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::MAC,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "agents.warp_agent.other.auto_handoff_on_sleep_enabled",
|
||||
description: "Whether Warp automatically hands off local agent conversations to cloud when the computer is about to sleep.",
|
||||
}
|
||||
|
||||
// This is not a user-visible setting - it's merely a one-time flag to track if the
|
||||
// auto-handoff sleep modal has been shown to the user.
|
||||
//
|
||||
// We model it as a setting so it's only shown once to a given user regardless of the number of
|
||||
// devices they use.
|
||||
did_show_auto_handoff_sleep_modal: DidShowAutoHandoffSleepModal {
|
||||
type: bool,
|
||||
default: false,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::No),
|
||||
private: true,
|
||||
}
|
||||
]);
|
||||
|
||||
impl AISettings {
|
||||
@@ -1668,7 +1668,7 @@ impl AISettings {
|
||||
CompiledCommandsForCodingAgentToolbar::register(app);
|
||||
|
||||
app.update_model(&Self::handle(app), |_me, ctx| {
|
||||
ctx.subscribe_to_model(&FocusedTerminalInfo::handle(ctx), |_me, event, ctx| {
|
||||
ctx.subscribe_to_model(&FocusedTerminalInfo::handle(ctx), |_me, _, event, ctx| {
|
||||
if matches!(event, FocusedTerminalInfoEvent::TerminalInfoUpdated) {
|
||||
// Pipe the event so that any view that listens for settings changes will be notified.
|
||||
ctx.emit(AISettingsChangedEvent::IsAnyAIEnabled {
|
||||
@@ -1839,8 +1839,42 @@ impl AISettings {
|
||||
*self.file_based_mcp_enabled
|
||||
}
|
||||
|
||||
pub fn is_orchestration_enabled(&self, _app: &galaxyui::AppContext) -> bool {
|
||||
false
|
||||
pub fn is_orchestration_enabled(&self, app: &galaxyui::AppContext) -> bool {
|
||||
self.is_any_ai_enabled(app)
|
||||
}
|
||||
|
||||
/// Returns true when local-to-cloud handoff is effectively enabled.
|
||||
/// False when the user/org has disabled it, cloud conversations are off,
|
||||
/// or AI is globally off.
|
||||
pub fn is_cloud_handoff_enabled(&self, app: &galaxyui::AppContext) -> bool {
|
||||
if !self.is_any_ai_enabled(app) || *self.should_force_disable_cloud_handoff {
|
||||
return false;
|
||||
}
|
||||
if !FeatureFlag::OzHandoff.is_enabled()
|
||||
|| !FeatureFlag::HandoffLocalCloud.is_enabled()
|
||||
|| !cfg!(all(feature = "local_fs", not(target_family = "wasm")))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
let privacy = PrivacySettings::as_ref(app);
|
||||
if !privacy.is_cloud_conversation_storage_enabled {
|
||||
return false;
|
||||
}
|
||||
!matches!(
|
||||
UserWorkspaces::as_ref(app).get_cloud_conversation_storage_enablement_setting(),
|
||||
crate::workspaces::workspace::AdminEnablementSetting::Disable
|
||||
)
|
||||
}
|
||||
pub fn is_ampersand_handoff_enabled(&self, app: &galaxyui::AppContext) -> bool {
|
||||
self.is_cloud_handoff_enabled(app) && !*self.should_force_disable_ampersand_handoff
|
||||
}
|
||||
|
||||
pub fn is_auto_handoff_on_sleep_enabled(&self, app: &galaxyui::AppContext) -> bool {
|
||||
self.is_cloud_handoff_enabled(app)
|
||||
&& self
|
||||
.auto_handoff_on_sleep_enabled
|
||||
.is_supported_on_current_platform()
|
||||
&& *self.auto_handoff_on_sleep_enabled
|
||||
}
|
||||
|
||||
/// Determines whether a quota reset banner should be displayed to the user.
|
||||
@@ -1929,11 +1963,7 @@ impl AISettings {
|
||||
}
|
||||
|
||||
pub fn is_command_denylist_editable(&self, app: &AppContext) -> bool {
|
||||
let set_by_workspace = UserWorkspaces::as_ref(app)
|
||||
.ai_autonomy_settings()
|
||||
.has_override_for_execute_commands_denylist();
|
||||
|
||||
self.is_any_ai_enabled(app) && !set_by_workspace
|
||||
self.is_any_ai_enabled(app)
|
||||
}
|
||||
|
||||
pub fn is_command_allowlist_editable(&self, app: &AppContext) -> bool {
|
||||
@@ -1999,6 +2029,10 @@ impl AISettings {
|
||||
self.is_any_ai_enabled(app)
|
||||
}
|
||||
|
||||
pub fn is_run_agents_permissions_editable(&self, app: &AppContext) -> bool {
|
||||
self.is_orchestration_enabled(app)
|
||||
}
|
||||
|
||||
pub fn show_code_suggestion_speedbump(&self, app: &AppContext) -> bool {
|
||||
self.is_any_ai_enabled(app) && *self.show_code_suggestion_speedbump
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user