Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/src/ai/agent/api/convert_conversation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1607,6 +1607,7 @@ pub(crate) fn convert_tool_call_result_to_input(
environment_id: remote.environment_id.clone(),
worker_host: remote.worker_host.clone(),
computer_use_enabled: remote.computer_use_enabled,
runner_id: remote.runner_id.clone(),
},
Some(api::run_agents_result::launched::ResolvedExecutionMode::Local(_))
| None => RunAgentsLaunchedExecutionMode::Local,
Expand Down
4 changes: 4 additions & 0 deletions app/src/ai/agent/api/convert_from.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ fn convert_run_agents_execution_mode(
environment_id: remote.environment_id,
worker_host: remote.worker_host,
computer_use_enabled: remote.computer_use_enabled,
runner_id: remote.runner_id,
},
Some(api::run_agents::ExecutionMode::Local(_)) | None => RunAgentsExecutionMode::Local,
}
Expand Down Expand Up @@ -198,6 +199,9 @@ fn convert_start_agent_v2_execution_mode(
// Auth secret is plumbed client-side via `RunAgentsRequest`;
// StartAgentV2 from the server never carries it.
auth_secret_name: None,
// StartAgentV2 (server→child) does not carry a runner
// override; runner selection flows through RunAgents.
runner_id: String::new(),
}
}
Some(api::start_agent_v2::execution_mode::Mode::Local(local)) => {
Expand Down
2 changes: 2 additions & 0 deletions app/src/ai/agent/api/convert_from_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,7 @@ fn converts_remote_start_agent_with_environment_id() {
harness_type: String::new(),
title: String::new(),
auth_secret_name: None,
runner_id: String::new(),
}
);
assert_eq!(lifecycle_subscription, None);
Expand Down Expand Up @@ -580,6 +581,7 @@ fn converts_remote_start_agent_v2_with_skill_references() {
harness_type: "claude-code".to_string(),
title: "Remote child".to_string(),
auth_secret_name: None,
runner_id: String::new(),
}
);
assert_eq!(lifecycle_subscription, None);
Expand Down
2 changes: 1 addition & 1 deletion app/src/ai/agent_sdk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ fn dispatch_command(
api_key::run(ctx, global_options, api_key_cmd)
}
CliCommand::Runner(runner_cmd) => {
if !FeatureFlag::CloudAgentRunnerCLICommands.is_enabled() {
if !FeatureFlag::CloudAgentRunners.is_enabled() {
return Err(anyhow::anyhow!("invalid value 'runner'"));
}
runner::run(ctx, global_options, runner_cmd)
Expand Down
2 changes: 1 addition & 1 deletion app/src/ai/agent_sdk/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ impl TelemetryEventDesc for CliTelemetryEventDiscriminants {
EnablementState::Flag(FeatureFlag::APIKeyManagement)
}
Self::RunnerList | Self::RunnerCreate | Self::RunnerUpdate | Self::RunnerDelete => {
EnablementState::Flag(FeatureFlag::CloudAgentRunnerCLICommands)
EnablementState::Flag(FeatureFlag::CloudAgentRunners)
}
Self::RunMessageWatch
| Self::RunMessageSend
Expand Down
4 changes: 4 additions & 0 deletions app/src/ai/blocklist/action_model/execute/run_agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,10 +344,12 @@ impl RunAgentsExecutor {
environment_id,
worker_host,
computer_use_enabled,
runner_id,
} => RunAgentsLaunchedExecutionMode::Remote {
environment_id: environment_id.clone(),
worker_host: worker_host.clone(),
computer_use_enabled: *computer_use_enabled,
runner_id: runner_id.clone(),
},
};
let result = RunAgentsResult::Launched {
Expand Down Expand Up @@ -705,6 +707,7 @@ pub fn run_agents_to_start_agent_mode(
environment_id,
worker_host,
computer_use_enabled,
runner_id,
} => {
// OpenCode is unsupported on Remote.
if run_harness_type.eq_ignore_ascii_case("opencode") {
Expand All @@ -723,6 +726,7 @@ pub fn run_agents_to_start_agent_mode(
auth_secret_name: run_auth_secret_name
.map(str::to_string)
.filter(|s| !s.trim().is_empty()),
runner_id: runner_id.clone(),
})
}
}
Expand Down
2 changes: 2 additions & 0 deletions app/src/ai/blocklist/action_model/execute/run_agents_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ fn persist_plan_config_with_harness(
execution_mode: OrchestrationExecutionMode::Remote {
environment_id: "env-1".to_string(),
worker_host: "warp".to_string(),
runner_id: String::new(),
},
},
status,
Expand Down Expand Up @@ -231,6 +232,7 @@ fn remote_run_agents_action(harness_type: &str) -> AIAgentAction {
environment_id: "env-1".to_string(),
worker_host: "warp".to_string(),
computer_use_enabled: false,
runner_id: String::new(),
},
agent_run_configs: vec![RunAgentsAgentRunConfig {
name: "child".to_string(),
Expand Down
2 changes: 2 additions & 0 deletions app/src/ai/blocklist/action_model/execute/start_agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ impl StartAgentExecutor {
harness_type,
title,
auth_secret_name,
runner_id,
} => {
let harness_type = Harness::parse_orchestration_harness(&harness_type)
.map(|harness| harness.to_string())
Expand Down Expand Up @@ -479,6 +480,7 @@ impl StartAgentExecutor {
harness_type,
title,
auth_secret_name,
runner_id,
},
Some(parent_run_id),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1039,6 +1039,7 @@ fn execute_returns_error_when_remote_opencode_harness_is_requested() {
harness_type: "opencode".to_string(),
title: String::new(),
auth_secret_name: None,
runner_id: String::new(),
},
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ fn start_agent_copy_uses_remote_labels_for_remote_children() {
harness_type: String::new(),
title: String::new(),
auth_secret_name: None,
runner_id: String::new(),
};

assert_eq!(start_agent_success_suffix(&execution_mode), " remotely.");
Expand Down
6 changes: 6 additions & 0 deletions app/src/ai/blocklist/block_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ fn remote_arm_propagates_skills_into_skill_references() {
environment_id: "env-1".to_string(),
worker_host: "warp".to_string(),
computer_use_enabled: true,
runner_id: String::new(),
},
"oz",
"auto",
Expand All @@ -413,6 +414,7 @@ fn remote_arm_propagates_skills_into_skill_references() {
computer_use_enabled,
title,
auth_secret_name,
runner_id: _,
} = mode
else {
panic!("expected Remote start-agent mode");
Expand All @@ -434,6 +436,7 @@ fn remote_arm_with_empty_skills_propagates_empty_vec() {
environment_id: "env-1".to_string(),
worker_host: "warp".to_string(),
computer_use_enabled: false,
runner_id: String::new(),
},
"claude",
"auto",
Expand All @@ -458,6 +461,7 @@ fn remote_arm_rejects_opencode() {
environment_id: "env-1".to_string(),
worker_host: "warp".to_string(),
computer_use_enabled: false,
runner_id: String::new(),
},
"opencode",
"auto",
Expand Down Expand Up @@ -510,6 +514,7 @@ fn remote_arm_propagates_claude_auth_secret_into_mode() {
environment_id: "env-1".to_string(),
worker_host: "warp".to_string(),
computer_use_enabled: false,
runner_id: String::new(),
},
"claude",
"auto",
Expand All @@ -534,6 +539,7 @@ fn remote_arm_filters_whitespace_auth_secret_name_to_none() {
environment_id: "env-1".to_string(),
worker_host: "warp".to_string(),
computer_use_enabled: false,
runner_id: String::new(),
},
"codex",
"auto",
Expand Down
92 changes: 89 additions & 3 deletions app/src/ai/blocklist/inline_action/orchestration_controls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ pub use crate::ai::orchestration::{
OrchestrationConfigState, OrchestrationEditState, ORCHESTRATION_WARP_WORKER_HOST,
};
use crate::ai::orchestration::{
api_key_snapshot, environment_snapshot, harness_snapshot, host_snapshot, model_snapshot,
persist_auth_secret_selection, OptionBadge, OptionFooter, OptionRow, OptionSnapshot,
OptionSourceStatus, AUTH_SECRET_INHERIT_LABEL,
api_key_snapshot, build_runner_snapshot, environment_snapshot, harness_snapshot, host_snapshot,
model_snapshot, persist_auth_secret_selection, OptionBadge, OptionFooter, OptionRow,
OptionSnapshot, OptionSourceStatus, AUTH_SECRET_INHERIT_LABEL,
};
use crate::appearance::Appearance;
use crate::menu::{MenuItem, MenuItemFields};
Expand Down Expand Up @@ -77,6 +77,9 @@ pub trait OrchestrationControlAction: DropdownItemAction + Clone {
fn harness_changed(harness_type: String) -> Self;
fn environment_changed(environment_id: String) -> Self;
fn create_environment_requested() -> Self;
/// Runner UID selected in the Runner dropdown; empty clears the
/// override ("Use environment default").
fn runner_changed(runner_id: String) -> Self;
/// `None` means Inherit; `Some(name)` means a named managed secret.
fn auth_secret_changed(name: Option<String>) -> Self;
/// User picked the "New API key…" item; opens the workspace create modal.
Expand All @@ -92,6 +95,9 @@ pub struct OrchestrationPickerHandles<A: OrchestrationControlAction> {
pub model_picker: Option<ViewHandle<FilterableDropdown<A>>>,
pub harness_picker: Option<ViewHandle<Dropdown<A>>>,
pub environment_picker: Option<ViewHandle<FilterableDropdown<A>>>,
/// Runner picker for the Cloud variant (gated on `CloudAgentRunners`).
/// `None` until built; runners are fetched via `FactoryClient::get_runners`.
pub runner_picker: Option<ViewHandle<FilterableDropdown<A>>>,
pub host_picker: Option<ViewHandle<HostPicker>>,
/// Picker for the managed auth secret used by non-Oz cloud children.
/// `None` when the picker hasn't been built yet (e.g. harness is Oz or
Expand All @@ -108,6 +114,7 @@ impl<A: OrchestrationControlAction> Default for OrchestrationPickerHandles<A> {
model_picker: None,
harness_picker: None,
environment_picker: None,
runner_picker: None,
host_picker: None,
auth_secret_picker: None,
local_toggle: MouseStateHandle::default(),
Expand Down Expand Up @@ -222,6 +229,7 @@ fn snapshot_execution_mode(is_local: bool) -> RunAgentsExecutionMode {
environment_id: String::new(),
worker_host: String::new(),
computer_use_enabled: false,
runner_id: String::new(),
}
}
}
Expand Down Expand Up @@ -402,6 +410,7 @@ pub fn populate_environment_picker<A: OrchestrationControlAction, V: View>(
environment_id: initial_env_id.to_string(),
worker_host: String::new(),
computer_use_enabled: false,
runner_id: String::new(),
},
);
dropdown_handle.update(ctx, |dropdown, ctx_dropdown| {
Expand All @@ -423,6 +432,62 @@ pub fn populate_environment_picker<A: OrchestrationControlAction, V: View>(
});
}

/// Creates the Runner picker dropdown with the shared orchestration
/// chrome, then populates it from the supplied runners list. Runners are
/// not cached client-side (unlike environments), so the owning view
/// fetches them via `FactoryClient::get_runners` and passes them in;
/// `loading` renders the picker in its loading state until they arrive.
pub fn create_runner_picker<A: OrchestrationControlAction, V: View>(
initial_runner_id: &str,
runners: &[(String, String)],
loading: bool,
styles: &UiComponentStyles,
ctx: &mut ViewContext<V>,
) -> ViewHandle<FilterableDropdown<A>> {
let styles = *styles;
let dropdown_handle = ctx.add_typed_action_view(move |ctx_dropdown| {
let mut dropdown = FilterableDropdown::<A>::new(ctx_dropdown);
dropdown.set_use_overlay_layer(false, ctx_dropdown);
dropdown.set_match_menu_width_to_top_bar(true, ctx_dropdown);
dropdown.set_main_axis_size(MainAxisSize::Max, ctx_dropdown);
dropdown.set_button_variant(ButtonVariant::Secondary);
dropdown.set_style(styles);
dropdown.set_top_bar_height(ORCHESTRATION_PICKER_HEIGHT, ctx_dropdown);
dropdown.set_top_bar_max_width(f32::INFINITY);
dropdown
});
populate_runner_picker(&dropdown_handle, runners, initial_runner_id, loading, ctx);
dropdown_handle
}

/// Populates the runner picker from [`build_runner_snapshot`] ("Use
/// environment default" plus the supplied runners).
pub fn populate_runner_picker<A: OrchestrationControlAction, V: View>(
dropdown_handle: &ViewHandle<FilterableDropdown<A>>,
runners: &[(String, String)],
current_runner_id: &str,
loading: bool,
ctx: &mut ViewContext<V>,
) {
let snapshot = build_runner_snapshot(runners.to_vec(), current_runner_id, loading);
let selected_label = selected_row_label(&snapshot);
dropdown_handle.update(ctx, |dropdown, ctx_dropdown| {
let items = snapshot
.rows
.into_iter()
.map(|row| {
MenuItem::Item(MenuItemFields::new(&row.label).with_on_select_action(
DropdownAction::select_action_and_close(A::runner_changed(row.id)),
))
})
.collect();
dropdown.set_rich_items(items, ctx_dropdown);
if let Some(label) = &selected_label {
dropdown.set_selected_by_name(label, ctx_dropdown);
}
});
}

fn render_new_environment_footer<A: OrchestrationControlAction>(
mouse_state: MouseStateHandle,
app: &AppContext,
Expand Down Expand Up @@ -487,6 +552,7 @@ pub fn populate_host_picker<V: View>(
environment_id: String::new(),
worker_host: initial_host.to_string(),
computer_use_enabled: false,
runner_id: String::new(),
},
);
let snapshot = host_snapshot(&state, ctx);
Expand Down Expand Up @@ -1116,6 +1182,16 @@ pub fn render_picker_row_with_layout<A: OrchestrationControlAction>(
.as_ref()
.map(|p| ChildView::new(p).finish()),
);
if warp_core::features::FeatureFlag::CloudAgentRunners.is_enabled() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import the FeatureFlag enum

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added use warp_core::features::FeatureFlag; and switched the usages to FeatureFlag::CloudAgentRunners.

add(
&mut column,
"Runner",
handles
.runner_picker
.as_ref()
.map(|p| ChildView::new(p).finish()),
);
}
}
add(
&mut column,
Expand Down Expand Up @@ -1163,6 +1239,16 @@ pub fn render_picker_row_with_layout<A: OrchestrationControlAction>(
.as_ref()
.map(|p| ChildView::new(p).finish()),
);
if warp_core::features::FeatureFlag::CloudAgentRunners.is_enabled() {
add_picker(
&mut row,
"Runner",
handles
.runner_picker
.as_ref()
.map(|p| ChildView::new(p).finish()),
);
}
}
add_picker(
&mut row,
Expand Down
Loading