Preserve completed command assessments
This commit is contained in:
@@ -23,6 +23,7 @@ use crate::ai::agent::api::convert_from::{
|
||||
convert_user_query_mode, ConversionParams, ConvertAPIMessageToClientOutputMessage,
|
||||
MaybeAIAgentOutputMessage,
|
||||
};
|
||||
use crate::ai::agent::api::is_internal_command_completion_assessment;
|
||||
use crate::ai::agent::conversation::{
|
||||
update_todo_list_from_todo_op, AIConversation, AIConversationId, ServerAIConversationMetadata,
|
||||
};
|
||||
@@ -389,17 +390,21 @@ impl ConvertToExchanges for &api::Task {
|
||||
|
||||
let added_message_as_exchange_input = match message {
|
||||
api::message::Message::UserQuery(user_query) => {
|
||||
// Add user query as input
|
||||
current_inputs.push(AIAgentInput::UserQuery {
|
||||
if is_internal_command_completion_assessment(api_message) {
|
||||
false
|
||||
} else {
|
||||
// Add user query as input
|
||||
current_inputs.push(AIAgentInput::UserQuery {
|
||||
query: user_query.query.clone(),
|
||||
context: convert_input_context(user_query.context.as_ref()),
|
||||
static_query_type: None,
|
||||
referenced_attachments: HashMap::new(),
|
||||
user_query_mode: convert_user_query_mode(user_query.mode.as_ref()),
|
||||
running_command: None,
|
||||
intended_agent: Some(user_query.intended_agent()),
|
||||
});
|
||||
true
|
||||
intended_agent: Some(user_query.intended_agent()),
|
||||
});
|
||||
true
|
||||
}
|
||||
}
|
||||
api::message::Message::SystemQuery(query) => {
|
||||
let Some(query_type) = &query.r#type else {
|
||||
|
||||
@@ -4,7 +4,7 @@ use chrono::Utc;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::agent::api::convert_conversation::*;
|
||||
use crate::ai::agent::api::ServerConversationToken;
|
||||
use crate::ai::agent::api::{mark_internal_command_completion_assessment, ServerConversationToken};
|
||||
use crate::ai::agent::conversation::{
|
||||
AIAgentHarness, AIConversationId, ServerAIConversationMetadata,
|
||||
};
|
||||
@@ -2129,6 +2129,61 @@ fn test_create_then_edit_then_create_version_tracking() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_internal_command_completion_assessment_restores_output_without_visible_input() {
|
||||
let assessment_text =
|
||||
"[Completed command: cargo test]\n[Final terminal output:\ntest result: ok\n]";
|
||||
let mut hidden_assessment = api::Message {
|
||||
id: "msg_assessment".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
request_id: "req1".to_string(),
|
||||
message: Some(api::message::Message::UserQuery(api::message::UserQuery {
|
||||
query: assessment_text.to_string(),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
mark_internal_command_completion_assessment(&mut hidden_assessment);
|
||||
let provider_history =
|
||||
crate::ai::bedrock::request_translator::convert_proto_message(&hidden_assessment)
|
||||
.expect("hidden assessment should remain in provider history");
|
||||
assert_eq!(
|
||||
provider_history.role,
|
||||
crate::ai::provider::types::MessageRole::User
|
||||
);
|
||||
assert!(matches!(
|
||||
provider_history.content,
|
||||
crate::ai::provider::types::MessageContent::Text(text) if text == assessment_text
|
||||
));
|
||||
|
||||
let task = api::Task {
|
||||
id: "task1".to_string(),
|
||||
messages: vec![
|
||||
hidden_assessment,
|
||||
api::Message {
|
||||
id: "msg_output".to_string(),
|
||||
task_id: "task1".to_string(),
|
||||
request_id: "req1".to_string(),
|
||||
message: Some(api::message::Message::AgentOutput(
|
||||
api::message::AgentOutput {
|
||||
text: "The command completed successfully.".to_string(),
|
||||
},
|
||||
)),
|
||||
..Default::default()
|
||||
},
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let exchanges = task.into_exchanges();
|
||||
assert_eq!(exchanges.len(), 1);
|
||||
assert!(exchanges[0].input.is_empty());
|
||||
assert_eq!(
|
||||
exchanges[0].format_output_for_copy(None),
|
||||
"The command completed successfully."
|
||||
);
|
||||
}
|
||||
|
||||
/// Verify that a `SystemQuery::HandoffRehydration` message does not produce
|
||||
/// a displayed input when restoring a conversation. It must be treated as
|
||||
/// hidden, so the exchange should have zero user-visible inputs.
|
||||
|
||||
@@ -16,6 +16,7 @@ use warp_multi_agent_api as api;
|
||||
use crate::ai::agent::api::convert_conversation::{
|
||||
convert_input_context, convert_tool_call_result_to_input,
|
||||
};
|
||||
use crate::ai::agent::api::is_internal_command_completion_assessment;
|
||||
use crate::ai::agent::comment::CodeReview;
|
||||
use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::todos::AIAgentTodoList;
|
||||
@@ -957,6 +958,9 @@ pub fn user_inputs_from_messages(messages: &[api::Message]) -> Vec<AIAgentInput>
|
||||
let Some(inner) = &m.message else { continue };
|
||||
match inner {
|
||||
api::message::Message::UserQuery(uq) => {
|
||||
if is_internal_command_completion_assessment(m) {
|
||||
continue;
|
||||
}
|
||||
let context = convert_input_context(uq.context.as_ref());
|
||||
let referenced_attachments = uq
|
||||
.referenced_attachments
|
||||
|
||||
@@ -7,13 +7,14 @@ use warp_multi_agent_api as api;
|
||||
use warp_util::local_or_remote_path::LocalOrRemotePath;
|
||||
|
||||
use super::{
|
||||
convert_api_question, ConversionParams, ConvertAPIMessageToClientOutputMessage,
|
||||
MaybeAIAgentOutputMessage,
|
||||
convert_api_question, user_inputs_from_messages, ConversionParams,
|
||||
ConvertAPIMessageToClientOutputMessage, MaybeAIAgentOutputMessage,
|
||||
};
|
||||
use crate::ai::agent::api::mark_internal_command_completion_assessment;
|
||||
use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::{
|
||||
runtime_activity, AIAgentActionType, AIAgentOutputMessageType, LifecycleEventType,
|
||||
StartAgentExecutionMode,
|
||||
runtime_activity, AIAgentActionType, AIAgentInput, AIAgentOutputMessageType,
|
||||
LifecycleEventType, StartAgentExecutionMode,
|
||||
};
|
||||
|
||||
fn start_agent_tool_call_message(
|
||||
@@ -617,6 +618,36 @@ fn converts_local_start_agent_v2_with_harness_type() {
|
||||
assert_eq!(lifecycle_subscription, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn internal_command_completion_assessment_is_not_restored_as_shared_user_input() {
|
||||
let mut hidden_assessment = api::Message {
|
||||
id: "hidden-assessment".to_string(),
|
||||
task_id: "task".to_string(),
|
||||
message: Some(api::message::Message::UserQuery(api::message::UserQuery {
|
||||
query: "[Completed command: cargo test]".to_string(),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
mark_internal_command_completion_assessment(&mut hidden_assessment);
|
||||
let visible_query = api::Message {
|
||||
id: "visible-query".to_string(),
|
||||
task_id: "task".to_string(),
|
||||
message: Some(api::message::Message::UserQuery(api::message::UserQuery {
|
||||
query: "What changed?".to_string(),
|
||||
..Default::default()
|
||||
})),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let inputs = user_inputs_from_messages(&[hidden_assessment, visible_query]);
|
||||
assert_eq!(inputs.len(), 1);
|
||||
assert!(matches!(
|
||||
&inputs[0],
|
||||
AIAgentInput::UserQuery { query, .. } if query == "What changed?"
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_control_tool_call_converts_to_action_message() {
|
||||
let task_id = TaskId::new("task".to_string());
|
||||
|
||||
@@ -348,6 +348,43 @@ fn convert_input_to_user_input(
|
||||
}
|
||||
))
|
||||
}
|
||||
AIAgentInput::CommandCompletionAssessment {
|
||||
prompt,
|
||||
completed_command:
|
||||
RunningCommand {
|
||||
command,
|
||||
block_id,
|
||||
grid_contents: output,
|
||||
cursor,
|
||||
requested_command_id,
|
||||
is_alt_screen_active,
|
||||
},
|
||||
..
|
||||
} => Ok(
|
||||
api::request::input::user_inputs::user_input::Input::CliAgentUserQuery(
|
||||
api::request::input::CliAgentUserQuery {
|
||||
user_query: Some(api::request::input::UserQuery {
|
||||
query: prompt,
|
||||
referenced_attachments: Default::default(),
|
||||
mode: Some(UserQueryMode::Normal.into()),
|
||||
intended_agent: api::AgentType::Primary.into(),
|
||||
}),
|
||||
running_command: Some(api::RunningShellCommand {
|
||||
command,
|
||||
snapshot: Some(api::LongRunningShellCommandSnapshot {
|
||||
output,
|
||||
cursor,
|
||||
command_id: block_id.as_str().to_owned(),
|
||||
is_alt_screen_active,
|
||||
is_preempted: false,
|
||||
}),
|
||||
}),
|
||||
run_shell_command_tool_call_id: requested_command_id
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or_default(),
|
||||
},
|
||||
),
|
||||
),
|
||||
AIAgentInput::ActionResult { result, .. } => result.try_into(),
|
||||
AIAgentInput::MessagesReceivedFromAgents { messages } => Ok(
|
||||
api::request::input::user_inputs::user_input::Input::MessagesReceivedFromAgents(
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use galaxy_core::command::ExitCode;
|
||||
use warp_multi_agent_api as api;
|
||||
|
||||
use crate::ai::agent::task::TaskId;
|
||||
use crate::ai::agent::{
|
||||
AIAgentActionResult, AIAgentActionResultType, AIAgentContext, ImageContext,
|
||||
TransferShellCommandControlToUserResult,
|
||||
AIAgentActionResult, AIAgentActionResultType, AIAgentContext, AIAgentInput, ImageContext,
|
||||
RunningCommand, TransferShellCommandControlToUserResult, UserQueryMode,
|
||||
};
|
||||
use crate::terminal::model::block::BlockId;
|
||||
|
||||
@@ -132,6 +134,84 @@ fn git_context_deserializes_legacy_string_pull_request_number() {
|
||||
assert_eq!(pull_request.number, 42);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_completion_assessment_converts_to_primary_cli_query() {
|
||||
let block_id = BlockId::from("completed-block".to_string());
|
||||
let converted = super::convert_input(vec![AIAgentInput::CommandCompletionAssessment {
|
||||
prompt: "Summarize whether the command succeeded.".to_string(),
|
||||
context: Arc::from([AIAgentContext::SelectedText("root context".to_string())]),
|
||||
completed_command: RunningCommand {
|
||||
command: "cargo test -p galaxy".to_string(),
|
||||
block_id: block_id.clone(),
|
||||
grid_contents: "test result: ok".to_string(),
|
||||
cursor: "cursor".to_string(),
|
||||
requested_command_id: Some("run-call".to_string().into()),
|
||||
is_alt_screen_active: true,
|
||||
},
|
||||
}])
|
||||
.unwrap();
|
||||
|
||||
let Some(api::request::input::Type::UserInputs(inputs)) = converted.r#type else {
|
||||
panic!("expected user inputs");
|
||||
};
|
||||
let Some(api::request::input::user_inputs::user_input::Input::CliAgentUserQuery(query)) =
|
||||
inputs.inputs[0].input.as_ref()
|
||||
else {
|
||||
panic!("expected CLI agent query");
|
||||
};
|
||||
let user_query = query
|
||||
.user_query
|
||||
.as_ref()
|
||||
.expect("expected assessment prompt");
|
||||
assert_eq!(user_query.query, "Summarize whether the command succeeded.");
|
||||
assert_eq!(user_query.intended_agent(), api::AgentType::Primary);
|
||||
let command = query
|
||||
.running_command
|
||||
.as_ref()
|
||||
.expect("expected completed command");
|
||||
assert_eq!(command.command, "cargo test -p galaxy");
|
||||
let snapshot = command.snapshot.as_ref().expect("expected final snapshot");
|
||||
assert_eq!(snapshot.command_id, block_id.as_str());
|
||||
assert_eq!(snapshot.output, "test result: ok");
|
||||
assert_eq!(snapshot.cursor, "cursor");
|
||||
assert!(snapshot.is_alt_screen_active);
|
||||
assert_eq!(query.run_shell_command_tool_call_id, "run-call");
|
||||
|
||||
let active = super::convert_input(vec![AIAgentInput::UserQuery {
|
||||
query: "Keep monitoring.".to_string(),
|
||||
context: Arc::from([]),
|
||||
static_query_type: None,
|
||||
referenced_attachments: Default::default(),
|
||||
user_query_mode: UserQueryMode::Normal,
|
||||
running_command: Some(RunningCommand {
|
||||
command: "cargo test -p galaxy".to_string(),
|
||||
block_id,
|
||||
grid_contents: "still running".to_string(),
|
||||
cursor: String::new(),
|
||||
requested_command_id: None,
|
||||
is_alt_screen_active: false,
|
||||
}),
|
||||
intended_agent: None,
|
||||
}])
|
||||
.unwrap();
|
||||
let Some(api::request::input::Type::UserInputs(inputs)) = active.r#type else {
|
||||
panic!("expected active user inputs");
|
||||
};
|
||||
let Some(api::request::input::user_inputs::user_input::Input::CliAgentUserQuery(query)) =
|
||||
inputs.inputs[0].input.as_ref()
|
||||
else {
|
||||
panic!("expected active CLI agent query");
|
||||
};
|
||||
assert_eq!(
|
||||
query
|
||||
.user_query
|
||||
.as_ref()
|
||||
.expect("expected active monitor prompt")
|
||||
.intended_agent(),
|
||||
api::AgentType::Cli
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transfer_control_snapshot_result_converts_to_tool_call_result_input() {
|
||||
let block_id = BlockId::default();
|
||||
|
||||
@@ -7,7 +7,7 @@ use warp_multi_agent_api as api;
|
||||
|
||||
use super::convert_to::convert_input;
|
||||
use super::{ConvertToAPITypeError, RequestParams, ResponseStream, StreamEvent};
|
||||
use crate::ai::agent::redaction;
|
||||
use crate::ai::agent::{redaction, AIAgentInput};
|
||||
use crate::ai::openai::translator as openai_translator;
|
||||
use crate::ai::provider::ProviderConfig;
|
||||
use crate::server::server_api::AIApiError;
|
||||
@@ -97,6 +97,10 @@ pub async fn generate_multi_agent_output(
|
||||
);
|
||||
}
|
||||
|
||||
let emit_user_query_message = !params
|
||||
.input
|
||||
.iter()
|
||||
.any(|input| matches!(input, AIAgentInput::CommandCompletionAssessment { .. }));
|
||||
let mut request = api::Request {
|
||||
task_context: Some(api::request::TaskContext {
|
||||
tasks: params.tasks,
|
||||
@@ -189,6 +193,7 @@ pub async fn generate_multi_agent_output(
|
||||
progressive_summary: params.progressive_summary.clone(),
|
||||
messages_sent: params.messages_sent.clone(),
|
||||
global_rules: params.global_rules.clone(),
|
||||
emit_user_query_message,
|
||||
};
|
||||
|
||||
match openai_translator::execute(translator_request, &mut request).await {
|
||||
@@ -223,6 +228,7 @@ pub async fn generate_multi_agent_output(
|
||||
bedrock_progressive_summary: params.progressive_summary.clone(),
|
||||
bedrock_messages_sent: params.messages_sent.clone(),
|
||||
global_rules: params.global_rules.clone(),
|
||||
emit_user_query_message,
|
||||
};
|
||||
|
||||
match crate::ai::bedrock::translator::execute(translator_request, &mut request).await {
|
||||
|
||||
Reference in New Issue
Block a user