Complete agent monitoring and Galaxy Control integration

- expose command-monitor conversations and preserve visible agent transcripts
- add bounded polling and a dedicated shell interrupt tool
- improve direct-provider images, skills, tool history, and usage handling
- package and brand Galaxy Control across releases, installers, persistence, and docs
This commit is contained in:
2026-07-29 15:04:58 -05:00
parent 100f1eff1c
commit dbfa8bcd48
172 changed files with 6357 additions and 3825 deletions
+2 -9
View File
@@ -1,4 +1,4 @@
//! Bridge between protocol-level control requests and Warp application models.
//! Bridge between protocol-level control requests and Galaxy application models.
//!
//! The bridge validates protocol version, selectors, credentials, and settings
//! before routing each supported action to an app-side handler.
@@ -17,7 +17,7 @@ use crate::local_control::permissions::{
};
use crate::local_control::resolver::{validate_action_params, validate_action_target};
/// WarpUI model that executes already-authenticated local-control actions.
/// GalaxyUI model that executes already-authenticated local-control actions.
pub struct LocalControlBridge {
instance_id: Option<InstanceId>,
}
@@ -105,20 +105,13 @@ impl LocalControlBridge {
| ActionKind::SurfaceCommandSearchOpen
| ActionKind::SurfaceThemePickerOpen
| ActionKind::SurfaceKeybindingsOpen
| ActionKind::SurfaceWarpDriveOpen
| ActionKind::SurfaceWarpDriveToggle
| ActionKind::SurfaceResourceCenterToggle
| ActionKind::SurfaceAiAssistantToggle
| ActionKind::SurfaceCodeReviewOpen
| ActionKind::SurfaceCodeReviewToggle
| ActionKind::SurfaceProjectExplorerOpen
| ActionKind::SurfaceGlobalSearchOpen
| ActionKind::SurfaceConversationListOpen
| ActionKind::SurfaceLeftPanelToggle
| ActionKind::SurfaceRightPanelToggle
| ActionKind::SurfaceVerticalTabsOpen
| ActionKind::SurfaceVerticalTabsToggle
| ActionKind::SurfaceAgentManagementOpen
| ActionKind::FileOpen => app_state::handle(
&self.instance_id,
request.action.kind,
+3 -55
View File
@@ -82,22 +82,6 @@ pub(crate) fn handle(
target,
ctx,
),
ActionKind::SurfaceWarpDriveOpen => surface_workspace_action(
instance_id,
action,
SurfaceDestination::WarpDrive,
WorkspaceAction::OpenGalaxyDrive,
target,
ctx,
),
ActionKind::SurfaceAgentManagementOpen => surface_workspace_action(
instance_id,
action,
SurfaceDestination::AgentManagement,
WorkspaceAction::OpenAgentManagementView,
target,
ctx,
),
ActionKind::SessionNext => workspace_action(
instance_id,
action,
@@ -121,27 +105,6 @@ pub(crate) fn handle(
surface_command_search_open(instance_id, params, target, ctx)
}
ActionKind::SurfaceThemePickerOpen => surface_theme_picker_open(instance_id, target, ctx),
ActionKind::SurfaceWarpDriveToggle => workspace_action(
instance_id,
action,
WorkspaceAction::ToggleGalaxyDrive,
target,
ctx,
),
ActionKind::SurfaceResourceCenterToggle => workspace_action(
instance_id,
action,
WorkspaceAction::ToggleResourceCenter,
target,
ctx,
),
ActionKind::SurfaceAiAssistantToggle => workspace_action(
instance_id,
action,
WorkspaceAction::ToggleAIAssistant,
target,
ctx,
),
ActionKind::SurfaceCodeReviewOpen => surface_code_review_open(instance_id, target, ctx),
ActionKind::SurfaceCodeReviewToggle | ActionKind::SurfaceRightPanelToggle => {
workspace_action(
@@ -168,21 +131,6 @@ pub(crate) fn handle(
target,
ctx,
),
ActionKind::SurfaceConversationListOpen => surface_workspace_action(
instance_id,
action,
SurfaceDestination::ConversationList,
WorkspaceAction::OpenConversationListView,
target,
ctx,
),
ActionKind::SurfaceLeftPanelToggle => workspace_action(
instance_id,
action,
WorkspaceAction::ToggleLeftPanel,
target,
ctx,
),
ActionKind::SurfaceVerticalTabsOpen => surface_workspace_action(
instance_id,
action,
@@ -239,7 +187,7 @@ fn window_create(
let params = decode_params::<TabCreateParams>(params)?;
match params.tab_type {
None | Some(TabType::Terminal | TabType::Default) => {}
Some(TabType::Agent | TabType::CloudAgent) => {
Some(TabType::Agent) => {
return Err(ControlError::new(
ErrorCode::UnsupportedAction,
"window.create only supports terminal or default window types",
@@ -695,7 +643,7 @@ fn settings_section(page: String) -> Result<SettingsSection, ControlError> {
if section == SettingsSection::WarpDrive {
return Err(ControlError::new(
ErrorCode::UnsupportedAction,
"surface.settings.open does not open Warp Drive settings",
"surface.settings.open does not open Galaxy Drive settings",
));
}
Ok(section)
@@ -715,7 +663,7 @@ fn surface_palette_open(
action_kind,
WorkspaceAction::OpenPalette {
mode,
source: PaletteSource::Keybinding,
source: PaletteSource::LocalControl,
query,
},
target,
@@ -16,19 +16,19 @@ fn staged_input_rejects_line_breaks_and_control_sequences() {
#[test]
fn unavailable_surface_open_returns_structured_error() {
let flag_guard = FeatureFlag::AgentManagementView.override_enabled(false);
let flag_guard = FeatureFlag::VerticalTabs.override_enabled(false);
warpui::App::test((), |mut app| async move {
let error = app
.update(|ctx| {
ensure_surface_available(
ActionKind::SurfaceAgentManagementOpen,
SurfaceDestination::AgentManagement,
ActionKind::SurfaceVerticalTabsOpen,
SurfaceDestination::VerticalTabs,
ctx,
)
})
.expect_err("disabled surface is rejected");
assert_eq!(error.code, ErrorCode::UnsupportedAction);
assert!(error.message.contains("surface.agent_management.open"));
assert!(error.message.contains("surface.vertical_tabs.open"));
});
drop(flag_guard);
}
+2 -6
View File
@@ -101,7 +101,7 @@ fn tab_create_action(
) -> Result<WorkspaceAction, ControlError> {
let params = decode_params::<TabCreateParams>(params)?;
if let Some(shell_name) = params.shell.as_deref() {
if matches!(params.tab_type, Some(TabType::Agent | TabType::CloudAgent)) {
if matches!(params.tab_type, Some(TabType::Agent)) {
return Err(ControlError::new(
ErrorCode::InvalidParams,
"tab.create cannot combine an agent tab type with a shell",
@@ -109,7 +109,7 @@ fn tab_create_action(
}
return Ok(WorkspaceAction::AddTabWithShell {
shell: resolve_shell(shell_name, ctx)?,
source: AddTabWithShellSource::CommandPalette,
source: AddTabWithShellSource::LocalControl,
});
}
match params.tab_type {
@@ -118,10 +118,6 @@ fn tab_create_action(
}),
Some(TabType::Agent) => Ok(WorkspaceAction::AddAgentTab),
Some(TabType::Default) => Ok(WorkspaceAction::AddDefaultTab),
Some(TabType::CloudAgent) => Err(ControlError::new(
ErrorCode::UnsupportedAction,
"tab.create does not support cloud-agent tabs",
)),
}
}
+9 -58
View File
@@ -15,12 +15,11 @@ use serde_json::{json, Value};
use settings::Setting as _;
use warpui::{AppContext, ModelContext, SingletonEntity, ViewHandle, WindowId};
use crate::drive::settings::WarpDriveSettings;
use crate::features::FeatureFlag;
use crate::local_control::resolver::{reject_target_families, require_active_window_id_for_action};
use crate::local_control::LocalControlBridge;
use crate::pane_group::{PaneGroup, PaneId};
use crate::settings::{AISettings, CodeSettings};
use crate::settings::CodeSettings;
use crate::workspace::tab_settings::TabSettings;
use crate::workspace::Workspace;
@@ -135,17 +134,11 @@ pub(crate) enum SurfaceDestination {
CommandSearch,
ThemePicker,
Keybindings,
WarpDrive,
ResourceCenter,
AiAssistant,
CodeReview,
ProjectExplorer,
GlobalSearch,
ConversationList,
LeftPanel,
RightPanel,
VerticalTabs,
AgentManagement,
}
impl SurfaceDestination {
@@ -155,17 +148,11 @@ impl SurfaceDestination {
Self::CommandSearch,
Self::ThemePicker,
Self::Keybindings,
Self::WarpDrive,
Self::ResourceCenter,
Self::AiAssistant,
Self::CodeReview,
Self::ProjectExplorer,
Self::GlobalSearch,
Self::ConversationList,
Self::LeftPanel,
Self::RightPanel,
Self::VerticalTabs,
Self::AgentManagement,
];
fn name(self) -> &'static str {
@@ -175,17 +162,11 @@ impl SurfaceDestination {
Self::CommandSearch => "command_search",
Self::ThemePicker => "theme_picker",
Self::Keybindings => "keybindings",
Self::WarpDrive => "warp_drive",
Self::ResourceCenter => "resource_center",
Self::AiAssistant => "ai_assistant",
Self::CodeReview => "code_review",
Self::ProjectExplorer => "project_explorer",
Self::GlobalSearch => "global_search",
Self::ConversationList => "conversation_list",
Self::LeftPanel => "left_panel",
Self::RightPanel => "right_panel",
Self::VerticalTabs => "vertical_tabs",
Self::AgentManagement => "agent_management",
}
}
}
@@ -197,7 +178,9 @@ pub(crate) fn instance(
action: ActionKind::InstanceList.as_str(),
instance_id: instance_id.as_ref().map(|id| id.0.as_str()),
pid: std::process::id(),
channel: ChannelState::channel().to_string(),
channel: ChannelState::channel()
.local_control_channel_name()
.to_owned(),
app_id: ChannelState::app_id().to_string(),
protocol_version: PROTOCOL_VERSION,
actions: ActionKind::implemented_metadata(),
@@ -218,7 +201,9 @@ pub(crate) fn version(instance_id: &Option<InstanceId>) -> Result<serde_json::Va
action: ActionKind::AppVersion.as_str(),
instance_id: instance_id.as_ref().map(|id| id.0.as_str()),
protocol_version: PROTOCOL_VERSION,
channel: ChannelState::channel().to_string(),
channel: ChannelState::channel()
.local_control_channel_name()
.to_owned(),
app_id: ChannelState::app_id().to_string(),
})
}
@@ -241,7 +226,7 @@ pub(crate) fn inspect(
"action": ActionKind::InstanceInspect.as_str(),
"instance_id": instance_id.as_ref().map(|id| id.0.as_str()),
"pid": std::process::id(),
"channel": ChannelState::channel().to_string(),
"channel": ChannelState::channel().local_control_channel_name(),
"app_id": ChannelState::app_id().to_string(),
"protocol_version": PROTOCOL_VERSION,
"active": active_chain(instance_id, ctx)?,
@@ -310,16 +295,7 @@ pub(crate) fn surface_unavailable_reason(
| SurfaceDestination::CommandPalette
| SurfaceDestination::CommandSearch
| SurfaceDestination::ThemePicker
| SurfaceDestination::Keybindings
| SurfaceDestination::ResourceCenter => None,
SurfaceDestination::WarpDrive if !WarpDriveSettings::is_warp_drive_enabled(ctx) => {
Some("Warp Drive is disabled")
}
SurfaceDestination::WarpDrive => None,
SurfaceDestination::AiAssistant if !AISettings::as_ref(ctx).is_any_ai_enabled(ctx) => {
Some("AI features are disabled")
}
SurfaceDestination::AiAssistant => None,
| SurfaceDestination::Keybindings => None,
SurfaceDestination::CodeReview | SurfaceDestination::RightPanel
if !cfg!(feature = "local_fs") =>
{
@@ -341,24 +317,6 @@ pub(crate) fn surface_unavailable_reason(
Some("global search is unavailable or disabled")
}
SurfaceDestination::GlobalSearch => None,
SurfaceDestination::ConversationList
if !FeatureFlag::AgentViewConversationListView.is_enabled()
|| !AISettings::as_ref(ctx).is_any_ai_enabled(ctx)
|| !*AISettings::as_ref(ctx).show_conversation_history.value() =>
{
Some("agent conversation history is unavailable or disabled")
}
SurfaceDestination::ConversationList => None,
SurfaceDestination::LeftPanel
if surface_unavailable_reason(SurfaceDestination::ProjectExplorer, ctx).is_some()
&& surface_unavailable_reason(SurfaceDestination::GlobalSearch, ctx).is_some()
&& surface_unavailable_reason(SurfaceDestination::ConversationList, ctx)
.is_some()
&& surface_unavailable_reason(SurfaceDestination::WarpDrive, ctx).is_some() =>
{
Some("the left panel has no available views")
}
SurfaceDestination::LeftPanel => None,
SurfaceDestination::VerticalTabs
if !FeatureFlag::VerticalTabs.is_enabled()
|| !*TabSettings::as_ref(ctx).use_vertical_tabs.value() =>
@@ -366,13 +324,6 @@ pub(crate) fn surface_unavailable_reason(
Some("vertical tabs are unavailable or disabled")
}
SurfaceDestination::VerticalTabs => None,
SurfaceDestination::AgentManagement
if !FeatureFlag::AgentManagementView.is_enabled()
|| !AISettings::as_ref(ctx).is_any_ai_enabled(ctx) =>
{
Some("agent management is unavailable or disabled")
}
SurfaceDestination::AgentManagement => None,
}
}
@@ -2,14 +2,12 @@ use super::{surface_unavailable_reason, SurfaceDestination};
use crate::features::FeatureFlag;
#[test]
fn agent_management_surface_reports_feature_flag_unavailable() {
let flag_guard = FeatureFlag::AgentManagementView.override_enabled(false);
fn vertical_tabs_surface_reports_feature_flag_unavailable() {
let flag_guard = FeatureFlag::VerticalTabs.override_enabled(false);
warpui::App::test((), |mut app| async move {
assert_eq!(
app.update(|ctx| {
surface_unavailable_reason(SurfaceDestination::AgentManagement, ctx)
}),
Some("agent management is unavailable or disabled")
app.update(|ctx| surface_unavailable_reason(SurfaceDestination::VerticalTabs, ctx)),
Some("vertical tabs are unavailable or disabled")
);
});
drop(flag_guard);
+14 -12
View File
@@ -1,7 +1,7 @@
//! Running app-side server for local Warp control requests.
//! Running app-side server for local Galaxy control requests.
//!
//! This module owns the in-process listener, discovery registration, credential
//! broker socket, and request handoff from Axum into the WarpUI model graph.
//! broker socket, and request handoff from Axum into the GalaxyUI model graph.
//! It complements `crates/local_control/src/discovery.rs`: that shared module
//! defines how clients find and validate candidate instances, while this module
//! creates the app-owned endpoints and publishes their routing metadata through
@@ -25,7 +25,7 @@
//! [0600 socket + kernel-reported peer UID]
//! |
//! v
//! feature flag + Settings > Scripting gate
//! feature flag + Settings > Galaxy Control gate
//! + protocol + exact action metadata
//! |
//! v
@@ -52,11 +52,11 @@
//! application: malicious software already running as the same user remains
//! outside this boundary.
//!
//! The Settings > Scripting gates used here are local-only settings backed by
//! Warp's secure storage provider.
//! The Settings > Galaxy Control gate used here is a local-only setting backed by
//! Galaxy's secure storage provider.
//!
//! Discovery records never include raw bearer tokens: discovery only exposes
//! endpoint metadata and credential broker references while Scripting is enabled.
//! endpoint metadata and credential broker references while Galaxy Control is enabled.
mod bridge;
mod handlers;
mod permissions;
@@ -111,7 +111,7 @@ struct ControlServerState {
expected_host: String,
credentials: Arc<Mutex<HashMap<String, CredentialGrant>>>,
}
/// Process-local publisher, credential broker, and HTTP server for one Warp instance.
/// Process-local publisher, credential broker, and HTTP server for one Galaxy instance.
///
/// Holding the runtime and registration keeps both listeners and the discovery
/// route alive. Dropping them stops request handling and removes the app's
@@ -151,7 +151,7 @@ impl LocalControlServer {
/// Starts, refreshes, or removes local-control publication as settings change.
fn refresh_for_settings(&mut self, ctx: &mut ModelContext<Self>) -> Result<(), ControlError> {
if !permissions::warp_control_cli_enabled() {
if !permissions::galaxy_control_cli_enabled() {
self.stop(ctx);
return Ok(());
}
@@ -287,7 +287,7 @@ impl LocalControlServer {
/// Builds routing metadata without embedding any bearer credential or secret.
///
/// The endpoint and derived broker reference are published only while the
/// protected Scripting setting permits clients to use them.
/// protected Galaxy Control setting permits clients to use them.
fn discovery_record_for_settings(
ctx: &ModelContext<LocalControlServer>,
control_endpoint: ControlEndpoint,
@@ -297,7 +297,9 @@ fn discovery_record_for_settings(
.then_some(control_endpoint);
InstanceRecord::for_current_process(
endpoint,
ChannelState::channel().to_string(),
ChannelState::channel()
.local_control_channel_name()
.to_owned(),
ChannelState::app_id().to_string(),
ChannelState::app_version().map(str::to_owned),
ActionKind::implemented_metadata(),
@@ -403,9 +405,9 @@ async fn handle_credential_broker_connection(
}
#[cfg(unix)]
/// Requires the kernel-reported peer UID to match Warp's effective UID.
/// Requires the kernel-reported peer UID to match Galaxy's effective UID.
///
/// This excludes other OS users but does not distinguish trusted Warp code from
/// This excludes other OS users but does not distinguish trusted Galaxy code from
/// arbitrary processes already running as the same user.
fn ensure_same_user_peer(stream: &tokio::net::UnixStream) -> Result<(), ControlError> {
ensure_peer_uid(stream, unsafe { libc::geteuid() })
+3 -3
View File
@@ -153,7 +153,7 @@ fn surface_list_rejects_target_selectors() {
#[test]
fn capabilities_advertises_the_complete_catalog() {
assert_eq!(capabilities().len(), 84);
assert_eq!(capabilities().len(), 77);
}
#[test]
@@ -223,7 +223,7 @@ fn missing_window_index_returns_missing_target() {
#[test]
fn feature_flag_disabled_denies_local_control() {
let _flag = FeatureFlag::WarpControlCli.override_enabled(false);
let _flag = FeatureFlag::GalaxyControlCli.override_enabled(false);
let err = ensure_feature_enabled().expect_err("feature flag disabled");
assert_eq!(err.code, ErrorCode::LocalControlDisabled);
}
@@ -379,7 +379,7 @@ fn expired_credential_is_rejected_and_pruned_before_request_decode() {
#[test]
fn disabling_scripting_invalidates_existing_grant_and_prevents_new_grants() {
let _flag = FeatureFlag::WarpControlCli.override_enabled(true);
let _flag = FeatureFlag::GalaxyControlCli.override_enabled(true);
warpui::App::test((), |mut app| async move {
crate::test_util::settings::initialize_settings_for_tests(&mut app);
app.update(|ctx| {
+4 -4
View File
@@ -6,8 +6,8 @@ use crate::features::FeatureFlag;
use crate::local_control::LocalControlBridge;
use crate::settings::LocalControlSettings;
pub(super) fn warp_control_cli_enabled() -> bool {
FeatureFlag::WarpControlCli.is_enabled()
pub(super) fn galaxy_control_cli_enabled() -> bool {
FeatureFlag::GalaxyControlCli.is_enabled()
}
pub(super) fn ensure_protocol_version(protocol_version: u32) -> Result<(), ControlError> {
@@ -21,12 +21,12 @@ pub(super) fn ensure_protocol_version(protocol_version: u32) -> Result<(), Contr
}
pub(super) fn ensure_feature_enabled() -> Result<(), ControlError> {
if warp_control_cli_enabled() {
if galaxy_control_cli_enabled() {
return Ok(());
}
Err(ControlError::new(
ErrorCode::LocalControlDisabled,
"Warp control CLI is disabled by feature flag",
"Galaxy Control CLI is disabled by feature flag",
))
}
+2 -2
View File
@@ -129,7 +129,7 @@ pub(crate) fn require_active_window_id_for_action(
active_window.ok_or_else(|| {
ControlError::new(
ErrorCode::MissingTarget,
format!("{} requires an active Warp window", action.as_str()),
format!("{} requires an active Galaxy window", action.as_str()),
)
})
}
@@ -148,7 +148,7 @@ fn active_or_single_window_id(
_ => Err(ControlError::new(
ErrorCode::AmbiguousTarget,
format!(
"{} requires an explicit window selector when no Warp window is active",
"{} requires an explicit window selector when no Galaxy window is active",
action.as_str()
),
)),