Preserve completed command assessments
This commit is contained in:
@@ -400,10 +400,10 @@ impl CLISubagentController {
|
||||
}
|
||||
|
||||
fn advance_completed_subagent(&mut self, block_id: &BlockId, ctx: &mut ModelContext<Self>) {
|
||||
let Some((task_id, completion)) = self
|
||||
let Some(completion) = self
|
||||
.active_subagents_by_block
|
||||
.get(block_id)
|
||||
.and_then(|state| Some((state.task_id.clone()?, state.completion.as_ref()?.clone())))
|
||||
.and_then(|state| state.completion.as_ref().cloned())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -433,7 +433,6 @@ impl CLISubagentController {
|
||||
let sent = self.controller.update(ctx, |controller, ctx| {
|
||||
controller.send_command_completion_assessment(
|
||||
completion.conversation_id,
|
||||
task_id,
|
||||
completion.prompt,
|
||||
completion.completed_command,
|
||||
ctx,
|
||||
|
||||
@@ -1375,6 +1375,7 @@ impl AIAgentInput {
|
||||
app,
|
||||
)),
|
||||
AIAgentInput::UserQuery { .. }
|
||||
| AIAgentInput::CommandCompletionAssessment { .. }
|
||||
| AIAgentInput::AutoCodeDiffQuery { .. }
|
||||
| AIAgentInput::ResumeConversation { .. }
|
||||
| AIAgentInput::InitProjectRules { .. }
|
||||
|
||||
@@ -3687,6 +3687,7 @@ pub(super) fn query_prefix_highlight_len(
|
||||
match input {
|
||||
AIAgentInput::InvokeSkill { skill, .. } => Some(1 + skill.name.len()),
|
||||
AIAgentInput::UserQuery { .. }
|
||||
| AIAgentInput::CommandCompletionAssessment { .. }
|
||||
| AIAgentInput::AutoCodeDiffQuery { .. }
|
||||
| AIAgentInput::ResumeConversation { .. }
|
||||
| AIAgentInput::InitProjectRules { .. }
|
||||
|
||||
@@ -1597,7 +1597,7 @@ impl BlocklistAIController {
|
||||
);
|
||||
}
|
||||
|
||||
/// Sends one non-preemptive final assessment to a completed CLI-monitor task.
|
||||
/// Sends one non-preemptive final assessment to the root task after a CLI monitor completes.
|
||||
///
|
||||
/// This deliberately bypasses `send_query`: command completion must not cancel
|
||||
/// another conversation, drain unrelated action results, or replace a request
|
||||
@@ -1605,8 +1605,7 @@ impl BlocklistAIController {
|
||||
pub fn send_command_completion_assessment(
|
||||
&mut self,
|
||||
conversation_id: AIConversationId,
|
||||
task_id: TaskId,
|
||||
query: String,
|
||||
prompt: String,
|
||||
completed_command: RunningCommand,
|
||||
ctx: &mut ModelContext<Self>,
|
||||
) -> bool {
|
||||
@@ -1621,6 +1620,16 @@ impl BlocklistAIController {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(root_task_id) = BlocklistAIHistoryModel::as_ref(ctx)
|
||||
.conversation(&conversation_id)
|
||||
.map(|conversation| conversation.get_root_task_id().clone())
|
||||
else {
|
||||
log::warn!(
|
||||
"Cannot send command completion assessment for missing conversation \
|
||||
{conversation_id:?}"
|
||||
);
|
||||
return false;
|
||||
};
|
||||
let context = input_context_for_request(
|
||||
false,
|
||||
self.context_model.as_ref(ctx),
|
||||
@@ -1629,16 +1638,12 @@ impl BlocklistAIController {
|
||||
ctx,
|
||||
);
|
||||
let request_input = RequestInput::for_task(
|
||||
vec![AIAgentInput::UserQuery {
|
||||
query,
|
||||
vec![AIAgentInput::CommandCompletionAssessment {
|
||||
prompt,
|
||||
context,
|
||||
static_query_type: None,
|
||||
referenced_attachments: HashMap::new(),
|
||||
user_query_mode: UserQueryMode::Normal,
|
||||
running_command: Some(completed_command),
|
||||
intended_agent: None,
|
||||
completed_command,
|
||||
}],
|
||||
task_id,
|
||||
root_task_id,
|
||||
&self.active_session,
|
||||
self.get_current_response_initiator(),
|
||||
conversation_id,
|
||||
@@ -4553,7 +4558,7 @@ impl BlocklistAIController {
|
||||
.as_ref()
|
||||
.map(|stream_cancellation| format!("{:?}", stream_cancellation.reason)),
|
||||
"queued_tools": remote_action_summaries(&actions_to_queue),
|
||||
"proposed_tools": remote_action_summaries(&proposed_actions),
|
||||
"proposed_tools": remote_action_summaries(proposed_actions),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -376,6 +376,7 @@ impl ResponseStream {
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn log_llm_request_started(
|
||||
ctx: &mut ModelContext<Self>,
|
||||
stream_id: &ResponseStreamId,
|
||||
|
||||
@@ -21,8 +21,10 @@ use crate::ai::agent::conversation::{
|
||||
ServerAIConversationMetadata,
|
||||
};
|
||||
use crate::ai::agent::{
|
||||
AIAgentExchange, AIAgentExchangeId, AIAgentInput, AIAgentOutputStatus, FinishedAIAgentOutput,
|
||||
RenderableAIError, Shared, TransientNetworkErrorKind, UserQueryMode,
|
||||
AIAgentExchange, AIAgentExchangeId, AIAgentInput, AIAgentOutput, AIAgentOutputMessage,
|
||||
AIAgentOutputMessageType, AIAgentOutputStatus, AIAgentText, AIAgentTextSection,
|
||||
AgentOutputText, FinishedAIAgentOutput, MessageId, RenderableAIError, RunningCommand, Shared,
|
||||
TransientNetworkErrorKind, UserQueryMode,
|
||||
};
|
||||
use crate::ai::ambient_agents::{
|
||||
conversation_output_status_from_conversation, AmbientAgentTaskId, AmbientConversationStatus,
|
||||
@@ -270,6 +272,125 @@ fn deactivating_cli_subtask_clears_activity_without_deleting_task() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completed_command_assessment_survives_cli_subtask_deactivation_on_root() {
|
||||
App::test((), |mut app| async move {
|
||||
initialize_history_persistence_for_tests(&mut app);
|
||||
let terminal_view_id = EntityId::new();
|
||||
let history_model = app.add_singleton_model(|_| BlocklistAIHistoryModel::new_for_test());
|
||||
let block_id = BlockId::new();
|
||||
let assessment_output = "The command completed successfully.";
|
||||
|
||||
history_model.update(&mut app, |model, ctx| {
|
||||
let conversation_id =
|
||||
model.start_new_conversation(terminal_view_id, false, false, false, ctx);
|
||||
let cli_task_id = model
|
||||
.create_cli_subagent_task_for_conversation(
|
||||
block_id.clone(),
|
||||
conversation_id,
|
||||
terminal_view_id,
|
||||
ctx,
|
||||
)
|
||||
.expect("CLI subtask should be created");
|
||||
|
||||
let monitor_exchange =
|
||||
create_exchange_with_query("Check the command status.", Local::now(), None);
|
||||
let monitor_exchange_id = monitor_exchange.id;
|
||||
let conversation = model
|
||||
.conversation_mut(&conversation_id)
|
||||
.expect("conversation should exist");
|
||||
conversation
|
||||
.append_task_exchange_for_test(
|
||||
&cli_task_id,
|
||||
monitor_exchange,
|
||||
terminal_view_id,
|
||||
ctx,
|
||||
)
|
||||
.expect("monitor exchange should be appended to the CLI task");
|
||||
|
||||
let now = Local::now();
|
||||
let assessment_exchange = AIAgentExchange {
|
||||
id: AIAgentExchangeId::new(),
|
||||
input: vec![AIAgentInput::CommandCompletionAssessment {
|
||||
prompt: "Assess the completed command.".to_string(),
|
||||
context: Arc::from([]),
|
||||
completed_command: RunningCommand {
|
||||
command: "cargo test -p galaxy".to_string(),
|
||||
block_id: block_id.clone(),
|
||||
grid_contents: "test result: ok".to_string(),
|
||||
cursor: String::new(),
|
||||
requested_command_id: None,
|
||||
is_alt_screen_active: false,
|
||||
},
|
||||
}],
|
||||
output_status: AIAgentOutputStatus::Finished {
|
||||
finished_output: FinishedAIAgentOutput::Success {
|
||||
output: Shared::new(AIAgentOutput {
|
||||
messages: vec![AIAgentOutputMessage {
|
||||
id: MessageId::new("assessment-output".to_string()),
|
||||
message: AIAgentOutputMessageType::Text(AIAgentText {
|
||||
sections: vec![AIAgentTextSection::PlainText {
|
||||
text: AgentOutputText::from(assessment_output.to_string()),
|
||||
}],
|
||||
}),
|
||||
citations: vec![],
|
||||
}],
|
||||
..Default::default()
|
||||
}),
|
||||
},
|
||||
},
|
||||
added_message_ids: HashSet::new(),
|
||||
start_time: now,
|
||||
finish_time: Some(now),
|
||||
time_to_first_token_ms: None,
|
||||
working_directory: None,
|
||||
model_id: LLMId::from("test-model"),
|
||||
request_cost: None,
|
||||
coding_model_id: LLMId::from("test-coding-model"),
|
||||
cli_agent_model_id: LLMId::from("test-cli-agent-model"),
|
||||
computer_use_model_id: LLMId::from("test-computer-use-model"),
|
||||
response_initiator: None,
|
||||
};
|
||||
let assessment_exchange_id = assessment_exchange.id;
|
||||
model
|
||||
.conversation_mut(&conversation_id)
|
||||
.expect("conversation should exist")
|
||||
.append_root_exchange_for_test(assessment_exchange);
|
||||
|
||||
model
|
||||
.deactivate_cli_subagent_task_for_conversation(&block_id, conversation_id)
|
||||
.expect("CLI subtask should deactivate");
|
||||
|
||||
let conversation = model
|
||||
.conversation(&conversation_id)
|
||||
.expect("conversation should still exist");
|
||||
assert!(!conversation.has_active_subagent());
|
||||
let cli_task = conversation
|
||||
.get_task(&cli_task_id)
|
||||
.expect("CLI task should be retained after deactivation");
|
||||
assert_eq!(cli_task.exchanges_len(), 1);
|
||||
assert_eq!(
|
||||
cli_task.last_exchange().map(|exchange| exchange.id),
|
||||
Some(monitor_exchange_id)
|
||||
);
|
||||
|
||||
let root_exchange = conversation
|
||||
.latest_visible_exchange()
|
||||
.expect("root assessment output should remain visible");
|
||||
assert_eq!(root_exchange.id, assessment_exchange_id);
|
||||
assert!(matches!(
|
||||
root_exchange.input.as_slice(),
|
||||
[AIAgentInput::CommandCompletionAssessment { .. }]
|
||||
));
|
||||
assert!(root_exchange.input[0].display_query().is_none());
|
||||
assert_eq!(
|
||||
root_exchange.format_output_for_copy(None),
|
||||
assessment_output
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn monitoring_a_different_block_preserves_completed_cli_task_history() {
|
||||
App::test((), |mut app| async move {
|
||||
|
||||
@@ -77,7 +77,8 @@ impl TryFrom<&AIAgentInput> for PersistedAIInputType {
|
||||
AIAgentInput::PassiveSuggestionResult { suggestion: PassiveSuggestionResultType::CodeDiff { .. }, .. } => Err(anyhow!(
|
||||
"PassiveSuggestionResult::CodeDiff is not persisted as a query."
|
||||
)),
|
||||
AIAgentInput::ActionResult { .. }
|
||||
AIAgentInput::CommandCompletionAssessment { .. }
|
||||
| AIAgentInput::ActionResult { .. }
|
||||
| AIAgentInput::ResumeConversation { .. }
|
||||
| AIAgentInput::InitProjectRules { .. }
|
||||
| AIAgentInput::CreateEnvironment { .. }
|
||||
|
||||
Reference in New Issue
Block a user