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:
@@ -94,7 +94,7 @@ impl ScopedCredential {
|
||||
}
|
||||
}
|
||||
|
||||
/// Authorization grant issued by the localhost server running inside Warp for a
|
||||
/// Authorization grant issued by the localhost server running inside Galaxy for a
|
||||
/// single action.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CredentialGrant {
|
||||
@@ -135,7 +135,7 @@ impl CredentialGrant {
|
||||
if &self.instance_id != instance_id {
|
||||
return Err(ControlError::new(
|
||||
ErrorCode::UnauthorizedLocalClient,
|
||||
"local-control credential belongs to a different Warp instance",
|
||||
"local-control credential belongs to a different Galaxy instance",
|
||||
));
|
||||
}
|
||||
if self.action != action {
|
||||
|
||||
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const PROTOCOL_VERSION: u32 = 1;
|
||||
|
||||
/// Level of Warp hierarchy or orthogonal product noun an action targets.
|
||||
/// Level of Galaxy hierarchy or orthogonal product noun an action targets.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TargetScope {
|
||||
@@ -111,7 +111,7 @@ macro_rules! define_action_catalog {
|
||||
),+ $(,)?
|
||||
}
|
||||
)+ $(,)?) => {
|
||||
/// Stable protocol name for every approved `warpctrl` action.
|
||||
/// Stable protocol name for every approved `galaxyctrl` action.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub enum ActionKind {
|
||||
$($(#[serde(rename = $name)] $variant,)+)+
|
||||
@@ -274,20 +274,13 @@ define_action_catalog! {
|
||||
SurfaceCommandSearchOpen => { name: "surface.command_search.open", status: Implemented, target: Surface, params: Query, result: Acknowledgement },
|
||||
SurfaceThemePickerOpen => { name: "surface.theme_picker.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceKeybindingsOpen => { name: "surface.keybindings.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceWarpDriveOpen => { name: "surface.warp_drive.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceWarpDriveToggle => { name: "surface.warp_drive.toggle", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceResourceCenterToggle => { name: "surface.resource_center.toggle", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceAiAssistantToggle => { name: "surface.ai_assistant.toggle", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceCodeReviewOpen => { name: "surface.code_review.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceCodeReviewToggle => { name: "surface.code_review.toggle", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceProjectExplorerOpen => { name: "surface.project_explorer.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceGlobalSearchOpen => { name: "surface.global_search.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceConversationListOpen => { name: "surface.conversation_list.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceLeftPanelToggle => { name: "surface.left_panel.toggle", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceRightPanelToggle => { name: "surface.right_panel.toggle", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceVerticalTabsOpen => { name: "surface.vertical_tabs.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceVerticalTabsToggle => { name: "surface.vertical_tabs.toggle", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
SurfaceAgentManagementOpen => { name: "surface.agent_management.open", status: Implemented, target: Surface, params: None, result: Acknowledgement },
|
||||
}
|
||||
|
||||
file {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Blocking client helpers used by the standalone `warpctrl` CLI.
|
||||
//! Blocking client helpers used by the standalone `galaxyctrl` CLI.
|
||||
//!
|
||||
//! Authentication is a two-transport flow:
|
||||
//!
|
||||
@@ -14,7 +14,7 @@
|
||||
//! issuing a short-lived, action-scoped credential.
|
||||
//! 4. The client keeps that credential in memory and presents it as a bearer
|
||||
//! token only to the selected instance's loopback HTTP endpoint. The running
|
||||
//! Warp app revalidates the credential, current settings, action scope, and
|
||||
//! Galaxy app revalidates the credential, current settings, action scope, and
|
||||
//! request before dispatch.
|
||||
//!
|
||||
//! Client-side validation prevents accidental use of inconsistent discovery
|
||||
|
||||
@@ -56,7 +56,7 @@ fn probe_rejects_mismatched_instance_identity() {
|
||||
instance_id: InstanceId("inst_expected".to_owned()),
|
||||
pid: std::process::id(),
|
||||
channel: "local".to_owned(),
|
||||
app_id: "dev.warp.WarpLocal".to_owned(),
|
||||
app_id: "dev.galaxy.GalaxyLocal".to_owned(),
|
||||
app_version: None,
|
||||
started_at: Utc::now(),
|
||||
executable_path: None,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Private filesystem registry for discovering running local Warp instances.
|
||||
//! Private filesystem registry for discovering running local Galaxy instances.
|
||||
//!
|
||||
//! This module answers “which compatible instances are available, and where
|
||||
//! can a client begin authentication?” It does not listen for control requests
|
||||
@@ -17,13 +17,13 @@
|
||||
//! Before following a record, clients require the endpoint host to be exactly
|
||||
//! `127.0.0.1` and the broker filename to be derived from the instance ID. A
|
||||
//! discovery scan also rejects incompatible records, prunes dead PIDs, and
|
||||
//! performs an authenticated `app.ping` probe. When Scripting is disabled,
|
||||
//! performs an authenticated `app.ping` probe. When Galaxy Control is disabled,
|
||||
//! records contain neither an endpoint nor a broker reference.
|
||||
//!
|
||||
//! The owner-only directory, records, and broker sockets protect against other
|
||||
//! OS users. The broker's kernel-reported peer-UID check is the authoritative
|
||||
//! same-user check before credential issuance. Neither mechanism distinguishes
|
||||
//! trusted Warp code from arbitrary software already running as that user.
|
||||
//! trusted Galaxy code from arbitrary software already running as that user.
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
#[cfg(unix)]
|
||||
@@ -38,12 +38,12 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::protocol::{ActionMetadata, ControlError, ErrorCode, PROTOCOL_VERSION};
|
||||
|
||||
const DISCOVERY_DIR_ENV: &str = "WARP_LOCAL_CONTROL_DISCOVERY_DIR";
|
||||
const DISCOVERY_DIR_ENV: &str = "GALAXY_LOCAL_CONTROL_DISCOVERY_DIR";
|
||||
const BROKER_SOCKET_SUFFIX: &str = ".broker.sock";
|
||||
const TEMP_RECORD_SUFFIX: &str = ".json.tmp";
|
||||
const ORPHAN_SOCKET_GRACE_PERIOD: Duration = Duration::from_secs(60);
|
||||
|
||||
/// Stable identifier for one running Warp instance.
|
||||
/// Stable identifier for one running Galaxy instance.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct InstanceId(pub String);
|
||||
@@ -93,7 +93,7 @@ pub struct CredentialBrokerReference {
|
||||
pub socket_path: PathBuf,
|
||||
}
|
||||
|
||||
/// Filesystem-published routing metadata for a running Warp app process.
|
||||
/// Filesystem-published routing metadata for a running Galaxy app process.
|
||||
///
|
||||
/// An enabled record connects the three stages of the protocol: filesystem
|
||||
/// discovery, Unix-socket credential issuance, and authenticated loopback HTTP
|
||||
@@ -282,10 +282,10 @@ pub fn discovery_dir() -> PathBuf {
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
if let Some(path) = std::env::var_os("XDG_RUNTIME_DIR") {
|
||||
return PathBuf::from(path).join("warp").join("local-control");
|
||||
return PathBuf::from(path).join("galaxy").join("local-control");
|
||||
}
|
||||
let home = std::env::var_os("HOME").unwrap_or_else(|| ".".into());
|
||||
PathBuf::from(home).join(".warp").join("local-control")
|
||||
PathBuf::from(home).join(".galaxy").join("local-control")
|
||||
}
|
||||
|
||||
/// Returns compatible live instances from `channel` that pass an authenticated app ping.
|
||||
|
||||
@@ -18,7 +18,7 @@ fn broker_socket_reference_is_bound_to_instance_identity() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -38,7 +38,7 @@ fn registered_instance_round_trips_discovery_record() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -54,7 +54,7 @@ fn incompatible_protocol_record_is_ignored() {
|
||||
let mut record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -119,7 +119,7 @@ fn stale_process_record_is_pruned() {
|
||||
let mut record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -148,7 +148,7 @@ fn multiple_live_process_records_are_discovered() {
|
||||
let mut first_record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -156,7 +156,7 @@ fn multiple_live_process_records_are_discovered() {
|
||||
let mut second_record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4001)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -188,7 +188,7 @@ fn records_from_other_channels_are_ignored() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"dev",
|
||||
"dev.warp.Warp-Dev",
|
||||
"dev.galaxy.Galaxy-Dev",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -206,7 +206,7 @@ fn serialized_discovery_record_does_not_contain_raw_credential_material() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -221,7 +221,7 @@ fn disabled_record_does_not_expose_actionable_authority() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
None,
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -234,7 +234,7 @@ fn rejects_unsafe_or_divergent_discovery_authority() {
|
||||
let mut record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -269,7 +269,7 @@ fn discovery_directory_is_owner_only_on_unix() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
@@ -290,7 +290,7 @@ fn discovery_record_is_owner_only_on_unix() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
"dev.galaxy.GalaxyLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Shared protocol, discovery, authentication, and client types for local Warp control.
|
||||
//! Shared protocol, discovery, authentication, and client types for local Galaxy control.
|
||||
//!
|
||||
//! The `local_control` crate is intentionally UI-agnostic so the Warp app and
|
||||
//! `warpctrl` CLI can share the same wire envelopes, action catalog, discovery
|
||||
//! The `local_control` crate is intentionally UI-agnostic so the Galaxy app and
|
||||
//! `galaxyctrl` CLI can share the same wire envelopes, action catalog, discovery
|
||||
//! records, selectors, and credential validation rules.
|
||||
pub mod auth;
|
||||
pub mod catalog;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Wire protocol envelopes and error types for Warp local control.
|
||||
//! Wire protocol envelopes and error types for Galaxy local control.
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
@@ -30,7 +30,6 @@ pub enum Direction {
|
||||
pub enum TabType {
|
||||
Terminal,
|
||||
Agent,
|
||||
CloudAgent,
|
||||
Default,
|
||||
}
|
||||
|
||||
@@ -89,7 +88,7 @@ pub struct DirectionParams {
|
||||
pub direction: Direction,
|
||||
}
|
||||
|
||||
/// Parameters for opening a file in Warp's app/editor state.
|
||||
/// Parameters for opening a file in Galaxy's app/editor state.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FileOpenParams {
|
||||
@@ -311,7 +310,7 @@ pub enum ControlResult {
|
||||
Content { data: serde_json::Value },
|
||||
}
|
||||
|
||||
/// Top-level request sent by a local-control client to a Warp instance.
|
||||
/// Top-level request sent by a local-control client to a Galaxy instance.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct RequestEnvelope {
|
||||
pub protocol_version: u32,
|
||||
@@ -372,7 +371,7 @@ impl Action {
|
||||
}
|
||||
}
|
||||
|
||||
/// Top-level response returned by a Warp instance for a control request.
|
||||
/// Top-level response returned by a Galaxy instance for a control request.
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct ResponseEnvelope {
|
||||
pub protocol_version: u32,
|
||||
|
||||
@@ -151,14 +151,26 @@ fn malformed_and_removed_action_names_are_not_deserialized() {
|
||||
"drive.object.insert",
|
||||
"drive.object.share_to_team",
|
||||
"drive.workflow.run",
|
||||
"surface.warp_drive.open",
|
||||
"surface.warp_drive.toggle",
|
||||
"surface.resource_center.toggle",
|
||||
"surface.ai_assistant.toggle",
|
||||
"surface.conversation_list.open",
|
||||
"surface.agent_management.open",
|
||||
"surface.left_panel.toggle",
|
||||
] {
|
||||
assert!(serde_json::from_value::<ActionKind>(serde_json::json!(action)).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catalog_has_exactly_84_retained_actions() {
|
||||
assert_eq!(ActionKind::ALL.len(), 84);
|
||||
fn removed_cloud_agent_tab_type_is_not_deserialized() {
|
||||
assert!(serde_json::from_value::<TabType>(serde_json::json!("cloud_agent")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn catalog_has_exactly_77_retained_actions() {
|
||||
assert_eq!(ActionKind::ALL.len(), 77);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -184,18 +196,10 @@ fn direct_surface_actions_have_stable_names() {
|
||||
ActionKind::SurfaceGlobalSearchOpen.as_str(),
|
||||
"surface.global_search.open"
|
||||
);
|
||||
assert_eq!(
|
||||
ActionKind::SurfaceConversationListOpen.as_str(),
|
||||
"surface.conversation_list.open"
|
||||
);
|
||||
assert_eq!(
|
||||
ActionKind::SurfaceVerticalTabsOpen.as_str(),
|
||||
"surface.vertical_tabs.open"
|
||||
);
|
||||
assert_eq!(
|
||||
ActionKind::SurfaceAgentManagementOpen.as_str(),
|
||||
"surface.agent_management.open"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use crate::discovery::{InstanceId, InstanceRecord};
|
||||
use crate::protocol::{ControlError, ErrorCode};
|
||||
|
||||
/// CLI-level selector for choosing one discovered Warp instance.
|
||||
/// CLI-level selector for choosing one discovered Galaxy instance.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum InstanceSelector {
|
||||
Active,
|
||||
@@ -23,7 +23,7 @@ pub fn select_instance(
|
||||
.ok_or_else(|| {
|
||||
ControlError::new(
|
||||
ErrorCode::NoInstance,
|
||||
format!("no Warp instance with id {}", instance_id.0),
|
||||
format!("no Galaxy instance with id {}", instance_id.0),
|
||||
)
|
||||
}),
|
||||
InstanceSelector::Pid(pid) => records
|
||||
@@ -33,7 +33,7 @@ pub fn select_instance(
|
||||
.ok_or_else(|| {
|
||||
ControlError::new(
|
||||
ErrorCode::NoInstance,
|
||||
format!("no Warp instance with pid {pid}"),
|
||||
format!("no Galaxy instance with pid {pid}"),
|
||||
)
|
||||
}),
|
||||
}
|
||||
@@ -43,12 +43,12 @@ fn select_active(records: &[InstanceRecord]) -> Result<InstanceRecord, ControlEr
|
||||
match records {
|
||||
[] => Err(ControlError::new(
|
||||
ErrorCode::NoInstance,
|
||||
"no local Warp control instances were discovered",
|
||||
"no local Galaxy instances with Galaxy Control enabled were discovered",
|
||||
)),
|
||||
[record] => Ok(record.clone()),
|
||||
_ => Err(ControlError::new(
|
||||
ErrorCode::AmbiguousInstance,
|
||||
"multiple local Warp control instances were discovered; pass --instance",
|
||||
"multiple local Galaxy instances with Galaxy Control enabled were discovered; pass --instance",
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ fn record(id: &str, pid: u32) -> InstanceRecord {
|
||||
instance_id: InstanceId(id.to_owned()),
|
||||
pid,
|
||||
channel: "local".to_owned(),
|
||||
app_id: "dev.warp.WarpLocal".to_owned(),
|
||||
app_id: "dev.galaxy.GalaxyLocal".to_owned(),
|
||||
app_version: None,
|
||||
started_at: Utc::now(),
|
||||
executable_path: None,
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
//! Serializable selectors for targeting windows, tabs, and panes.
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Opaque window identifier supplied by Warp metadata.
|
||||
/// Opaque window identifier supplied by Galaxy metadata.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct WindowSelector(pub String);
|
||||
|
||||
/// Opaque tab identifier supplied by Warp metadata.
|
||||
/// Opaque tab identifier supplied by Galaxy metadata.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct TabSelector(pub String);
|
||||
|
||||
/// Opaque pane identifier supplied by Warp metadata.
|
||||
/// Opaque pane identifier supplied by Galaxy metadata.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct PaneSelector(pub String);
|
||||
|
||||
/// Opaque session identifier supplied by Warp metadata.
|
||||
/// Opaque session identifier supplied by Galaxy metadata.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct SessionSelector(pub String);
|
||||
|
||||
/// Hierarchical target for actions that operate on a specific Warp surface.
|
||||
/// Hierarchical target for actions that operate on a specific Galaxy surface.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct TargetSelector {
|
||||
|
||||
Reference in New Issue
Block a user