Yield orchestrators for child groups and correct subscription context catalogs
This commit is contained in:
@@ -34,6 +34,7 @@ use crate::ai::agent::{
|
||||
};
|
||||
use crate::ai::auth_secret_types::auth_secret_types_for_harness;
|
||||
use crate::ai::blocklist::inline_action::orchestration_controls::OrchestrationEditState;
|
||||
use crate::ai::blocklist::orchestration_events::OrchestrationEventService;
|
||||
use crate::ai::blocklist::{BlocklistAIHistoryModel, BlocklistAIPermissions};
|
||||
use crate::ai::cloud_agent_settings::CloudAgentSettings;
|
||||
use crate::ai::document::plan_publication::{
|
||||
@@ -180,6 +181,9 @@ impl RunAgentsExecutor {
|
||||
"RunAgents cancellation detached {detached_dispatches} pending child dispatch(es) for action {action_id}"
|
||||
);
|
||||
if self.pending.remove(&action_key).is_some() {
|
||||
OrchestrationEventService::handle(ctx).update(ctx, |service, ctx| {
|
||||
service.seal_local_group(conversation_id, &action_id.to_string(), Vec::new(), ctx);
|
||||
});
|
||||
ctx.emit(RunAgentsExecutorEvent::SpawningFinished {
|
||||
conversation_id,
|
||||
action_id: action_id.clone(),
|
||||
@@ -423,15 +427,30 @@ impl RunAgentsExecutor {
|
||||
}),
|
||||
);
|
||||
|
||||
if matches!(run_execution_mode, RunAgentsExecutionMode::Local) {
|
||||
OrchestrationEventService::handle(ctx).update(ctx, |service, ctx| {
|
||||
service.begin_local_group(parent_conversation_id, &action_id.to_string(), ctx);
|
||||
});
|
||||
}
|
||||
let mut slots: Vec<ChildSlot> = Vec::with_capacity(agent_run_configs.len());
|
||||
let wait_policy = match &run_execution_mode {
|
||||
RunAgentsExecutionMode::Local => StartAgentWaitPolicy::Completion,
|
||||
RunAgentsExecutionMode::Local => StartAgentWaitPolicy::LocalStartup,
|
||||
RunAgentsExecutionMode::Remote { .. } => StartAgentWaitPolicy::Startup,
|
||||
};
|
||||
for cfg in &agent_run_configs {
|
||||
let normalized_name = normalize_agent_name(&cfg.name)
|
||||
.expect("validated RunAgents requests have non-empty agent names");
|
||||
if let Some(child_conversation_id) = recovery_children.remove(&normalized_name) {
|
||||
if matches!(run_execution_mode, RunAgentsExecutionMode::Local) {
|
||||
OrchestrationEventService::handle(ctx).update(ctx, |service, ctx| {
|
||||
service.register_local_group_child(
|
||||
parent_conversation_id,
|
||||
&action_id.to_string(),
|
||||
child_conversation_id,
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
}
|
||||
let dispatch = self.start_agent_executor.update(ctx, |executor, exec_ctx| {
|
||||
executor.reattach(
|
||||
action_id.clone(),
|
||||
@@ -509,7 +528,7 @@ impl RunAgentsExecutor {
|
||||
}),
|
||||
);
|
||||
let dispatch = self.start_agent_executor.update(ctx, |executor, exec_ctx| {
|
||||
executor.dispatch(
|
||||
let mut dispatch = executor.dispatch(
|
||||
action_id.clone(),
|
||||
cfg.name.clone(),
|
||||
prompt,
|
||||
@@ -518,7 +537,11 @@ impl RunAgentsExecutor {
|
||||
parent_conversation_id,
|
||||
parent_run_id.clone(),
|
||||
exec_ctx,
|
||||
)
|
||||
);
|
||||
if matches!(run_execution_mode, RunAgentsExecutionMode::Local) {
|
||||
executor.yield_after_local_start(&mut dispatch);
|
||||
}
|
||||
dispatch
|
||||
});
|
||||
slots.push(ChildSlot::Pending(dispatch));
|
||||
}
|
||||
@@ -583,6 +606,16 @@ impl RunAgentsExecutor {
|
||||
})
|
||||
.collect();
|
||||
me.record_launched_agents(parent_conversation_id_for_result, &agents);
|
||||
if matches!(run_execution_mode_for_aggr, RunAgentsExecutionMode::Local) {
|
||||
let errors = agents.iter().filter_map(|agent| match &agent.kind {
|
||||
RunAgentsAgentOutcomeKind::Failed { error } => Some(format!("{}: {error}", agent.name)),
|
||||
RunAgentsAgentOutcomeKind::Launched { .. } | RunAgentsAgentOutcomeKind::Completed { .. } => None,
|
||||
}).collect();
|
||||
OrchestrationEventService::handle(ctx).update(ctx, |service, ctx| {
|
||||
service.seal_local_group(parent_conversation_id_for_result, &action_id_for_aggr.to_string(), errors, ctx);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
log_run_agents_event(
|
||||
ctx,
|
||||
@@ -838,7 +871,9 @@ async fn resolve_child_slot_with_timeout(
|
||||
let request_id = dispatch.request_id;
|
||||
|
||||
let outcome = match dispatch.wait_policy {
|
||||
StartAgentWaitPolicy::Completion => dispatch.receiver.recv().await.ok(),
|
||||
StartAgentWaitPolicy::Completion | StartAgentWaitPolicy::LocalStartup => {
|
||||
dispatch.receiver.recv().await.ok()
|
||||
}
|
||||
StartAgentWaitPolicy::Startup => {
|
||||
let timeout = warpui::r#async::Timer::after(spawn_timeout);
|
||||
match futures::future::select(Box::pin(dispatch.receiver.recv()), Box::pin(timeout))
|
||||
|
||||
@@ -458,12 +458,12 @@ fn recovered_run_agents_reattaches_existing_child_and_launches_only_missing_chil
|
||||
assert_eq!(agents.len(), 2);
|
||||
assert!(matches!(
|
||||
&agents[0].kind,
|
||||
RunAgentsAgentOutcomeKind::Completed { agent_id, .. }
|
||||
RunAgentsAgentOutcomeKind::Launched { agent_id }
|
||||
if agent_id == &existing_child_id.to_string()
|
||||
));
|
||||
assert!(matches!(
|
||||
&agents[1].kind,
|
||||
RunAgentsAgentOutcomeKind::Completed { agent_id, .. }
|
||||
RunAgentsAgentOutcomeKind::Launched { agent_id }
|
||||
if agent_id == &missing_child_id.to_string()
|
||||
));
|
||||
});
|
||||
@@ -698,6 +698,7 @@ fn initialize_run_agents_test(app: &mut App, mode: ExecutionMode) -> RunAgentsTe
|
||||
let global_resource_handles = GlobalResourceHandles::mock(app);
|
||||
app.add_singleton_model(|_| GlobalResourceHandlesProvider::new(global_resource_handles));
|
||||
let history = app.add_singleton_model(|_| BlocklistAIHistoryModel::new(vec![], &[]));
|
||||
app.add_singleton_model(OrchestrationEventService::new);
|
||||
app.add_singleton_model(|_| CLIAgentSessionsModel::new());
|
||||
app.add_singleton_model(|_| ActiveAgentViewsModel::new());
|
||||
app.add_singleton_model(AgentNotificationsModel::new);
|
||||
|
||||
@@ -18,6 +18,7 @@ use crate::ai::agent::{
|
||||
StartAgentExecutionMode, StartAgentResult,
|
||||
};
|
||||
use crate::ai::blocklist::orchestration_event_streamer::OrchestrationEventStreamer;
|
||||
use crate::ai::blocklist::orchestration_events::OrchestrationEventService;
|
||||
use crate::ai::blocklist::{BlocklistAIHistoryEvent, BlocklistAIHistoryModel};
|
||||
use crate::ai::llms::{LLMId, LLMPreferences};
|
||||
use crate::ai::local_harness_setup::local_harness_product_disabled_message;
|
||||
@@ -42,6 +43,8 @@ pub enum StartAgentOutcome {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum StartAgentWaitPolicy {
|
||||
Startup,
|
||||
/// A local child has been linked; its persistent group owns completion.
|
||||
LocalStartup,
|
||||
Completion,
|
||||
}
|
||||
|
||||
@@ -227,6 +230,20 @@ impl StartAgentExecutor {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(pending) = self.pending.get(&request_id) {
|
||||
if matches!(pending.wait_policy, StartAgentWaitPolicy::LocalStartup) {
|
||||
let parent = pending.parent_conversation_id;
|
||||
let group_id = pending.action_id.to_string();
|
||||
OrchestrationEventService::handle(ctx).update(ctx, |service, ctx| {
|
||||
service.register_local_group_child(
|
||||
parent,
|
||||
&group_id,
|
||||
child_conversation_id,
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
if let Some(event) = child_link_event {
|
||||
ctx.emit(event);
|
||||
}
|
||||
@@ -373,6 +390,14 @@ impl StartAgentExecutor {
|
||||
| ConversationStatus::Blocked { .. }
|
||||
| ConversationStatus::WaitingForEvents => {}
|
||||
},
|
||||
Some(StartAgentWaitPolicy::LocalStartup) => {
|
||||
// Membership is durable before launch acknowledgement reaches the parent.
|
||||
if let Some(pending) = self.pending.remove(&request_id) {
|
||||
let _ = pending.sender.try_send(StartAgentOutcome::Started {
|
||||
agent_id: child_conversation_id.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Some(StartAgentWaitPolicy::Startup) => {
|
||||
if let Some(error_msg) = start_agent_startup_error_message_for_status(
|
||||
conversation.status(),
|
||||
@@ -683,6 +708,15 @@ impl StartAgentExecutor {
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn yield_after_local_start(&mut self, dispatch: &mut StartAgentDispatch) {
|
||||
if matches!(dispatch.wait_policy, StartAgentWaitPolicy::Completion) {
|
||||
dispatch.wait_policy = StartAgentWaitPolicy::LocalStartup;
|
||||
if let Some(pending) = self.pending.get_mut(&dispatch.request_id) {
|
||||
pending.wait_policy = StartAgentWaitPolicy::LocalStartup;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatch a pre-validated StartAgent request. Returns a receiver
|
||||
/// for the resulting [`StartAgentOutcome`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
||||
@@ -559,7 +559,7 @@ fn execute_resolves_error_when_request_linkage_happens_after_child_already_faile
|
||||
assert!(matches!(
|
||||
result,
|
||||
AIAgentActionResultType::StartAgent(StartAgentResult::Error { error, version })
|
||||
if error == "'codex' CLI not found on your machine."
|
||||
if error.contains("'codex' CLI not found on your machine.") && error.contains("send_message_to_agent")
|
||||
&& version == StartAgentVersion::V1
|
||||
));
|
||||
|
||||
@@ -906,7 +906,7 @@ fn direct_provider_nonterminal_states_remain_pending_until_cancelled() {
|
||||
assert!(matches!(
|
||||
state.dispatch.receiver.try_recv(),
|
||||
Ok(StartAgentOutcome::Error(error))
|
||||
if error == "Child agent was cancelled by the user."
|
||||
if error.contains("Child agent was cancelled by the user.") && error.contains("send_message_to_agent")
|
||||
));
|
||||
state.executor.read(&app, |executor, _| {
|
||||
assert!(executor.pending.is_empty());
|
||||
@@ -933,7 +933,7 @@ fn direct_provider_error_preserves_child_for_inspection() {
|
||||
|
||||
assert!(matches!(
|
||||
state.dispatch.receiver.try_recv(),
|
||||
Ok(StartAgentOutcome::Error(error)) if error == "Child execution failed"
|
||||
Ok(StartAgentOutcome::Error(error)) if error.contains("Child execution failed") && error.contains("send_message_to_agent")
|
||||
));
|
||||
state.captured_cleanup.read(&app, |captured, _| {
|
||||
assert!(captured.0.is_empty());
|
||||
@@ -1369,7 +1369,7 @@ fn execute_returns_detailed_error_when_child_startup_fails_before_initialization
|
||||
assert!(matches!(
|
||||
result,
|
||||
AIAgentActionResultType::StartAgent(StartAgentResult::Error { error, version })
|
||||
if error == "Failed to resolve child agent skills: review-comments"
|
||||
if error.contains("Failed to resolve child agent skills: review-comments") && error.contains("send_message_to_agent")
|
||||
&& version == StartAgentVersion::V1
|
||||
));
|
||||
});
|
||||
@@ -1739,7 +1739,7 @@ fn parallel_pendings_each_resolve_independently_via_recorded_child_id() {
|
||||
assert!(matches!(
|
||||
result_b,
|
||||
AIAgentActionResultType::StartAgent(StartAgentResult::Error { error, .. })
|
||||
if error == "Agent B init failed"
|
||||
if error.contains("Agent B init failed") && error.contains("send_message_to_agent")
|
||||
));
|
||||
|
||||
executor.read(&app, |executor, _| {
|
||||
|
||||
Reference in New Issue
Block a user