Simplify Galaxy permissions and preserve explicit user authorization

This commit is contained in:
2026-10-02 01:58:37 -05:00
parent 5a70995502
commit 2796fdd114
37 changed files with 1818 additions and 3530 deletions
+49 -734
View File
@@ -1,5 +1,3 @@
use std::path::{Path, PathBuf};
use ai::api_keys::{ApiKeyManager, ApiKeyManagerEvent};
use galaxy_core::ui::theme::color::internal_colors;
use itertools::Itertools;
@@ -25,13 +23,12 @@ use crate::ai::execution_profiles::profiles::{
AIExecutionProfilesModel, AIExecutionProfilesModelEvent, ClientProfileId,
};
use crate::ai::execution_profiles::{
AIExecutionProfile, AIExecutionProfileAppExt as _, ActionPermission, RunAgentsPermission,
WriteToPtyPermission,
AIExecutionProfile, AIExecutionProfileAppExt as _, PermissionMode,
};
use crate::ai::llms::{
DisableReason, LLMContextWindow, LLMId, LLMInfo, LLMPreferences, LLMPreferencesEvent,
};
use crate::ai::paths::host_native_absolute_path;
use crate::cloud_object::model::generic_string_model::StringModel;
use crate::editor::{
EditorView, Event as EditorEvent, InteractionState, SingleLineEditorOptions, TextOptions,
};
@@ -46,8 +43,8 @@ use crate::view_components::{
Dropdown, DropdownItem, FilterableDropdown, SubmittableTextInput, SubmittableTextInputEvent,
};
use crate::workspace::WorkspaceAction;
use crate::workspaces::user_workspaces::UserWorkspacesEvent;
use crate::{Appearance, TemplatableMCPServerManager, UserWorkspaces};
use crate::workspaces::user_workspaces::{UserWorkspaces, UserWorkspacesEvent};
use crate::{Appearance, TemplatableMCPServerManager};
const MODEL_MENU_WIDTH: f32 = 480.;
@@ -119,19 +116,7 @@ fn render_upgrade_footer(
#[derive(Default)]
struct TooltipMouseStateHandles {
// Separate mouse state handles for each permission dropdown (for workspace override tooltips)
apply_code_diffs_tooltip_mouse_state: MouseStateHandle,
read_files_tooltip_mouse_state: MouseStateHandle,
execute_commands_tooltip_mouse_state: MouseStateHandle,
write_to_pty_tooltip_mouse_state: MouseStateHandle,
computer_use_tooltip_mouse_state: MouseStateHandle,
ask_user_question_tooltip_mouse_state: MouseStateHandle,
run_agents_tooltip_mouse_state: MouseStateHandle,
call_mcp_servers_tooltip_mouse_state: MouseStateHandle,
// Separate mouse state handles for text input editors (for workspace override tooltips)
command_allowlist_editor_tooltip_mouse_state: MouseStateHandle,
directory_allowlist_editor_tooltip_mouse_state: MouseStateHandle,
mcp_allowlist_editor_tooltip_mouse_state: MouseStateHandle,
permission_mode_tooltip_mouse_state: MouseStateHandle,
mcp_denylist_editor_tooltip_mouse_state: MouseStateHandle,
}
@@ -171,36 +156,8 @@ pub enum ExecutionProfileEditorViewAction {
id: LLMId,
},
SetApplyCodeDiffs {
permission: ActionPermission,
},
SetReadFiles {
permission: ActionPermission,
},
SetExecuteCommands {
permission: ActionPermission,
},
SetWriteToPty {
permission: WriteToPtyPermission,
},
SetCallMcpServers {
permission: ActionPermission,
},
SetComputerUse {
permission: super::ComputerUsePermission,
},
SetAskUserQuestion {
permission: super::AskUserQuestionPermission,
},
SetRunAgents {
permission: RunAgentsPermission,
},
AddToCommandAllowlist {
predicate: AgentModeCommandExecutionPredicate,
},
RemoveFromCommandAllowlist {
predicate: AgentModeCommandExecutionPredicate,
SetPermissionMode {
mode: PermissionMode,
},
AddToCommandDenylist {
predicate: AgentModeCommandExecutionPredicate,
@@ -208,18 +165,6 @@ pub enum ExecutionProfileEditorViewAction {
RemoveFromCommandDenylist {
predicate: AgentModeCommandExecutionPredicate,
},
AddToDirectoryAllowlist {
path: PathBuf,
},
RemoveFromDirectoryAllowlist {
path: PathBuf,
},
AddToMCPAllowlist {
id: uuid::Uuid,
},
RemoveFromMCPAllowlist {
id: uuid::Uuid,
},
AddToMCPDenylist {
id: uuid::Uuid,
},
@@ -249,23 +194,10 @@ pub struct ExecutionProfileEditorView {
full_terminal_use_model_dropdown:
ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
computer_use_model_dropdown: ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
apply_code_diffs_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
read_files_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
execute_commands_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
write_to_pty_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
call_mcp_servers_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
computer_use_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
ask_user_question_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
run_agents_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
command_allowlist_editor: ViewHandle<SubmittableTextInput>,
permission_mode_dropdown: ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
command_denylist_editor: ViewHandle<SubmittableTextInput>,
directory_allowlist_editor: ViewHandle<SubmittableTextInput>,
command_allowlist_mouse_state_handles: Vec<MouseStateHandle>,
command_denylist_mouse_state_handles: Vec<MouseStateHandle>,
command_denylist_tooltip_mouse_state_handles: Vec<MouseStateHandle>,
directory_allowlist_mouse_state_handles: Vec<MouseStateHandle>,
mcp_allowlist_dropdown: ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
mcp_allowlist_mouse_state_handles: Vec<MouseStateHandle>,
mcp_denylist_dropdown: ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
mcp_denylist_mouse_state_handles: Vec<MouseStateHandle>,
profile_name_editor: ViewHandle<EditorView>,
@@ -280,254 +212,26 @@ impl ExecutionProfileEditorView {
pub fn new(profile_id: ClientProfileId, ctx: &mut ViewContext<Self>) -> Self {
let pane_configuration = ctx.add_model(|_ctx| PaneConfiguration::new(HEADER_TEXT));
let apply_code_diffs_dropdown = ctx.add_typed_action_view(|ctx| {
let permission_mode_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = Dropdown::new(ctx);
dropdown.set_items(
vec![
[
PermissionMode::ReadOnly,
PermissionMode::AskFirst,
PermissionMode::GalaxyDecides,
]
.into_iter()
.map(|mode| {
DropdownItem::new(
"Always allow",
ExecutionProfileEditorViewAction::SetApplyCodeDiffs {
permission: ActionPermission::AlwaysAllow,
},
),
DropdownItem::new(
"Always ask",
ExecutionProfileEditorViewAction::SetApplyCodeDiffs {
permission: ActionPermission::AlwaysAsk,
},
),
DropdownItem::new(
"Galaxy Decides",
ExecutionProfileEditorViewAction::SetApplyCodeDiffs {
permission: ActionPermission::GalaxyDecides,
},
),
],
mode.label(),
ExecutionProfileEditorViewAction::SetPermissionMode { mode },
)
})
.collect(),
ctx,
);
dropdown
});
let read_files_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = Dropdown::new(ctx);
dropdown.set_items(
vec![
DropdownItem::new(
"Always allow",
ExecutionProfileEditorViewAction::SetReadFiles {
permission: ActionPermission::AlwaysAllow,
},
),
DropdownItem::new(
"Always ask",
ExecutionProfileEditorViewAction::SetReadFiles {
permission: ActionPermission::AlwaysAsk,
},
),
DropdownItem::new(
"Galaxy Decides",
ExecutionProfileEditorViewAction::SetReadFiles {
permission: ActionPermission::GalaxyDecides,
},
),
],
ctx,
);
dropdown
});
let execute_commands_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = Dropdown::new(ctx);
dropdown.set_items(
vec![
DropdownItem::new(
"Always allow",
ExecutionProfileEditorViewAction::SetExecuteCommands {
permission: ActionPermission::AlwaysAllow,
},
),
DropdownItem::new(
"Always ask",
ExecutionProfileEditorViewAction::SetExecuteCommands {
permission: ActionPermission::AlwaysAsk,
},
),
DropdownItem::new(
"Galaxy Decides",
ExecutionProfileEditorViewAction::SetExecuteCommands {
permission: ActionPermission::GalaxyDecides,
},
),
],
ctx,
);
dropdown
});
let write_to_pty_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = Dropdown::new(ctx);
dropdown.set_items(
vec![
DropdownItem::new(
"Always allow",
ExecutionProfileEditorViewAction::SetWriteToPty {
permission: WriteToPtyPermission::AlwaysAllow,
},
),
DropdownItem::new(
"Always ask",
ExecutionProfileEditorViewAction::SetWriteToPty {
permission: WriteToPtyPermission::AlwaysAsk,
},
),
DropdownItem::new(
"Ask on first write",
ExecutionProfileEditorViewAction::SetWriteToPty {
permission: WriteToPtyPermission::AskOnFirstWrite,
},
),
DropdownItem::new(
"Galaxy Decides",
ExecutionProfileEditorViewAction::SetWriteToPty {
permission: WriteToPtyPermission::GalaxyDecides,
},
),
],
ctx,
);
dropdown
});
let call_mcp_servers_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = Dropdown::new(ctx);
dropdown.set_items(
vec![
DropdownItem::new(
"Always allow",
ExecutionProfileEditorViewAction::SetCallMcpServers {
permission: ActionPermission::AlwaysAllow,
},
),
DropdownItem::new(
"Always ask",
ExecutionProfileEditorViewAction::SetCallMcpServers {
permission: ActionPermission::AlwaysAsk,
},
),
DropdownItem::new(
"Galaxy Decides",
ExecutionProfileEditorViewAction::SetCallMcpServers {
permission: ActionPermission::GalaxyDecides,
},
),
],
ctx,
);
dropdown
});
let computer_use_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = Dropdown::new(ctx);
dropdown.set_items(
vec![
DropdownItem::new(
"Never",
ExecutionProfileEditorViewAction::SetComputerUse {
permission: super::ComputerUsePermission::Never,
},
),
DropdownItem::new(
"Always ask",
ExecutionProfileEditorViewAction::SetComputerUse {
permission: super::ComputerUsePermission::AlwaysAsk,
},
),
DropdownItem::new(
"Always allow",
ExecutionProfileEditorViewAction::SetComputerUse {
permission: super::ComputerUsePermission::AlwaysAllow,
},
),
DropdownItem::new(
"Galaxy Decides",
ExecutionProfileEditorViewAction::SetComputerUse {
permission: super::ComputerUsePermission::GalaxyDecides,
},
),
],
ctx,
);
dropdown
});
let ask_user_question_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = Dropdown::new(ctx);
dropdown.set_items(
vec![
DropdownItem::new(
"Never ask",
ExecutionProfileEditorViewAction::SetAskUserQuestion {
permission: super::AskUserQuestionPermission::Never,
},
),
DropdownItem::new(
"Ask unless auto-approve",
ExecutionProfileEditorViewAction::SetAskUserQuestion {
permission: super::AskUserQuestionPermission::AskExceptInAutoApprove,
},
),
DropdownItem::new(
"Always ask",
ExecutionProfileEditorViewAction::SetAskUserQuestion {
permission: super::AskUserQuestionPermission::AlwaysAsk,
},
),
],
ctx,
);
dropdown
});
let run_agents_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = Dropdown::new(ctx);
dropdown.set_items(
vec![
DropdownItem::new(
"Never",
ExecutionProfileEditorViewAction::SetRunAgents {
permission: RunAgentsPermission::NeverAllow,
},
),
DropdownItem::new(
"Always allow",
ExecutionProfileEditorViewAction::SetRunAgents {
permission: RunAgentsPermission::AlwaysAllow,
},
),
DropdownItem::new(
"Always ask",
ExecutionProfileEditorViewAction::SetRunAgents {
permission: RunAgentsPermission::AlwaysAsk,
},
),
DropdownItem::new(
"Galaxy Decides",
ExecutionProfileEditorViewAction::SetRunAgents {
permission: RunAgentsPermission::GalaxyDecides,
},
),
],
ctx,
);
dropdown
});
let mcp_allowlist_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = FilterableDropdown::new(ctx);
dropdown.set_menu_header_to_static("Select MCP servers");
dropdown
});
let mcp_denylist_dropdown = ctx.add_typed_action_view(|ctx| {
let mut dropdown = FilterableDropdown::new(ctx);
dropdown.set_menu_header_to_static("Select MCP servers");
@@ -537,12 +241,6 @@ impl ExecutionProfileEditorView {
let permissions = BlocklistAIPermissions::as_ref(ctx);
let profile_data = permissions.permissions_profile_for_id(ctx, profile_id);
let mcp_allowlist_mouse_state_handles = profile_data
.mcp_allowlist
.iter()
.map(|_| Default::default())
.collect();
let mcp_denylist_mouse_state_handles = profile_data
.mcp_denylist
.iter()
@@ -594,19 +292,6 @@ impl ExecutionProfileEditorView {
dropdown.set_menu_width(MODEL_MENU_WIDTH, ctx);
dropdown
});
let command_allowlist_editor = ctx.add_typed_action_view(|ctx| {
let mut input =
SubmittableTextInput::new(ctx).validate_on_edit(|s| Regex::new(s).is_ok());
input.set_placeholder_text("e.g. ls .*", ctx);
input
});
let command_allowlist_mouse_state_handles = profile_data
.command_allowlist
.iter()
.map(|_| Default::default())
.collect();
let command_denylist_editor = ctx.add_typed_action_view(|ctx| {
let mut input =
SubmittableTextInput::new(ctx).validate_on_edit(|s| Regex::new(s).is_ok());
@@ -620,21 +305,6 @@ impl ExecutionProfileEditorView {
.map(|_| Default::default())
.collect();
let directory_allowlist_editor = ctx.add_typed_action_view(|ctx| {
let mut input = SubmittableTextInput::new(ctx).validate_on_submit(|s| {
let expanded = host_native_absolute_path(s, &None, &None);
Path::new(&expanded).is_dir()
});
input.set_placeholder_text("e.g. ~/code-repos/repo", ctx);
input
});
let directory_allowlist_mouse_state_handles = profile_data
.directory_allowlist
.iter()
.map(|_| Default::default())
.collect();
let profile_name_editor = ctx.add_view(|ctx| {
let mut editor = EditorView::single_line(
SingleLineEditorOptions {
@@ -677,27 +347,14 @@ impl ExecutionProfileEditorView {
coding_model_dropdown,
full_terminal_use_model_dropdown,
computer_use_model_dropdown,
apply_code_diffs_dropdown,
read_files_dropdown,
execute_commands_dropdown,
write_to_pty_dropdown,
call_mcp_servers_dropdown,
computer_use_dropdown,
ask_user_question_dropdown,
run_agents_dropdown,
command_allowlist_editor,
permission_mode_dropdown,
command_denylist_editor,
directory_allowlist_editor,
command_allowlist_mouse_state_handles,
command_denylist_mouse_state_handles,
command_denylist_tooltip_mouse_state_handles: profile_data
.command_denylist
.iter()
.map(|_| Default::default())
.collect(),
directory_allowlist_mouse_state_handles,
mcp_allowlist_dropdown,
mcp_allowlist_mouse_state_handles,
mcp_denylist_dropdown,
mcp_denylist_mouse_state_handles,
profile_name_editor,
@@ -718,24 +375,6 @@ impl ExecutionProfileEditorView {
view.handle_context_window_editor_event(event, ctx);
});
ctx.subscribe_to_view(&view.command_allowlist_editor, |view, _, event, ctx| {
if let SubmittableTextInputEvent::Submit(s) = event {
let predicate = match AgentModeCommandExecutionPredicate::new_regex(s) {
Ok(regex) => regex,
Err(e) => {
log::warn!(
"Failed to convert string to regex for cmd execution allowlist: {e}"
);
return;
}
};
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.add_to_command_allowlist(view.profile_id, &predicate, ctx);
});
ctx.notify();
}
});
ctx.subscribe_to_view(&view.command_denylist_editor, |view, _, event, ctx| {
if let SubmittableTextInputEvent::Submit(s) = event {
let predicate = match AgentModeCommandExecutionPredicate::new_regex(s) {
@@ -754,20 +393,6 @@ impl ExecutionProfileEditorView {
}
});
ctx.subscribe_to_view(&view.directory_allowlist_editor, |view, _, event, ctx| {
if let SubmittableTextInputEvent::Submit(s) = event {
let expanded = host_native_absolute_path(s, &None, &None);
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.add_to_directory_allowlist(
view.profile_id,
&PathBuf::from(expanded),
ctx,
);
});
ctx.notify();
}
});
ctx.subscribe_to_model(&LLMPreferences::handle(ctx), |me, _, event, ctx| {
let permissions = BlocklistAIPermissions::as_ref(ctx);
let current_permissions = permissions.permissions_profile_for_id(ctx, me.profile_id);
@@ -867,23 +492,22 @@ impl ExecutionProfileEditorView {
);
let workspace = UserWorkspaces::handle(ctx);
ctx.subscribe_to_model(&workspace, |me, workspace, event, ctx| {
ctx.subscribe_to_model(&workspace, |me, _, event, ctx| {
if let UserWorkspacesEvent::TeamsChanged = event {
Self::update_all_editor_interaction_states(me, workspace, ctx);
Self::update_all_editor_interaction_states(me, ctx);
me.update_mouse_state_handles(ctx);
ctx.notify();
}
});
ctx.subscribe_to_model(&AISettings::handle(ctx), |me, _, event, ctx| {
if let AISettingsChangedEvent::IsAnyAIEnabled { .. } = event {
let workspace = UserWorkspaces::handle(ctx);
Self::update_all_editor_interaction_states(me, workspace, ctx);
Self::update_all_editor_interaction_states(me, ctx);
me.sync_context_window_editor(ctx, true);
ctx.notify();
}
});
Self::update_all_editor_interaction_states(&view, workspace, ctx);
Self::update_all_editor_interaction_states(&view, ctx);
view.refresh_profile_state(ctx);
@@ -901,12 +525,6 @@ impl ExecutionProfileEditorView {
let permissions = BlocklistAIPermissions::as_ref(app);
let current_permissions = permissions.permissions_profile_for_id(app, self.profile_id);
self.command_allowlist_mouse_state_handles = current_permissions
.command_allowlist
.iter()
.map(|_| Default::default())
.collect();
self.command_denylist_mouse_state_handles = current_permissions
.command_denylist
.iter()
@@ -919,18 +537,6 @@ impl ExecutionProfileEditorView {
.map(|_| Default::default())
.collect();
self.directory_allowlist_mouse_state_handles = current_permissions
.directory_allowlist
.iter()
.map(|_| Default::default())
.collect();
self.mcp_allowlist_mouse_state_handles = current_permissions
.mcp_allowlist
.iter()
.map(|_| Default::default())
.collect();
self.mcp_denylist_mouse_state_handles = current_permissions
.mcp_denylist
.iter()
@@ -941,18 +547,6 @@ impl ExecutionProfileEditorView {
fn refresh_profile_state(&mut self, ctx: &mut ViewContext<Self>) {
let permissions = BlocklistAIPermissions::as_ref(ctx);
let current_permissions = permissions.permissions_profile_for_id(ctx, self.profile_id);
let ai_settings = AISettings::as_ref(ctx);
let apply_code_diffs_disabled = !ai_settings.is_code_diffs_permissions_editable(ctx);
let read_files_disabled = !ai_settings.is_read_files_permissions_editable(ctx);
let execute_commands_disabled = !ai_settings.is_execute_commands_permissions_editable(ctx);
let write_to_pty_disabled = !ai_settings.is_write_to_pty_permissions_editable(ctx);
let computer_use_disabled = !ai_settings.is_computer_use_permissions_editable(ctx);
let ask_user_question_disabled =
!ai_settings.is_ask_user_question_permissions_editable(ctx);
let run_agents_disabled = !ai_settings.is_run_agents_permissions_editable(ctx);
let mcp_disabled = !ai_settings.is_mcp_permission_editable(ctx);
Self::refresh_filterable_model_dropdown(
&self.base_model_dropdown,
current_permissions.base_model.clone(),
@@ -986,65 +580,22 @@ impl ExecutionProfileEditorView {
ctx,
);
Self::refresh_execution_profile_dropdown_menu(
&self.apply_code_diffs_dropdown,
current_permissions.apply_code_diffs,
apply_code_diffs_disabled,
ctx,
);
Self::refresh_execution_profile_dropdown_menu(
&self.read_files_dropdown,
current_permissions.read_files,
read_files_disabled,
ctx,
);
Self::refresh_execution_profile_dropdown_menu(
&self.execute_commands_dropdown,
current_permissions.execute_commands,
execute_commands_disabled,
ctx,
);
Self::refresh_write_to_pty_dropdown_menu(
&self.write_to_pty_dropdown,
current_permissions.write_to_pty,
write_to_pty_disabled,
ctx,
);
Self::refresh_execution_profile_dropdown_menu(
&self.call_mcp_servers_dropdown,
current_permissions.mcp_permissions,
mcp_disabled,
ctx,
);
Self::refresh_computer_use_dropdown_menu(
&self.computer_use_dropdown,
current_permissions.computer_use,
computer_use_disabled,
ctx,
);
Self::refresh_ask_user_question_dropdown_menu(
&self.ask_user_question_dropdown,
current_permissions.ask_user_question,
ask_user_question_disabled,
ctx,
);
Self::refresh_run_agents_dropdown_menu(
&self.run_agents_dropdown,
current_permissions.run_agents,
run_agents_disabled,
ctx,
);
Self::refresh_mcp_dropdown(
&self.mcp_allowlist_dropdown,
|uuid| ExecutionProfileEditorViewAction::AddToMCPAllowlist { id: uuid },
&current_permissions.mcp_allowlist,
&current_permissions.mcp_denylist,
ctx,
);
self.permission_mode_dropdown.update(ctx, |dropdown, ctx| {
dropdown.set_selected_by_action(
ExecutionProfileEditorViewAction::SetPermissionMode {
mode: current_permissions.permission_mode,
},
ctx,
);
if AISettings::as_ref(ctx).is_any_ai_enabled(ctx) {
dropdown.set_enabled(ctx);
} else {
dropdown.set_disabled(ctx);
}
});
Self::refresh_mcp_dropdown(
&self.mcp_denylist_dropdown,
|uuid| ExecutionProfileEditorViewAction::AddToMCPDenylist { id: uuid },
&current_permissions.mcp_allowlist,
&current_permissions.mcp_denylist,
ctx,
);
@@ -1053,135 +604,6 @@ impl ExecutionProfileEditorView {
self.sync_context_window_editor(ctx, false);
}
fn refresh_execution_profile_dropdown_menu(
menu: &ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
current_permission: ActionPermission,
disabled: bool,
ctx: &mut ViewContext<Self>,
) {
menu.update(ctx, |menu, ctx| {
if !disabled {
menu.set_enabled(ctx);
} else {
menu.set_disabled(ctx);
}
let active = match current_permission {
ActionPermission::AlwaysAllow => 0,
ActionPermission::AlwaysAsk | ActionPermission::Unknown => 1,
ActionPermission::GalaxyDecides => 2,
};
menu.set_selected_by_index(active, ctx);
ctx.notify();
});
ctx.notify();
}
fn refresh_write_to_pty_dropdown_menu(
menu: &ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
current_permission: WriteToPtyPermission,
disabled: bool,
ctx: &mut ViewContext<Self>,
) {
menu.update(ctx, |menu, ctx| {
if !disabled {
menu.set_enabled(ctx);
} else {
menu.set_disabled(ctx);
}
let active = match current_permission {
WriteToPtyPermission::AlwaysAllow => 0,
WriteToPtyPermission::AlwaysAsk | WriteToPtyPermission::Unknown => 1,
WriteToPtyPermission::AskOnFirstWrite => 2,
WriteToPtyPermission::GalaxyDecides => 3,
};
menu.set_selected_by_index(active, ctx);
ctx.notify();
});
ctx.notify();
}
fn refresh_computer_use_dropdown_menu(
menu: &ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
current_permission: super::ComputerUsePermission,
disabled: bool,
ctx: &mut ViewContext<Self>,
) {
menu.update(ctx, |menu, ctx| {
if !disabled {
menu.set_enabled(ctx);
} else {
menu.set_disabled(ctx);
}
let active = match current_permission {
super::ComputerUsePermission::Never | super::ComputerUsePermission::Unknown => 0,
super::ComputerUsePermission::AlwaysAsk => 1,
super::ComputerUsePermission::AlwaysAllow => 2,
super::ComputerUsePermission::GalaxyDecides => 3,
};
menu.set_selected_by_index(active, ctx);
ctx.notify();
});
ctx.notify();
}
fn refresh_ask_user_question_dropdown_menu(
menu: &ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
current_permission: super::AskUserQuestionPermission,
disabled: bool,
ctx: &mut ViewContext<Self>,
) {
menu.update(ctx, |menu, ctx| {
if !disabled {
menu.set_enabled(ctx);
} else {
menu.set_disabled(ctx);
}
let active = match current_permission {
super::AskUserQuestionPermission::Never => 0,
super::AskUserQuestionPermission::AskExceptInAutoApprove
| super::AskUserQuestionPermission::Unknown => 1,
super::AskUserQuestionPermission::AlwaysAsk => 2,
};
menu.set_selected_by_index(active, ctx);
ctx.notify();
});
ctx.notify();
}
fn refresh_run_agents_dropdown_menu(
menu: &ViewHandle<Dropdown<ExecutionProfileEditorViewAction>>,
current_permission: RunAgentsPermission,
disabled: bool,
ctx: &mut ViewContext<Self>,
) {
menu.update(ctx, |menu, ctx| {
if !disabled {
menu.set_enabled(ctx);
} else {
menu.set_disabled(ctx);
}
let active = match current_permission {
RunAgentsPermission::NeverAllow | RunAgentsPermission::Unknown => 0,
RunAgentsPermission::AlwaysAllow => 1,
RunAgentsPermission::AlwaysAsk => 2,
RunAgentsPermission::GalaxyDecides => 3,
};
menu.set_selected_by_index(active, ctx);
ctx.notify();
});
ctx.notify();
}
fn refresh_filterable_model_dropdown<G, A, D>(
menu: &ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
profile_model: Option<LLMId>,
@@ -1295,7 +717,6 @@ impl ExecutionProfileEditorView {
fn refresh_mcp_dropdown<F>(
dropdown: &ViewHandle<FilterableDropdown<ExecutionProfileEditorViewAction>>,
action_creator: F,
profile_mcp_allowlist: &[uuid::Uuid],
profile_mcp_denylist: &[uuid::Uuid],
ctx: &mut ViewContext<Self>,
) where
@@ -1305,9 +726,7 @@ impl ExecutionProfileEditorView {
dropdown.update(ctx, |dropdown, ctx| {
let mcps_in_dropdown: Vec<(uuid::Uuid, String)> = all_mcp_servers
.into_iter()
.filter(|(uuid, _server_name)| {
!profile_mcp_allowlist.contains(uuid) && !profile_mcp_denylist.contains(uuid)
})
.filter(|(uuid, _server_name)| !profile_mcp_denylist.contains(uuid))
.collect();
dropdown.set_items(
@@ -1358,11 +777,7 @@ impl ExecutionProfileEditorView {
ctx: &mut ViewContext<Self>,
) {
profile_name_editor.update(ctx, |editor, ctx| {
let display_name = if profile_data.is_default_profile {
"Default".to_string()
} else {
profile_data.name.clone()
};
let display_name = profile_data.display_name();
// Only update the buffer text if it's different from what's currently displayed
// This preserves the cursor position when the text hasn't changed
@@ -1370,37 +785,19 @@ impl ExecutionProfileEditorView {
if current_text != display_name {
editor.set_buffer_text(&display_name, ctx);
}
if profile_data.is_default_profile {
editor.set_interaction_state(InteractionState::Disabled, ctx);
}
});
}
fn update_all_editor_interaction_states(
view: &Self,
workspace: ModelHandle<UserWorkspaces>,
ctx: &mut ViewContext<Self>,
) {
fn update_all_editor_interaction_states(view: &Self, ctx: &mut ViewContext<Self>) {
let is_any_ai_enabled = AISettings::as_ref(ctx).is_any_ai_enabled(ctx);
let ai_autonomy_settings = workspace.as_ref(ctx).ai_autonomy_settings();
Self::update_editor_interaction_state(
view.command_denylist_editor.as_ref(ctx).editor().clone(),
view.profile_name_editor.clone(),
is_any_ai_enabled,
ctx,
);
Self::update_editor_interaction_state(
view.command_allowlist_editor.as_ref(ctx).editor().clone(),
is_any_ai_enabled
&& !ai_autonomy_settings.has_override_for_execute_commands_allowlist(),
ctx,
);
Self::update_editor_interaction_state(
view.directory_allowlist_editor.as_ref(ctx).editor().clone(),
is_any_ai_enabled && !ai_autonomy_settings.has_override_for_read_files_allowlist(),
view.command_denylist_editor.as_ref(ctx).editor().clone(),
is_any_ai_enabled,
ctx,
);
}
@@ -1537,11 +934,7 @@ impl View for ExecutionProfileEditorView {
let profile_data = permissions.permissions_profile_for_id(app, self.profile_id);
let mut column = Flex::column()
.with_child(render_header_section(
appearance,
&self.profile_name_editor,
profile_data.is_default_profile,
))
.with_child(render_header_section(appearance, &self.profile_name_editor))
.with_child(render_models_section(appearance, self, app))
.with_child(render_permissions_section(
appearance,
@@ -1649,63 +1042,9 @@ impl TypedActionView for ExecutionProfileEditorView {
});
ctx.notify();
}
ExecutionProfileEditorViewAction::SetApplyCodeDiffs { permission } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.set_apply_code_diffs(self.profile_id, permission, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::SetReadFiles { permission } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.set_read_files(self.profile_id, permission, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::SetExecuteCommands { permission } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.set_execute_commands(self.profile_id, permission, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::SetWriteToPty { permission } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.set_write_to_pty(self.profile_id, permission, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::SetCallMcpServers { permission } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.set_mcp_permissions(self.profile_id, permission, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::SetComputerUse { permission } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.set_computer_use(self.profile_id, permission, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::SetAskUserQuestion { permission } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.set_ask_user_question(self.profile_id, *permission, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::SetRunAgents { permission } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.set_run_agents(self.profile_id, *permission, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::AddToCommandAllowlist { predicate } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.add_to_command_allowlist(self.profile_id, predicate, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::RemoveFromCommandAllowlist { predicate } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.remove_from_command_allowlist(self.profile_id, predicate, ctx);
ExecutionProfileEditorViewAction::SetPermissionMode { mode } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles, ctx| {
profiles.set_permission_mode(self.profile_id, *mode, ctx);
});
ctx.notify();
}
@@ -1721,30 +1060,6 @@ impl TypedActionView for ExecutionProfileEditorView {
});
ctx.notify();
}
ExecutionProfileEditorViewAction::AddToDirectoryAllowlist { path } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.add_to_directory_allowlist(self.profile_id, path, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::RemoveFromDirectoryAllowlist { path } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.remove_from_directory_allowlist(self.profile_id, path, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::AddToMCPAllowlist { id } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.add_to_mcp_allowlist(self.profile_id, id, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::RemoveFromMCPAllowlist { id } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.remove_from_mcp_allowlist(self.profile_id, id, ctx);
});
ctx.notify();
}
ExecutionProfileEditorViewAction::AddToMCPDenylist { id } => {
AIExecutionProfilesModel::handle(ctx).update(ctx, |profiles_model, ctx| {
profiles_model.add_to_mcp_denylist(self.profile_id, id, ctx);