first pass of merging in warp (doesn't build)

This commit is contained in:
Ryan Ward
2026-07-01 16:08:58 -05:00
parent 2f64909469
commit 4770ac06b5
3662 changed files with 414574 additions and 89772 deletions
+145 -39
View File
@@ -9,26 +9,21 @@ use galaxyui::{AppContext, Entity, EntityId, ModelContext, SingletonEntity};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::ai::llms::LLMId;
use crate::ai::mcp::templatable_manager::TemplatableMCPServerManagerEvent;
use crate::cloud_object::model::persistence::{CloudModelEvent, UpdateSource};
use crate::{send_telemetry_from_ctx, LaunchMode, TelemetryEvent};
use crate::ai::mcp::TemplatableMCPServerManager;
use crate::cloud_object::{GenericStringObjectFormat, JsonObjectType};
use crate::drive::CloudObjectTypeAndId;
use crate::server::cloud_objects::update_manager::UpdateManager;
use crate::server::ids::SyncId;
use crate::settings::AgentModeCommandExecutionPredicate;
use crate::workspaces::user_workspaces::UserWorkspaces;
use crate::CloudModel;
use crate::{
cloud_object::model::generic_string_model::GenericStringObjectId, server::ids::ClientId,
};
use super::{
AIExecutionProfile, ActionPermission, CloudAIExecutionProfileModel, WriteToPtyPermission,
};
use crate::ai::llms::{LLMId, LLMPreferences};
use crate::ai::mcp::templatable_manager::TemplatableMCPServerManagerEvent;
use crate::ai::mcp::TemplatableMCPServerManager;
use crate::cloud_object::model::generic_string_model::GenericStringObjectId;
use crate::cloud_object::model::persistence::{CloudModelEvent, UpdateSource};
use crate::cloud_object::{CloudObject as _, GenericStringObjectFormat, JsonObjectType};
use crate::drive::CloudObjectTypeAndId;
use crate::server::cloud_objects::update_manager::UpdateManager;
use crate::server::ids::{ClientId, SyncId};
use crate::settings::AgentModeCommandExecutionPredicate;
use crate::workspaces::user_workspaces::UserWorkspaces;
use crate::{send_telemetry_from_ctx, CloudModel, LaunchMode, TelemetryEvent};
/// ExecutionProfileId is the identifier that users of the AIExecutionProfilesModel use
/// to refer back to a specific profile. These are unique across the lifespan of the app.
@@ -144,6 +139,7 @@ impl AIExecutionProfilesModel {
let cloud_model = CloudModel::handle(ctx).as_ref(ctx);
let all_profiles_from_cloud: Vec<&super::CloudAIExecutionProfile> = cloud_model
.get_all_objects_of_type::<GenericStringObjectId, CloudAIExecutionProfileModel>()
.filter(|p| Self::is_owned_by_current_user(p, ctx))
.collect();
let default_profile_from_cloud: Option<&super::CloudAIExecutionProfile> = all_profiles_from_cloud
@@ -161,19 +157,25 @@ impl AIExecutionProfilesModel {
}
let default_profile_state = match launch_mode {
LaunchMode::App { .. } | LaunchMode::Test { .. } => match default_profile_from_cloud {
Some(p) => {
let execution_profile_id = ClientProfileId::new();
profile_id_to_sync_id.insert(execution_profile_id, p.id);
DefaultProfileState::Synced {
id: execution_profile_id,
// The TUI front-end is an app-style client, so it shares the
// GUI app's cloud-synced default execution profile.
LaunchMode::App { .. }
| LaunchMode::Test { .. }
| LaunchMode::Tui { .. } => {
match default_profile_from_cloud {
Some(p) => {
let execution_profile_id = ClientProfileId::new();
profile_id_to_sync_id.insert(execution_profile_id, p.id);
DefaultProfileState::Synced {
id: execution_profile_id,
}
}
None => DefaultProfileState::Unsynced {
id: ClientProfileId::new(),
profile: super::create_default_from_legacy_settings(ctx),
},
}
None => DefaultProfileState::Unsynced {
id: ClientProfileId::new(),
profile: AIExecutionProfile::create_default_from_legacy_settings(ctx),
},
},
}
// When running as a CLI, we ignore the GUI default and use a more permissive default.
LaunchMode::CommandLine { is_sandboxed, computer_use_override, .. } => {
DefaultProfileState::Cli {
@@ -181,6 +183,14 @@ impl AIExecutionProfilesModel {
id: ClientProfileId::new()
}
}
// RemoteServerProxy and RemoteServerDaemon don't use AI
// execution profiles. They never reach this code path
// since they don't go through initialize_app, but handle
// exhaustively.
LaunchMode::RemoteServerProxy | LaunchMode::RemoteServerDaemon { .. } => DefaultProfileState::Unsynced {
id: ClientProfileId::new(),
profile: super::create_default_from_legacy_settings(ctx),
},
};
}
}
@@ -190,14 +200,14 @@ impl AIExecutionProfilesModel {
// (2) Let views subscribed to us know whenever a backing profile changes.
// (3) Keep profile_id_to_sync_id map up to date when profiles are created/deleted remotely
if !cfg!(feature = "agent_mode_evals") {
ctx.subscribe_to_model(&CloudModel::handle(ctx), |me, event, ctx| {
ctx.subscribe_to_model(&CloudModel::handle(ctx), |me, _, event, ctx| {
me.handle_cloud_model_event(event, ctx);
});
}
ctx.subscribe_to_model(
&TemplatableMCPServerManager::handle(ctx),
|me, event, ctx| {
|me, _, event, ctx| {
me.handle_templatable_mcp_server_manager_event(event, ctx);
},
);
@@ -211,7 +221,7 @@ impl AIExecutionProfilesModel {
let sync_id_of_default_profile = *profile_id_to_sync_id
.get(id)
.expect("default profile is synced but no sync id found");
ctx.subscribe_to_model(&CloudModel::handle(ctx), move |me, event, _| {
ctx.subscribe_to_model(&CloudModel::handle(ctx), move |me, _, event, _| {
if let CloudModelEvent::ObjectDeleted {
type_and_id: CloudObjectTypeAndId::GenericStringObject {
id: deleted_sync_id,
@@ -240,6 +250,15 @@ impl AIExecutionProfilesModel {
model
}
fn is_owned_by_current_user(
profile: &super::CloudAIExecutionProfile,
ctx: &AppContext,
) -> bool {
UserWorkspaces::as_ref(ctx)
.personal_drive(ctx)
.is_some_and(|owner| profile.permissions().owner == owner)
}
/// This function performs one-time migrations from legacy settings into the default profile.
/// The issue this solves is that, whenever we migrate an existing setting into the profile object,
/// users will initialize the new field to its default value. We need to manually check to see if
@@ -600,6 +619,48 @@ impl AIExecutionProfilesModel {
}
}
pub fn set_context_window_limit(
&mut self,
profile_id: ClientProfileId,
limit: Option<u32>,
ctx: &mut ModelContext<Self>,
) {
let changed = self.edit_profile_internal(
profile_id,
|profile| {
if profile.context_window_limit != limit {
profile.context_window_limit = limit;
return true;
}
false
},
ctx,
);
// Gate on the limit being non-empty. The limit is cleared during
// reconciliation, which runs inside an `LLMPreferences` update where the
// `LLMPreferences::as_ref` read below would panic.
if changed && limit.is_some() {
let Some(profile) = self.get_profile_by_id(profile_id, ctx) else {
return;
};
let llm_preferences = LLMPreferences::as_ref(ctx);
let model_info = profile
.data()
.base_model
.as_ref()
.and_then(|id| llm_preferences.get_llm_info(id))
.unwrap_or_else(|| llm_preferences.get_default_base_model());
send_telemetry_from_ctx!(
TelemetryEvent::AIExecutionProfileContextWindowSelected {
tokens: limit,
model_id: model_info.id.to_string(),
},
ctx
);
}
}
pub fn set_apply_code_diffs(
&mut self,
profile_id: ClientProfileId,
@@ -806,6 +867,39 @@ impl AIExecutionProfilesModel {
}
}
pub fn set_run_agents(
&mut self,
profile_id: ClientProfileId,
permission: super::RunAgentsPermission,
ctx: &mut ModelContext<Self>,
) {
let current_value = self
.get_profile_by_id(profile_id, ctx)
.map(|p| p.data().run_agents);
self.edit_profile_internal(
profile_id,
|profile| {
if profile.run_agents != permission {
profile.run_agents = permission;
return true;
}
false
},
ctx,
);
if current_value != Some(permission) {
send_telemetry_from_ctx!(
TelemetryEvent::AIExecutionProfileSettingUpdated {
setting_type: "run_agents".to_string(),
setting_value: format!("{permission:?}"),
},
ctx
);
}
}
pub fn set_web_search_enabled(
&mut self,
profile_id: ClientProfileId,
@@ -1150,19 +1244,23 @@ impl AIExecutionProfilesModel {
/// `edit_profile_internal` edits an AIExecutionProfile and upserts the changed profile to the cloud
/// Parameters:
/// * `profile_id`: The id of the profile to edit
/// * `edit_fn`: a closure that safely modifies the AIExecutionProfile. It should return `true` if the profile was changed, `false` otherwise. When `true`, it syncs the changes to the cloud, and otherwise exits early to prevent excessive cloud operations if no changes occured.
/// * `edit_fn`: a closure that safely modifies the AIExecutionProfile. It should return `true` if the profile was changed, `false` otherwise. When `true`, it syncs the changes to the cloud, and otherwise exits early to prevent excessive cloud operations if no changes occurred.
/// * `ctx`: The model context
///
/// Returns `true` if the profile was actually changed (and synced),
/// `false` otherwise. Callers can use this to gate side effects such as
/// telemetry on real changes.
fn edit_profile_internal(
&mut self,
profile_id: ClientProfileId,
edit_fn: impl FnOnce(&mut AIExecutionProfile) -> bool,
ctx: &mut ModelContext<Self>,
) {
) -> bool {
// We don't yet support editing the default profile for the CLI.
if let DefaultProfileState::Cli { id, .. } = &self.default_profile_state {
if *id == profile_id {
log::warn!("Attempted to edit CLI default profile, which is not yet supported.");
return;
return false;
}
}
@@ -1174,7 +1272,7 @@ impl AIExecutionProfilesModel {
// If the edit function didn't make any changes to the profile, it's still the default profile, so we don't need to sync it
let value_changed = edit_fn(&mut new_profile);
if !value_changed {
return;
return false;
}
if let Some(owner) = UserWorkspaces::as_ref(ctx).personal_drive(ctx) {
@@ -1215,10 +1313,11 @@ impl AIExecutionProfilesModel {
);
}
ctx.emit(AIExecutionProfilesModelEvent::ProfileUpdated(profile_id));
return;
return true;
}
}
let mut value_changed = false;
if let Some(sync_id) = self.profile_id_to_sync_id.get(&profile_id) {
let cloud_model = CloudModel::as_ref(ctx);
if let Some(object) = cloud_model
@@ -1226,9 +1325,9 @@ impl AIExecutionProfilesModel {
{
let mut data = object.model().string_model.clone();
// If the edit function didn't make any changes to the profile, we should exit early
let value_changed = edit_fn(&mut data);
value_changed = edit_fn(&mut data);
if !value_changed {
return;
return false;
}
let update_manager = UpdateManager::handle(ctx);
update_manager.update(ctx, |update_manager, ctx| {
@@ -1241,6 +1340,7 @@ impl AIExecutionProfilesModel {
}
}
ctx.emit(AIExecutionProfilesModelEvent::ProfileUpdated(profile_id));
value_changed
}
/// Handle CloudModel events to keep the profile_id_to_sync_id map and default profile state up to date.
@@ -1319,6 +1419,7 @@ impl AIExecutionProfilesModel {
let cloud_model = CloudModel::as_ref(ctx);
let all_profiles: Vec<(SyncId, bool)> = cloud_model
.get_all_objects_of_type::<GenericStringObjectId, CloudAIExecutionProfileModel>()
.filter(|o| Self::is_owned_by_current_user(o, ctx))
.map(|o| (o.id, o.model().string_model.is_default_profile))
.collect();
@@ -1335,7 +1436,7 @@ impl AIExecutionProfilesModel {
}
}
// Register any non-default profiles from cloud that we aren't
// Register non-default profiles from cloud that we aren't
// already tracking so later edits find their backing sync_id.
let mut added_non_default = false;
for (sync_id, is_default) in all_profiles {
@@ -1386,6 +1487,11 @@ impl AIExecutionProfilesModel {
return;
};
if !Self::is_owned_by_current_user(object, ctx) {
log::info!("Ignoring non-owned execution profile from cloud: {sync_id:?}");
return;
}
// Check if this is the default profile
if object.model().string_model.is_default_profile {
// Don't add the cloud default profile if we're in CLI mode