Fix child startup readiness and ordered provider tools
Wait for shell bootstrap before dispatching child prompts, serialize provider preprocessing, and make permission callbacks idempotent. Restrict task lists to concrete multistep plans and stop inferring completion from tool activity. Update regression fixtures and resolve existing lint and test-layout failures. Verified formatting, both presubmit Clippy commands, and 354 targeted nextest tests.
This commit is contained in:
@@ -16,11 +16,23 @@ use crate::test_util::settings::initialize_history_persistence_for_tests;
|
||||
|
||||
const FIRST_REQUEST_ID: StartAgentRequestId = StartAgentRequestId::from_raw_for_test(0);
|
||||
|
||||
/// Stable placeholder run_id assigned to the parent conversation in tests
|
||||
/// that exercise the server-backed Oz child path. Tests without this id
|
||||
/// exercise the direct-provider local child path instead.
|
||||
/// Stable server run ID for parent conversations. Child execution mode, rather
|
||||
/// than the presence of this ID, selects startup versus completion waits.
|
||||
const PARENT_RUN_ID: &str = "00000000-0000-0000-0000-000000000001";
|
||||
|
||||
fn remote_execution_mode() -> StartAgentExecutionMode {
|
||||
StartAgentExecutionMode::Remote {
|
||||
environment_id: "env-123".to_string(),
|
||||
skill_references: vec![],
|
||||
model_id: String::new(),
|
||||
computer_use_enabled: false,
|
||||
worker_host: String::new(),
|
||||
harness_type: "oz".to_string(),
|
||||
title: String::new(),
|
||||
auth_secret_name: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct CapturedDirectProviderChildLinks(Vec<(AIAgentActionId, AIConversationId, AIConversationId)>);
|
||||
|
||||
@@ -394,10 +406,7 @@ fn execute_returns_error_when_child_startup_is_blocked_before_initialization() {
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
let action = build_start_agent_action(
|
||||
StartAgentVersion::V1,
|
||||
StartAgentExecutionMode::local_with_defaults(),
|
||||
);
|
||||
let action = build_start_agent_action(StartAgentVersion::V1, remote_execution_mode());
|
||||
|
||||
let execution = executor.update(&mut app, |executor, ctx| {
|
||||
let input = ExecuteActionInput {
|
||||
@@ -581,10 +590,7 @@ fn execute_resolves_success_when_request_linkage_happens_after_child_already_sta
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
let action = build_start_agent_action(
|
||||
StartAgentVersion::V1,
|
||||
StartAgentExecutionMode::local_with_defaults(),
|
||||
);
|
||||
let action = build_start_agent_action(StartAgentVersion::V1, remote_execution_mode());
|
||||
|
||||
let execution = executor.update(&mut app, |executor, ctx| {
|
||||
let input = ExecuteActionInput {
|
||||
@@ -739,10 +745,7 @@ fn hosted_child_link_does_not_publish_direct_provider_panel_event() {
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
let action = build_start_agent_action(
|
||||
StartAgentVersion::V1,
|
||||
StartAgentExecutionMode::local_with_defaults(),
|
||||
);
|
||||
let action = build_start_agent_action(StartAgentVersion::V1, remote_execution_mode());
|
||||
|
||||
let execution = executor.update(&mut app, |executor, ctx| {
|
||||
let input = ExecuteActionInput {
|
||||
@@ -1769,7 +1772,7 @@ struct PendingChildLaunch {
|
||||
child_conversation_id: AIConversationId,
|
||||
}
|
||||
|
||||
/// Dispatches a local child launch and creates (but does not yet link) its
|
||||
/// Dispatches a remote child launch and creates (but does not yet link) its
|
||||
/// child conversation, leaving one in-flight pending in the executor with a
|
||||
/// model subscribed to capture `CleanupFailedChildLaunch` events. Tests link
|
||||
/// the child and drive it to a terminal state, then assert on cleanup. The
|
||||
@@ -1803,10 +1806,7 @@ fn dispatch_pending_child_launch(
|
||||
ctx,
|
||||
);
|
||||
});
|
||||
let action = build_start_agent_action(
|
||||
StartAgentVersion::V1,
|
||||
StartAgentExecutionMode::local_with_defaults(),
|
||||
);
|
||||
let action = build_start_agent_action(StartAgentVersion::V1, remote_execution_mode());
|
||||
// The pending lives in the executor regardless of the returned execution,
|
||||
// and cleanup is emitted synchronously from the child status update, so the
|
||||
// action-result plumbing is discarded.
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::ai::blocklist::orchestration_event_streamer::OrchestrationEventStream
|
||||
use crate::ai::blocklist::BlocklistAIHistoryModel;
|
||||
use crate::server::server_api::ai::{AIClient, MockAIClient};
|
||||
use crate::server::server_api::ServerApiProvider;
|
||||
use crate::test_util::settings::initialize_history_persistence_for_tests;
|
||||
|
||||
#[test]
|
||||
fn watchdog_timeout_constants_match_documented_values() {
|
||||
@@ -88,24 +89,12 @@ fn execute_invokes_parent_registration_and_honors_child_short_circuit() {
|
||||
// without a server fetch (asserted via the mock's times(0) expectation),
|
||||
// and the wait still flips the conversation into WaitingForEvents.
|
||||
App::test((), |mut app| async move {
|
||||
initialize_history_persistence_for_tests(&mut app);
|
||||
let _flag_guard = FeatureFlag::WaitForEventsParentRegistration.override_enabled(true);
|
||||
|
||||
let terminal_view_id = EntityId::new();
|
||||
let history_model = app.add_singleton_model(|_| BlocklistAIHistoryModel::new(vec![], &[]));
|
||||
|
||||
// A streamer whose server fetch must never be called: the child
|
||||
// short-circuit precedes any `get_ambient_agent_task` call.
|
||||
let mut mock = MockAIClient::new();
|
||||
mock.expect_get_ambient_agent_task().times(0);
|
||||
let ai_client: Arc<dyn AIClient> = Arc::new(mock);
|
||||
let server_api = ServerApiProvider::new_for_test().get();
|
||||
// Held for the lifetime of the test so the mock's times(0) expectation
|
||||
// is verified on drop; resolved internally by `execute()` via
|
||||
// `OrchestrationEventStreamer::handle`.
|
||||
let _streamer = app.add_singleton_model(|ctx| {
|
||||
OrchestrationEventStreamer::new_with_clients_for_test(ai_client, server_api, ctx)
|
||||
});
|
||||
|
||||
let executor = app.add_model(|ctx| WaitForEventsExecutor::new(terminal_view_id, ctx));
|
||||
|
||||
// Child conversation: own run_id plus a parent_agent_id.
|
||||
@@ -123,6 +112,21 @@ fn execute_invokes_parent_registration_and_honors_child_short_circuit() {
|
||||
);
|
||||
});
|
||||
|
||||
// Subscribe after restoring the fixture so unrelated restore-time harness
|
||||
// discovery does not count as a parent-registration fetch.
|
||||
// A streamer whose server fetch must never be called: the child
|
||||
// short-circuit precedes any `get_ambient_agent_task` call.
|
||||
let mut mock = MockAIClient::new();
|
||||
mock.expect_get_ambient_agent_task().times(0);
|
||||
let ai_client: Arc<dyn AIClient> = Arc::new(mock);
|
||||
let server_api = ServerApiProvider::new_for_test().get();
|
||||
// Held for the lifetime of the test so the mock's times(0) expectation
|
||||
// is verified on drop; resolved internally by `execute()` via
|
||||
// `OrchestrationEventStreamer::handle`.
|
||||
let _streamer = app.add_singleton_model(|ctx| {
|
||||
OrchestrationEventStreamer::new_with_clients_for_test(ai_client, server_api, ctx)
|
||||
});
|
||||
|
||||
let action = AIAgentAction {
|
||||
id: AIAgentActionId::from("wait-action".to_string()),
|
||||
action: AIAgentActionType::WaitForEvents {
|
||||
|
||||
Reference in New Issue
Block a user