Improve Galaxy permissions, computer-use routing, and agent UX

This commit is contained in:
2026-09-30 09:55:24 -05:00
parent cc064d4050
commit a2a2633199
30 changed files with 623 additions and 180 deletions
@@ -17,10 +17,10 @@ pub const PROFILE_NAME_MAX_LENGTH: usize = 50;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ActionPermission {
AgentDecides,
AlwaysAllow,
AlwaysAsk,
/// Independently assess each action before allowing execution.
#[serde(alias = "AgentDecides")]
GalaxyDecides,
// This is intended to catch deserialization errors whenever we add new variants to this enum. Say we
// want to add a "Never" variant. Without this catch-all, old clients wouldn't be able to deserialize
@@ -35,9 +35,7 @@ impl ActionPermission {
ActionPermission::GalaxyDecides => {
"Galaxy reviews each action with your terminal-use model. Risk 0–3 runs automatically; risk 4–5 asks you, and is rejected after 60 seconds without a response."
}
ActionPermission::AgentDecides | ActionPermission::Unknown => {
"The Agent chooses the safest path: acting on its own when confident, and asking for approval when uncertain."
}
ActionPermission::Unknown => "Unknown setting.",
ActionPermission::AlwaysAllow => {
"Give the Agent full autonomy — no manual approval ever required."
}
@@ -419,11 +417,11 @@ impl Default for AIExecutionProfile {
Self {
name: Default::default(),
is_default_profile: false,
apply_code_diffs: ActionPermission::AgentDecides,
read_files: ActionPermission::AgentDecides,
apply_code_diffs: ActionPermission::GalaxyDecides,
read_files: ActionPermission::GalaxyDecides,
execute_commands: ActionPermission::AlwaysAsk,
write_to_pty: WriteToPtyPermission::AlwaysAsk,
mcp_permissions: ActionPermission::AgentDecides,
mcp_permissions: ActionPermission::GalaxyDecides,
ask_user_question: AskUserQuestionPermission::AlwaysAsk,
run_agents: RunAgentsPermission::AlwaysAsk,
command_denylist: DEFAULT_COMMAND_EXECUTION_DENYLIST.clone(),
@@ -867,6 +867,18 @@ impl ProviderRun {
matches!(self.state, ProviderRunState::ReadyToCallModel).then(|| self.current_work_id())
}
/// Selects the runtime for the next turn after a tool batch has been committed.
pub fn set_ready_profile(
&mut self,
profile: impl Into<ProviderRequestProfile>,
) -> Result<(), ProviderRunProtocolError> {
if !matches!(self.state, ProviderRunState::ReadyToCallModel) {
return Err(self.unexpected_state(ProviderRunPhase::ReadyToCallModel));
}
self.profile = profile.into();
Ok(())
}
fn model_turn_limit_reached(&self) -> bool {
self.limits
.max_model_turns
@@ -18,6 +18,18 @@ fn run() -> ProviderRun {
run_with_limits(ProviderRunLimits::default())
}
#[test]
fn ready_profile_can_change_without_replaying_or_adding_history() {
let mut run = run();
let original_transcript = run.transcript().to_vec();
run.set_ready_profile("computer-use").unwrap();
assert_eq!(run.profile().as_str(), "computer-use");
assert_eq!(run.transcript(), original_transcript);
assert_eq!(next_model_call(&mut run).profile.as_str(), "computer-use");
assert!(run.set_ready_profile("base").is_err());
}
fn next_model_call(run: &mut ProviderRun) -> ProviderModelCall {
let Some(ProviderRunStep::CallModel(call)) = run.next_step().unwrap() else {
panic!("expected model call");