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:
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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,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(¶ms));
|
||||
let supported_cli_agent_tools = get_supported_cli_agent_tools(¶ms);
|
||||
let supported_cli_agent_tools =
|
||||
supported_tools_override.unwrap_or_else(|| get_supported_cli_agent_tools(¶ms));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
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(¶ms);
|
||||
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user