fix(kernel): enable multi-agent compilation + A2A routing tests
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
- director.rs: add missing CompletionRequest fields (thinking_enabled, reasoning_effort, plan_mode) for multi-agent feature gate - agents.rs: remove unused AgentState import behind multi-agent feature - lib.rs: replace ambiguous glob re-export with explicit director types, resolving AgentRole conflict between director and generation modules - a2a.rs: add 5 integration tests covering direct message delivery, broadcast routing, group messaging, agent unregistration, and expired message rejection (10 total A2A tests, all passing) - Verified: 537 workspace tests pass with multi-agent feature enabled Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -448,6 +448,9 @@ Respond with ONLY the number (1-{}) of the agent who should speak next. No expla
|
||||
temperature: Some(0.3),
|
||||
stop: vec![],
|
||||
stream: false,
|
||||
thinking_enabled: false,
|
||||
reasoning_effort: None,
|
||||
plan_mode: false,
|
||||
};
|
||||
|
||||
match driver.complete(request).await {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Agent CRUD operations
|
||||
|
||||
use zclaw_types::{AgentConfig, AgentId, AgentInfo, AgentState, Event, Result};
|
||||
use zclaw_types::{AgentConfig, AgentId, AgentInfo, Event, Result};
|
||||
|
||||
#[cfg(feature = "multi-agent")]
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -22,7 +22,11 @@ pub use events::*;
|
||||
pub use config::*;
|
||||
pub use trigger_manager::{TriggerManager, TriggerEntry, TriggerUpdateRequest, TriggerManagerConfig};
|
||||
#[cfg(feature = "multi-agent")]
|
||||
pub use director::*;
|
||||
pub use director::{
|
||||
Director, DirectorConfig, DirectorBuilder, DirectorAgent,
|
||||
ConversationState, ScheduleStrategy,
|
||||
// Note: AgentRole is intentionally NOT re-exported here — use generation::AgentRole instead
|
||||
};
|
||||
#[cfg(feature = "multi-agent")]
|
||||
pub use zclaw_protocols::{
|
||||
A2aRouter, A2aAgentProfile, A2aCapability, A2aEnvelope, A2aMessageType, A2aRecipient,
|
||||
|
||||
@@ -754,4 +754,184 @@ mod tests {
|
||||
let cap_index = router.capability_index.read().await;
|
||||
assert!(cap_index.contains_key("code-generation"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_direct_message_delivery() {
|
||||
let router = A2aRouter::new(AgentId::new());
|
||||
|
||||
// Register two agents
|
||||
let alice_id = AgentId::new();
|
||||
let bob_id = AgentId::new();
|
||||
|
||||
let alice_profile = A2aAgentProfile {
|
||||
id: alice_id, name: "Alice".into(), description: String::new(),
|
||||
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||
};
|
||||
let bob_profile = A2aAgentProfile {
|
||||
id: bob_id, name: "Bob".into(), description: String::new(),
|
||||
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||
};
|
||||
|
||||
let mut alice_rx = router.register_agent(alice_profile).await;
|
||||
let mut bob_rx = router.register_agent(bob_profile).await;
|
||||
|
||||
// Alice sends direct message to Bob
|
||||
let envelope = A2aEnvelope::new(
|
||||
alice_id,
|
||||
A2aRecipient::Direct { agent_id: bob_id },
|
||||
A2aMessageType::Notification,
|
||||
serde_json::json!({"msg": "hello bob"}),
|
||||
);
|
||||
router.route(envelope).await.unwrap();
|
||||
|
||||
// Bob should receive it
|
||||
let received = bob_rx.recv().await.unwrap();
|
||||
assert_eq!(received.from, alice_id);
|
||||
assert_eq!(received.payload["msg"], "hello bob");
|
||||
|
||||
// Alice should NOT receive it
|
||||
assert!(alice_rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_broadcast_delivery() {
|
||||
let router = A2aRouter::new(AgentId::new());
|
||||
|
||||
let alice_id = AgentId::new();
|
||||
let bob_id = AgentId::new();
|
||||
let carol_id = AgentId::new();
|
||||
|
||||
let make_profile = |id: AgentId, name: &str| A2aAgentProfile {
|
||||
id, name: name.into(), description: String::new(),
|
||||
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||
};
|
||||
|
||||
let mut alice_rx = router.register_agent(make_profile(alice_id, "Alice")).await;
|
||||
let mut bob_rx = router.register_agent(make_profile(bob_id, "Bob")).await;
|
||||
let mut carol_rx = router.register_agent(make_profile(carol_id, "Carol")).await;
|
||||
|
||||
// Alice broadcasts
|
||||
let envelope = A2aEnvelope::new(
|
||||
alice_id,
|
||||
A2aRecipient::Broadcast,
|
||||
A2aMessageType::Notification,
|
||||
serde_json::json!({"announcement": "standup in 5"}),
|
||||
);
|
||||
router.route(envelope).await.unwrap();
|
||||
|
||||
// Bob and Carol should receive, Alice should NOT (sender excluded)
|
||||
let bob_msg = bob_rx.recv().await.unwrap();
|
||||
assert_eq!(bob_msg.payload["announcement"], "standup in 5");
|
||||
|
||||
let carol_msg = carol_rx.recv().await.unwrap();
|
||||
assert_eq!(carol_msg.payload["announcement"], "standup in 5");
|
||||
|
||||
assert!(alice_rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_group_message_delivery() {
|
||||
let router = A2aRouter::new(AgentId::new());
|
||||
|
||||
let alice_id = AgentId::new();
|
||||
let bob_id = AgentId::new();
|
||||
let carol_id = AgentId::new();
|
||||
|
||||
let make_profile = |id: AgentId, name: &str| A2aAgentProfile {
|
||||
id, name: name.into(), description: String::new(),
|
||||
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||
};
|
||||
|
||||
let mut alice_rx = router.register_agent(make_profile(alice_id, "Alice")).await;
|
||||
let mut bob_rx = router.register_agent(make_profile(bob_id, "Bob")).await;
|
||||
let mut carol_rx = router.register_agent(make_profile(carol_id, "Carol")).await;
|
||||
|
||||
// Add Bob and Carol to "dev-team" group
|
||||
router.add_to_group("dev-team", bob_id).await;
|
||||
router.add_to_group("dev-team", carol_id).await;
|
||||
|
||||
// Alice sends to group
|
||||
let envelope = A2aEnvelope::new(
|
||||
alice_id,
|
||||
A2aRecipient::Group { group_id: "dev-team".into() },
|
||||
A2aMessageType::Notification,
|
||||
serde_json::json!({"sprint": "review"}),
|
||||
);
|
||||
router.route(envelope).await.unwrap();
|
||||
|
||||
// Bob and Carol should receive
|
||||
let bob_msg = bob_rx.recv().await.unwrap();
|
||||
assert_eq!(bob_msg.payload["sprint"], "review");
|
||||
let carol_msg = carol_rx.recv().await.unwrap();
|
||||
assert_eq!(carol_msg.payload["sprint"], "review");
|
||||
|
||||
// Alice not in group, should NOT receive
|
||||
assert!(alice_rx.try_recv().is_err());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_unregister_agent() {
|
||||
let router = A2aRouter::new(AgentId::new());
|
||||
|
||||
let agent_id = AgentId::new();
|
||||
let profile = A2aAgentProfile {
|
||||
id: agent_id, name: "Temp".into(), description: String::new(),
|
||||
capabilities: vec![A2aCapability {
|
||||
name: "temp-work".into(), description: "temp".into(),
|
||||
input_schema: None, output_schema: None, requires_approval: false,
|
||||
version: "1.0.0".into(), tags: vec![],
|
||||
}],
|
||||
protocols: vec!["a2a".into()], role: "worker".into(),
|
||||
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||
};
|
||||
|
||||
router.register_agent(profile).await;
|
||||
assert_eq!(router.list_profiles().await.len(), 1);
|
||||
|
||||
// Discover should find it
|
||||
let found = router.discover("temp-work").await.unwrap();
|
||||
assert_eq!(found.len(), 1);
|
||||
|
||||
// Unregister
|
||||
router.unregister_agent(&agent_id).await;
|
||||
assert_eq!(router.list_profiles().await.len(), 0);
|
||||
|
||||
// Capability index should be cleaned
|
||||
let found_after = router.discover("temp-work").await.unwrap();
|
||||
assert!(found_after.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_expired_message_rejected() {
|
||||
let router = A2aRouter::new(AgentId::new());
|
||||
|
||||
let alice_id = AgentId::new();
|
||||
let bob_id = AgentId::new();
|
||||
|
||||
let profile = |id: AgentId| A2aAgentProfile {
|
||||
id, name: "Agent".into(), description: String::new(),
|
||||
capabilities: vec![], protocols: vec!["a2a".into()], role: "worker".into(),
|
||||
priority: 5, metadata: HashMap::new(), groups: vec![], last_seen: 0,
|
||||
};
|
||||
|
||||
let _rx = router.register_agent(profile(alice_id)).await;
|
||||
let _rx = router.register_agent(profile(bob_id)).await;
|
||||
|
||||
// Create envelope with already-expired TTL
|
||||
let mut envelope = A2aEnvelope::new(
|
||||
alice_id,
|
||||
A2aRecipient::Direct { agent_id: bob_id },
|
||||
A2aMessageType::Notification,
|
||||
serde_json::json!({}),
|
||||
);
|
||||
envelope.ttl = 1; // 1 second
|
||||
envelope.timestamp = 0; // Far in the past — definitely expired
|
||||
|
||||
let result = router.route(envelope).await;
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user