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
+10 -1
View File
@@ -2,6 +2,8 @@
use ai::agent::convert::ConvertToAPITypeError;
use anyhow::anyhow;
use base64::engine::general_purpose;
use base64::Engine as _;
use chrono::{DateTime, Local, Timelike};
use warp_multi_agent_api as api;
@@ -763,8 +765,15 @@ fn convert_context(context: &[AIAgentContext]) -> api::InputContext {
});
}
AIAgentContext::Image(image_context) => {
let Ok(data) = general_purpose::STANDARD.decode(&image_context.data) else {
log::warn!(
"Skipping AI image context with invalid base64 data (mime_type={})",
image_context.mime_type
);
continue;
};
api_context.images.push(api::input_context::Image {
data: image_context.data.into(),
data,
mime_type: image_context.mime_type,
});
}
+27 -1
View File
@@ -4,11 +4,37 @@ use warp_multi_agent_api as api;
use crate::ai::agent::task::TaskId;
use crate::ai::agent::{
AIAgentActionResult, AIAgentActionResultType, AIAgentContext,
AIAgentActionResult, AIAgentActionResultType, AIAgentContext, ImageContext,
TransferShellCommandControlToUserResult,
};
use crate::terminal::model::block::BlockId;
#[test]
fn image_context_decodes_base64_into_proto_bytes() {
let api_context = super::convert_context(&[AIAgentContext::Image(ImageContext {
data: "AQIDBA==".to_string(),
mime_type: "image/png".to_string(),
file_name: "test.png".to_string(),
is_figma: false,
})]);
assert_eq!(api_context.images.len(), 1);
assert_eq!(api_context.images[0].data, vec![1, 2, 3, 4]);
assert_eq!(api_context.images[0].mime_type, "image/png");
}
#[test]
fn image_context_skips_invalid_base64() {
let api_context = super::convert_context(&[AIAgentContext::Image(ImageContext {
data: "not base64".to_string(),
mime_type: "image/png".to_string(),
file_name: "test.png".to_string(),
is_figma: false,
})]);
assert_eq!(api_context.images, vec![]);
}
#[test]
fn git_context_converts_repository_and_pull_request_metadata() {
let context = vec![
+10 -35
View File
@@ -10,21 +10,20 @@ use super::{ConvertToAPITypeError, RequestParams, ResponseStream};
use crate::ai::agent::redaction;
use crate::ai::openai::translator as openai_translator;
use crate::ai::provider::ProviderConfig;
use crate::server::server_api::ai::AIClient;
use crate::server::server_api::AIApiError;
use crate::terminal::model::session::SessionType;
pub async fn generate_multi_agent_output(
provider_config: ProviderConfig,
server_api: Arc<dyn AIClient>,
mut params: RequestParams,
cancellation_rx: futures::channel::oneshot::Receiver<()>,
) -> Result<ResponseStream, ConvertToAPITypeError> {
let supported_tools = params
.supported_tools_override
.take()
let supported_tools_override = params.supported_tools_override.take();
let supported_tools = supported_tools_override
.clone()
.unwrap_or_else(|| get_supported_tools(&params));
let supported_cli_agent_tools = get_supported_cli_agent_tools(&params);
let supported_cli_agent_tools =
supported_tools_override.unwrap_or_else(|| get_supported_cli_agent_tools(&params));
let mut logging_metadata = HashMap::new();
if let Some(metadata) = params.metadata {
logging_metadata.insert(
@@ -83,7 +82,10 @@ pub async fn generate_multi_agent_output(
supports_todos_ui: true,
supports_linked_code_blocks: FeatureFlag::LinkedCodeBlocks.is_enabled(),
supports_started_child_task_message: true,
supports_suggest_prompt: true,
// Galaxy's direct providers only receive tools with local schemas and
// executors. Hosted-only suggestion/orchestration capability bits must
// remain false so models do not plan around unavailable Warp services.
supports_suggest_prompt: false,
supports_read_image_files: FeatureFlag::ReadImageFiles.is_enabled(),
supports_reasoning_message: true,
api_keys: params.api_keys,
@@ -99,7 +101,7 @@ pub async fn generate_multi_agent_output(
FeatureFlag::SummarizationViaMessageReplacement.is_enabled(),
supports_bundled_skills: FeatureFlag::BundledSkills.is_enabled(),
supports_research_agent: params.research_agent_enabled,
supports_orchestration_v2: supports_orchestration_v2(params.orchestration_enabled),
supports_orchestration_v2: false,
supports_background_computer_use: FeatureFlag::BackgroundComputerUse.is_enabled()
&& computer_use::background_supported(),
custom_model_providers: params.custom_model_providers,
@@ -212,9 +214,6 @@ pub async fn generate_multi_agent_output(
}
}
fn supports_orchestration_v2(orchestration_enabled: bool) -> bool {
orchestration_enabled
}
fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
let mut supported_tools = vec![
api::ToolType::Grep,
@@ -222,17 +221,13 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
api::ToolType::FileGlobV2,
api::ToolType::ReadMcpResource,
api::ToolType::CallMcpTool,
api::ToolType::InitProject,
api::ToolType::OpenCodeReview,
api::ToolType::RunShellCommand,
api::ToolType::SuggestNewConversation,
api::ToolType::Subagent,
api::ToolType::WriteToLongRunningShellCommand,
api::ToolType::ReadShellCommandOutput,
api::ToolType::ReadDocuments,
api::ToolType::CreateDocuments,
api::ToolType::EditDocuments,
api::ToolType::SuggestPrompt,
];
if FeatureFlag::ConversationsAsContext.is_enabled() {
@@ -246,10 +241,6 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
api::ToolType::ApplyFileDiffs,
api::ToolType::SearchCodebase,
]);
if FeatureFlag::ArtifactCommand.is_enabled() {
supported_tools.push(api::ToolType::UploadFileArtifact);
}
}
Some(SessionType::WarpifiedRemote { host_id: Some(_) }) => {
// Remote session with a known host — enable tools that route
@@ -264,26 +255,10 @@ fn get_supported_tools(params: &RequestParams) -> Vec<api::ToolType> {
Some(SessionType::WarpifiedRemote { host_id: None }) => {}
}
if FeatureFlag::AgentModeComputerUse.is_enabled() && params.computer_use_enabled {
supported_tools.extend(&[api::ToolType::UseComputer]);
supported_tools.extend(&[api::ToolType::RequestComputerUse])
}
if FeatureFlag::PRCommentsSlashCommand.is_enabled() {
supported_tools.push(api::ToolType::InsertReviewComments);
}
if FeatureFlag::ListSkills.is_enabled() {
supported_tools.push(api::ToolType::ReadSkill);
}
if params.orchestration_enabled {
supported_tools.extend([api::ToolType::RunAgents, api::ToolType::SendMessageToAgent]);
// Declare client-handled wait_for_events so the server doesn't
// fall back to the legacy server-handled form.
supported_tools.push(api::ToolType::WaitForEvents);
}
if FeatureFlag::AskUserQuestion.is_enabled() && params.ask_user_question_enabled {
supported_tools.push(api::ToolType::AskUserQuestion);
}
+26 -34
View File
@@ -2,7 +2,7 @@ use galaxy_core::features::FeatureFlag;
use galaxy_core::HostId;
use warp_multi_agent_api as api;
use super::{get_supported_cli_agent_tools, get_supported_tools, supports_orchestration_v2};
use super::{get_supported_cli_agent_tools, get_supported_tools};
use crate::ai::agent::api::RequestParams;
use crate::ai::blocklist::SessionContext;
use crate::ai::llms::LLMId;
@@ -62,34 +62,44 @@ fn request_params_for_remote(host_id: Option<HostId>) -> RequestParams {
}
#[test]
fn supports_orchestration_v2_matches_request_orchestration_setting() {
assert!(supports_orchestration_v2(true));
assert!(!supports_orchestration_v2(false));
}
#[test]
fn supported_tools_include_orchestration_tools_when_orchestration_enabled() {
fn supported_tools_expose_local_subagents_without_hosted_orchestration_tools() {
let mut params = request_params_with_ask_user_question_enabled(false);
params.orchestration_enabled = true;
let supported_tools = get_supported_tools(&params);
assert!(supported_tools.contains(&api::ToolType::RunAgents));
assert!(supported_tools.contains(&api::ToolType::SendMessageToAgent));
assert!(!supported_tools.contains(&api::ToolType::StartAgent));
assert!(supported_tools.contains(&api::ToolType::Subagent));
assert!(!supported_tools.contains(&api::ToolType::RunAgents));
assert!(!supported_tools.contains(&api::ToolType::SendMessageToAgent));
assert!(!supported_tools.contains(&api::ToolType::WaitForEvents));
assert!(!supported_tools.contains(&api::ToolType::StartAgentV2));
}
#[test]
fn supported_tools_omit_orchestration_tools_when_orchestration_disabled() {
fn supported_tools_omit_hosted_only_capabilities() {
let params = request_params_with_ask_user_question_enabled(false);
let supported_tools = get_supported_tools(&params);
assert!(!supported_tools.contains(&api::ToolType::RunAgents));
assert!(!supported_tools.contains(&api::ToolType::SendMessageToAgent));
assert!(!supported_tools.contains(&api::ToolType::StartAgent));
assert!(!supported_tools.contains(&api::ToolType::StartAgentV2));
for hosted_only_tool in [
api::ToolType::InitProject,
api::ToolType::OpenCodeReview,
api::ToolType::SuggestNewConversation,
api::ToolType::SuggestPrompt,
api::ToolType::UploadFileArtifact,
api::ToolType::UseComputer,
api::ToolType::RequestComputerUse,
api::ToolType::InsertReviewComments,
api::ToolType::RunAgents,
api::ToolType::SendMessageToAgent,
api::ToolType::WaitForEvents,
] {
assert!(
!supported_tools.contains(&hosted_only_tool),
"{hosted_only_tool:?} has no direct-provider tool schema"
);
}
}
#[test]
fn supported_tools_omits_ask_user_question_when_disabled() {
let params = request_params_with_ask_user_question_enabled(false);
@@ -110,24 +120,6 @@ fn supported_tools_includes_ask_user_question_when_enabled_and_feature_flag_is_e
assert!(supported_tools.contains(&api::ToolType::AskUserQuestion));
}
#[test]
fn supported_tools_include_upload_artifact_when_feature_flag_is_enabled() {
let _flag = FeatureFlag::ArtifactCommand.override_enabled(true);
let params = request_params_with_ask_user_question_enabled(false);
let supported_tools = get_supported_tools(&params);
assert!(supported_tools.contains(&api::ToolType::UploadFileArtifact));
}
#[test]
fn supported_tools_omit_upload_artifact_when_feature_flag_is_disabled() {
let _flag = FeatureFlag::ArtifactCommand.override_enabled(false);
let params = request_params_with_ask_user_question_enabled(false);
let supported_tools = get_supported_tools(&params);
assert!(!supported_tools.contains(&api::ToolType::UploadFileArtifact));
}
#[test]
fn remote_supported_tools_include_search_codebase_when_connected_and_feature_flag_is_enabled() {
let _flag = FeatureFlag::RemoteCodebaseIndexing.override_enabled(true);